mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-27 20:21:24 +00:00
mpegtsdemux: accurate seeking
* pes header parsing for pts is ugly, refactor * timestamps/newsegment after seeking is still off
This commit is contained in:
parent
15391b29e1
commit
3ce1ec7c9c
3 changed files with 211 additions and 13 deletions
|
@ -31,6 +31,8 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <gst/gst-i18n-plugin.h>
|
||||
#include "mpegtsbase.h"
|
||||
#include "gstmpegdesc.h"
|
||||
|
@ -1221,6 +1223,8 @@ mpegts_base_handle_seek_event (MpegTSBase * base, GstPad * pad,
|
|||
GstSeekFlags flags;
|
||||
GstSeekType start_type, stop_type;
|
||||
gint64 start, stop;
|
||||
gchar *pad_name;
|
||||
guint16 pid = 0;
|
||||
|
||||
gst_event_parse_seek (event, &rate, &format, &flags, &start_type, &start,
|
||||
&stop_type, &stop);
|
||||
|
@ -1232,6 +1236,17 @@ mpegts_base_handle_seek_event (MpegTSBase * base, GstPad * pad,
|
|||
" stop: %" GST_TIME_FORMAT, rate, GST_TIME_ARGS (start),
|
||||
GST_TIME_ARGS (stop));
|
||||
|
||||
/* extract the pid from the pad name */
|
||||
pad_name = gst_pad_get_name (pad);
|
||||
if (pad_name) {
|
||||
gchar *pidstr = g_strrstr (pad_name, "_");
|
||||
if (pidstr) {
|
||||
pidstr++;
|
||||
pid = g_ascii_strtoull (pidstr, NULL, 16);
|
||||
}
|
||||
g_free (pad_name);
|
||||
}
|
||||
|
||||
flush = flags & GST_SEEK_FLAG_FLUSH;
|
||||
|
||||
if (base->mode == BASE_MODE_PUSHING) {
|
||||
|
@ -1267,7 +1282,7 @@ mpegts_base_handle_seek_event (MpegTSBase * base, GstPad * pad,
|
|||
if (format == GST_FORMAT_TIME) {
|
||||
/* If the subclass can seek, do that */
|
||||
if (klass->seek) {
|
||||
ret = klass->seek (base, event);
|
||||
ret = klass->seek (base, event, pid);
|
||||
if (G_UNLIKELY (ret != GST_FLOW_OK)) {
|
||||
GST_WARNING ("seeking failed %s", gst_flow_get_name (ret));
|
||||
goto done;
|
||||
|
|
|
@ -142,7 +142,7 @@ struct _MpegTSBaseClass {
|
|||
GstFlowReturn (*find_timestamps) (MpegTSBase * base, guint64 initoff, guint64 *offset);
|
||||
|
||||
/* seek is called to wait for seeking */
|
||||
GstFlowReturn (*seek) (MpegTSBase * base, GstEvent * event);
|
||||
GstFlowReturn (*seek) (MpegTSBase * base, GstEvent * event, guint16 pid);
|
||||
|
||||
/* signals */
|
||||
void (*pat_info) (GstStructure *pat);
|
||||
|
|
|
@ -190,12 +190,12 @@ gst_ts_demux_stream_added (MpegTSBase * base, MpegTSBaseStream * stream,
|
|||
MpegTSBaseProgram * program);
|
||||
static void
|
||||
gst_ts_demux_stream_removed (MpegTSBase * base, MpegTSBaseStream * stream);
|
||||
static GstFlowReturn gst_ts_demux_do_seek (MpegTSBase * base, GstEvent * event);
|
||||
static GstFlowReturn
|
||||
find_pcr_packet (MpegTSBase * base, guint64 offset, gint64 length,
|
||||
TSPcrOffset * pcroffset);
|
||||
static GstFlowReturn
|
||||
find_timestamps (MpegTSBase * base, guint64 initoff, guint64 * offset);
|
||||
static GstFlowReturn gst_ts_demux_do_seek (MpegTSBase * base, GstEvent * event,
|
||||
guint16 pid);
|
||||
static GstFlowReturn find_pcr_packet (MpegTSBase * base, guint64 offset,
|
||||
gint64 length, TSPcrOffset * pcroffset);
|
||||
static GstFlowReturn find_timestamps (MpegTSBase * base, guint64 initoff,
|
||||
guint64 * offset);
|
||||
static void gst_ts_demux_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * pspec);
|
||||
static void gst_ts_demux_get_property (GObject * object, guint prop_id,
|
||||
|
@ -430,6 +430,164 @@ calculate_gsttime (TSPcrOffset * start, guint64 pcr)
|
|||
return time;
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_ts_demux_parse_pes_header_pts (GstTSDemux * demux,
|
||||
MpegTSPacketizerPacket * packet, guint64 * time)
|
||||
{
|
||||
GstFlowReturn res = GST_FLOW_ERROR;
|
||||
guint8 *data;
|
||||
guint32 length;
|
||||
guint32 psc_stid;
|
||||
guint8 stid;
|
||||
guint16 pesplength;
|
||||
guint8 PES_header_data_length = 0;
|
||||
|
||||
data = packet->payload;
|
||||
length = packet->data_end - data;
|
||||
|
||||
GST_MEMDUMP ("Header buffer", data, MIN (length, 32));
|
||||
|
||||
/* packet_start_code_prefix 24
|
||||
* stream_id 8*/
|
||||
psc_stid = GST_READ_UINT32_BE (data);
|
||||
data += 4;
|
||||
length -= 4;
|
||||
if (G_UNLIKELY ((psc_stid & 0xffffff00) != 0x00000100)) {
|
||||
GST_DEBUG ("WRONG PACKET START CODE! pid: 0x%x", packet->pid);
|
||||
goto discont;
|
||||
}
|
||||
stid = psc_stid & 0x000000ff;
|
||||
GST_LOG ("stream_id:0x%02x", stid);
|
||||
|
||||
/* PES_packet_length 16 */
|
||||
/* FIXME : store the expected pes length somewhere ? */
|
||||
pesplength = GST_READ_UINT16_BE (data);
|
||||
data += 2;
|
||||
length -= 2;
|
||||
GST_LOG ("PES_packet_length:%d", pesplength);
|
||||
|
||||
/* FIXME : Only parse header on streams which require it (see table 2-21) */
|
||||
if (stid != 0xbf) {
|
||||
guint64 pts;
|
||||
guint8 p1, p2;
|
||||
p1 = *data++;
|
||||
p2 = *data++;
|
||||
PES_header_data_length = *data++ + 3;
|
||||
length -= 3;
|
||||
|
||||
GST_LOG ("0x%02x 0x%02x 0x%02x", p1, p2, PES_header_data_length);
|
||||
GST_LOG ("PES header data length:%d", PES_header_data_length);
|
||||
|
||||
/* '10' 2
|
||||
* PES_scrambling_control 2
|
||||
* PES_priority 1
|
||||
* data_alignment_indicator 1
|
||||
* copyright 1
|
||||
* original_or_copy 1 */
|
||||
if (G_UNLIKELY ((p1 & 0xc0) != 0x80)) {
|
||||
GST_WARNING ("p1 >> 6 != 0x2");
|
||||
goto discont;
|
||||
}
|
||||
|
||||
/* PTS_DTS_flags 2
|
||||
* ESCR_flag 1
|
||||
* ES_rate_flag 1
|
||||
* DSM_trick_mode_flag 1
|
||||
* additional_copy_info_flag 1
|
||||
* PES_CRC_flag 1
|
||||
* PES_extension_flag 1*/
|
||||
|
||||
/* PES_header_data_length 8 */
|
||||
if (G_UNLIKELY (length < PES_header_data_length)) {
|
||||
GST_WARNING ("length < PES_header_data_length");
|
||||
goto discont;
|
||||
}
|
||||
|
||||
/* PTS 32 */
|
||||
if ((p2 & 0x80)) { /* PTS */
|
||||
READ_TS (data, pts, discont);
|
||||
length -= 4;
|
||||
*time = pts;
|
||||
res = GST_FLOW_OK;
|
||||
}
|
||||
}
|
||||
discont:
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/* performs a accurate seek to the last packet with pts < seektime */
|
||||
static GstFlowReturn
|
||||
gst_ts_demux_perform_accurate_seek (MpegTSBase * base, GstClockTime seektime,
|
||||
TSPcrOffset * pcroffset, gint64 length, gint16 pid)
|
||||
{
|
||||
GstTSDemux *demux = (GstTSDemux *) base;
|
||||
GstFlowReturn res = GST_FLOW_ERROR;
|
||||
gboolean done = FALSE;
|
||||
GstBuffer *buf;
|
||||
MpegTSPacketizerPacket packet;
|
||||
MpegTSPacketizerPacketReturn pret;
|
||||
gint64 offset = pcroffset->offset;
|
||||
gint64 scan_offset = MIN (length, 50 * MPEGTS_MAX_PACKETSIZE);
|
||||
|
||||
|
||||
GST_DEBUG ("accurate seek for %" GST_TIME_FORMAT " from offset: %"
|
||||
G_GINT64_FORMAT " in %" G_GINT64_FORMAT " bytes for PID: %d",
|
||||
GST_TIME_ARGS (seektime), pcroffset->offset, length, pid);
|
||||
|
||||
mpegts_packetizer_flush (base->packetizer);
|
||||
|
||||
while (!done && scan_offset <= length) {
|
||||
res =
|
||||
gst_pad_pull_range (base->sinkpad, offset + scan_offset,
|
||||
50 * MPEGTS_MAX_PACKETSIZE, &buf);
|
||||
if (res != GST_FLOW_OK)
|
||||
goto beach;
|
||||
mpegts_packetizer_push (base->packetizer, buf);
|
||||
|
||||
while ((!done)
|
||||
&& ((pret =
|
||||
mpegts_packetizer_next_packet (base->packetizer,
|
||||
&packet)) != PACKET_NEED_MORE)) {
|
||||
if (G_UNLIKELY (pret == PACKET_BAD))
|
||||
/* bad header, skip the packet */
|
||||
goto next;
|
||||
|
||||
if (packet.payload_unit_start_indicator)
|
||||
GST_DEBUG ("found packet for PID: %d with pcr: %" GST_TIME_FORMAT
|
||||
" at offset: %" G_GINT64_FORMAT, packet.pid,
|
||||
GST_TIME_ARGS (packet.pcr), packet.offset);
|
||||
|
||||
if (packet.payload != NULL && packet.payload_unit_start_indicator
|
||||
&& packet.pid == pid) {
|
||||
guint64 pts = 0;
|
||||
|
||||
res = gst_ts_demux_parse_pes_header_pts (demux, &packet, &pts);
|
||||
if (res == GST_FLOW_OK) {
|
||||
GstClockTime time = calculate_gsttime (pcroffset, pts * 300);
|
||||
|
||||
GST_DEBUG ("packet has PTS: %" GST_TIME_FORMAT,
|
||||
GST_TIME_ARGS (time));
|
||||
|
||||
if (time <= seektime) {
|
||||
pcroffset->gsttime = time;
|
||||
pcroffset->pcr = packet.pcr;
|
||||
pcroffset->offset = packet.offset;
|
||||
} else
|
||||
done = TRUE;
|
||||
} else
|
||||
goto next;
|
||||
}
|
||||
next:
|
||||
mpegts_packetizer_clear_packet (base->packetizer, &packet);
|
||||
}
|
||||
scan_offset += 50 * MPEGTS_MAX_PACKETSIZE;
|
||||
}
|
||||
|
||||
beach:
|
||||
mpegts_packetizer_flush (base->packetizer);
|
||||
return res;
|
||||
}
|
||||
|
||||
static gint
|
||||
TSPcrOffset_find (gconstpointer a, gconstpointer b, gpointer user_data)
|
||||
|
@ -449,11 +607,11 @@ TSPcrOffset_find (gconstpointer a, gconstpointer b, gpointer user_data)
|
|||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_ts_demux_perform_seek (MpegTSBase * base, GstSegment * segment)
|
||||
gst_ts_demux_perform_seek (MpegTSBase * base, GstSegment * segment, guint16 pid)
|
||||
{
|
||||
GstTSDemux *demux = (GstTSDemux *) base;
|
||||
GstFlowReturn res = GST_FLOW_ERROR;
|
||||
int loop_cnt = 0;
|
||||
int max_loop_cnt, loop_cnt = 0;
|
||||
double bias = 1.0;
|
||||
gint64 desired_offset;
|
||||
gint64 seekpos = 0;
|
||||
|
@ -461,6 +619,8 @@ gst_ts_demux_perform_seek (MpegTSBase * base, GstSegment * segment)
|
|||
GstClockTime seektime;
|
||||
TSPcrOffset seekpcroffset, pcr_start, pcr_stop, *tmp;
|
||||
|
||||
max_loop_cnt = (segment->flags & GST_SEEK_FLAG_ACCURATE) ? 25 : 10;
|
||||
|
||||
desired_offset = segment->last_stop;
|
||||
|
||||
seektime = desired_offset + demux->first_pcr.gsttime;
|
||||
|
@ -516,7 +676,8 @@ gst_ts_demux_perform_seek (MpegTSBase * base, GstSegment * segment)
|
|||
GST_TIME_ARGS (demux->cur_pcr.gsttime), demux->cur_pcr.offset, time_diff);
|
||||
|
||||
/* seek loop */
|
||||
while (loop_cnt++ < 10 && (time_diff < 0 || time_diff > 333 * GST_MSECOND)) {
|
||||
while (loop_cnt++ < max_loop_cnt && (time_diff < 0
|
||||
|| time_diff > 333 * GST_MSECOND)) {
|
||||
gint64 duration = pcr_stop.gsttime - pcr_start.gsttime;
|
||||
gint64 size = pcr_stop.offset - pcr_start.offset;
|
||||
|
||||
|
@ -580,6 +741,28 @@ gst_ts_demux_perform_seek (MpegTSBase * base, GstSegment * segment)
|
|||
|
||||
GST_DEBUG ("seeking finished after %d loops", loop_cnt);
|
||||
|
||||
if (segment->flags & GST_SEEK_FLAG_ACCURATE) {
|
||||
MpegTSBaseProgram *program = demux->program;
|
||||
|
||||
if (program->streams[pid]) {
|
||||
switch (program->streams[pid]->stream_type) {
|
||||
case ST_VIDEO_MPEG1:
|
||||
case ST_VIDEO_MPEG2:
|
||||
case ST_VIDEO_MPEG4:
|
||||
case ST_VIDEO_H264:
|
||||
case ST_VIDEO_DIRAC:
|
||||
GST_WARNING ("no payload parser for stream 0x%04x type: 0x%02x", pid,
|
||||
program->streams[pid]->stream_type);
|
||||
break;
|
||||
}
|
||||
} else
|
||||
GST_WARNING ("no stream info for PID: 0x%04x", pid);
|
||||
seekpcroffset.pcr = pcr_start.pcr;
|
||||
seekpcroffset.offset = pcr_start.offset;
|
||||
res =
|
||||
gst_ts_demux_perform_accurate_seek (base, seektime, &seekpcroffset,
|
||||
pcr_stop.offset - pcr_start.offset, pid);
|
||||
}
|
||||
|
||||
segment->last_stop = seekpcroffset.gsttime;
|
||||
segment->time = seekpcroffset.gsttime;
|
||||
|
@ -600,7 +783,7 @@ done:
|
|||
|
||||
|
||||
static GstFlowReturn
|
||||
gst_ts_demux_do_seek (MpegTSBase * base, GstEvent * event)
|
||||
gst_ts_demux_do_seek (MpegTSBase * base, GstEvent * event, guint16 pid)
|
||||
{
|
||||
GstTSDemux *demux = (GstTSDemux *) base;
|
||||
GstFlowReturn res = GST_FLOW_ERROR;
|
||||
|
@ -655,7 +838,7 @@ gst_ts_demux_do_seek (MpegTSBase * base, GstEvent * event)
|
|||
GST_TIME_ARGS (seeksegment.last_stop),
|
||||
GST_TIME_ARGS (seeksegment.duration));
|
||||
|
||||
res = gst_ts_demux_perform_seek (base, &seeksegment);
|
||||
res = gst_ts_demux_perform_seek (base, &seeksegment, pid);
|
||||
if (G_UNLIKELY (res != GST_FLOW_OK)) {
|
||||
GST_WARNING ("seeking failed %s", gst_flow_get_name (res));
|
||||
goto done;
|
||||
|
|
Loading…
Reference in a new issue