mpegtsdemux: accurate seeking

* pes header parsing for pts is ugly, refactor
 * timestamps/newsegment after seeking is still off
This commit is contained in:
Janne Grunau 2011-03-22 16:49:13 +01:00 committed by Edward Hervey
parent 15391b29e1
commit 3ce1ec7c9c
3 changed files with 211 additions and 13 deletions

View file

@ -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;

View file

@ -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);

View file

@ -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;