ffdec: use a better algorithm to detect DTS timestamps

Add function to reset the timestamp tracking.
Check for reordered timestamps on the input buffers and assume PTS input
timestamps when we see reordered timestamps.
Recover from an occasionally wrong input timestamp by also tracking the output
timestamps. When we detect a reordered output timestamp, assume DTS input
timestamps again.

Fixes #611500
This commit is contained in:
Wim Taymans 2010-10-07 17:46:22 +02:00
parent a0bc6f6e9d
commit 3f213c1b73

View file

@ -91,10 +91,15 @@ struct _GstFFMpegDec
gboolean waiting_for_key; gboolean waiting_for_key;
gboolean discont; gboolean discont;
gboolean clear_ts; gboolean clear_ts;
guint64 next_ts;
GstClockTime last_out; /* for tracking DTS/PTS */
gboolean ts_is_dts;
gboolean has_b_frames; gboolean has_b_frames;
gboolean reordered_in;
GstClockTime last_in;
GstClockTime next_in;
gboolean reordered_out;
GstClockTime last_out;
GstClockTime next_out;
/* parsing */ /* parsing */
gboolean turnoff_parser; /* used for turning off aac raw parsing gboolean turnoff_parser; /* used for turning off aac raw parsing
@ -489,6 +494,17 @@ gst_ffmpegdec_query (GstPad * pad, GstQuery * query)
return res; return res;
} }
static void
gst_ffmpegdec_reset_ts (GstFFMpegDec * ffmpegdec)
{
ffmpegdec->last_in = GST_CLOCK_TIME_NONE;
ffmpegdec->next_in = GST_CLOCK_TIME_NONE;
ffmpegdec->last_out = GST_CLOCK_TIME_NONE;
ffmpegdec->next_out = GST_CLOCK_TIME_NONE;
ffmpegdec->reordered_in = FALSE;
ffmpegdec->reordered_out = FALSE;
}
static void static void
gst_ffmpegdec_update_qos (GstFFMpegDec * ffmpegdec, gdouble proportion, gst_ffmpegdec_update_qos (GstFFMpegDec * ffmpegdec, gdouble proportion,
GstClockTime timestamp) GstClockTime timestamp)
@ -670,8 +686,7 @@ gst_ffmpegdec_open (GstFFMpegDec * ffmpegdec)
break; break;
} }
ffmpegdec->last_out = GST_CLOCK_TIME_NONE; gst_ffmpegdec_reset_ts (ffmpegdec);
ffmpegdec->next_ts = GST_CLOCK_TIME_NONE;
/* FIXME, reset_qos holds the LOCK */ /* FIXME, reset_qos holds the LOCK */
ffmpegdec->proportion = 0.0; ffmpegdec->proportion = 0.0;
ffmpegdec->earliest_time = -1; ffmpegdec->earliest_time = -1;
@ -729,9 +744,6 @@ gst_ffmpegdec_setcaps (GstPad * pad, GstCaps * caps)
/* default is to let format decide if it needs a parser */ /* default is to let format decide if it needs a parser */
ffmpegdec->turnoff_parser = FALSE; ffmpegdec->turnoff_parser = FALSE;
/* assume PTS as input, we will adapt when we detect timestamp reordering
* in the output frames. */
ffmpegdec->ts_is_dts = FALSE;
ffmpegdec->has_b_frames = FALSE; ffmpegdec->has_b_frames = FALSE;
GST_LOG_OBJECT (ffmpegdec, "size %dx%d", ffmpegdec->context->width, GST_LOG_OBJECT (ffmpegdec, "size %dx%d", ffmpegdec->context->width,
@ -1688,7 +1700,6 @@ gst_ffmpegdec_video_frame (GstFFMpegDec * ffmpegdec,
|| ffmpegdec->context->hurry_up)) { || ffmpegdec->context->hurry_up)) {
/* we consumed some bytes but nothing decoded and we are skipping frames, /* we consumed some bytes but nothing decoded and we are skipping frames,
* disable the interpollation of DTS timestamps */ * disable the interpollation of DTS timestamps */
ffmpegdec->ts_is_dts = FALSE;
ffmpegdec->last_out = -1; ffmpegdec->last_out = -1;
} }
@ -1736,13 +1747,18 @@ gst_ffmpegdec_video_frame (GstFFMpegDec * ffmpegdec,
iskeyframe = check_keyframe (ffmpegdec); iskeyframe = check_keyframe (ffmpegdec);
/* check that the timestamps go upwards */ /* check that the timestamps go upwards */
if (ffmpegdec->last_out != -1) { if (ffmpegdec->last_out != -1 && ffmpegdec->last_out > out_pts) {
if (ffmpegdec->last_out > out_pts) { /* timestamps go backwards, this means frames were reordered and we must
/* timestamps go backwards, this means frames were reordered and we must * be dealing with DTS as the buffer timestamps */
* be dealing with DTS as the buffer timestamps */ if (!ffmpegdec->reordered_out) {
ffmpegdec->ts_is_dts = TRUE; GST_DEBUG_OBJECT (ffmpegdec, "detected reordered out timestamps");
GST_DEBUG_OBJECT (ffmpegdec, ffmpegdec->reordered_out = TRUE;
"timestamp discont, we have DTS as timestamps"); }
if (ffmpegdec->reordered_in) {
/* we reset the input reordering here because we want to recover from an
* occasionally wrong reordered input timestamp */
GST_DEBUG_OBJECT (ffmpegdec, "assuming DTS input timestamps");
ffmpegdec->reordered_in = FALSE;
} }
} }
@ -1752,20 +1768,18 @@ gst_ffmpegdec_video_frame (GstFFMpegDec * ffmpegdec,
* output timestamp based on the input timestamp. We do this by making the * output timestamp based on the input timestamp. We do this by making the
* ffmpeg timestamp and the interpollated next timestamp invalid. */ * ffmpeg timestamp and the interpollated next timestamp invalid. */
out_pts = -1; out_pts = -1;
ffmpegdec->next_ts = -1; ffmpegdec->next_out = -1;
} else } else
ffmpegdec->last_out = out_pts; ffmpegdec->last_out = out_pts;
if (ffmpegdec->ts_is_dts) { /* we assume DTS as input timestamps unless we see reordered input
/* we are dealing with DTS as the timestamps, only copy the DTS on the picture * timestamps */
* to the PTS of the output frame if we are dealing with a non-reference if (!ffmpegdec->reordered_in) {
* frame, else we leave the timestamp as -1, which will interpollate from the /* PTS and DTS are the same for keyframes */
* last outputted value. */ if (!iskeyframe && ffmpegdec->next_out != -1) {
if (ffmpegdec->context->has_b_frames && ffmpegdec->has_b_frames && /* interpolate all timestamps except for keyframes, FIXME, this is
ffmpegdec->picture->reference && ffmpegdec->next_ts != -1) { * wrong when QoS is active. */
/* we have b frames and this picture is a reference picture, don't use the GST_DEBUG_OBJECT (ffmpegdec, "interpolate timestamps");
* DTS as the PTS, same for offset */
GST_DEBUG_OBJECT (ffmpegdec, "DTS as timestamps, interpolate");
out_pts = -1; out_pts = -1;
out_offset = -1; out_offset = -1;
} }
@ -1800,8 +1814,8 @@ gst_ffmpegdec_video_frame (GstFFMpegDec * ffmpegdec,
GST_LOG_OBJECT (ffmpegdec, "using timestamp %" GST_TIME_FORMAT GST_LOG_OBJECT (ffmpegdec, "using timestamp %" GST_TIME_FORMAT
" returned by ffmpeg", GST_TIME_ARGS (out_timestamp)); " returned by ffmpeg", GST_TIME_ARGS (out_timestamp));
} }
if (!GST_CLOCK_TIME_IS_VALID (out_timestamp) && ffmpegdec->next_ts != -1) { if (!GST_CLOCK_TIME_IS_VALID (out_timestamp) && ffmpegdec->next_out != -1) {
out_timestamp = ffmpegdec->next_ts; out_timestamp = ffmpegdec->next_out;
GST_LOG_OBJECT (ffmpegdec, "using next timestamp %" GST_TIME_FORMAT, GST_LOG_OBJECT (ffmpegdec, "using next timestamp %" GST_TIME_FORMAT,
GST_TIME_ARGS (out_timestamp)); GST_TIME_ARGS (out_timestamp));
} }
@ -1854,7 +1868,7 @@ gst_ffmpegdec_video_frame (GstFFMpegDec * ffmpegdec,
*/ */
if (GST_CLOCK_TIME_IS_VALID (out_duration)) { if (GST_CLOCK_TIME_IS_VALID (out_duration)) {
/* We have a valid (reordered) duration */ /* We have a valid (reordered) duration */
GST_LOG_OBJECT (ffmpegdec, "We have a valid (reordered) duration"); GST_LOG_OBJECT (ffmpegdec, "Using duration returned by ffmpeg");
} else if (GST_CLOCK_TIME_IS_VALID (dec_info->duration)) { } else if (GST_CLOCK_TIME_IS_VALID (dec_info->duration)) {
GST_LOG_OBJECT (ffmpegdec, "using in_duration"); GST_LOG_OBJECT (ffmpegdec, "using in_duration");
out_duration = dec_info->duration; out_duration = dec_info->duration;
@ -1890,7 +1904,7 @@ gst_ffmpegdec_video_frame (GstFFMpegDec * ffmpegdec,
GST_BUFFER_DURATION (*outbuf) = out_duration; GST_BUFFER_DURATION (*outbuf) = out_duration;
if (out_timestamp != -1 && out_duration != -1) if (out_timestamp != -1 && out_duration != -1)
ffmpegdec->next_ts = out_timestamp + out_duration; ffmpegdec->next_out = out_timestamp + out_duration;
/* palette is not part of raw video frame in gst and the size /* palette is not part of raw video frame in gst and the size
* of the outgoing buffer needs to be adjusted accordingly */ * of the outgoing buffer needs to be adjusted accordingly */
@ -2018,9 +2032,9 @@ gst_ffmpegdec_audio_frame (GstFFMpegDec * ffmpegdec,
GST_DEBUG_OBJECT (ffmpegdec, GST_DEBUG_OBJECT (ffmpegdec,
"size:%d, offset:%" G_GINT64_FORMAT ", ts:%" GST_TIME_FORMAT ", dur:%" "size:%d, offset:%" G_GINT64_FORMAT ", ts:%" GST_TIME_FORMAT ", dur:%"
GST_TIME_FORMAT ", ffmpegdec->next_ts:%" GST_TIME_FORMAT, size, GST_TIME_FORMAT ", ffmpegdec->next_out:%" GST_TIME_FORMAT, size,
dec_info->offset, GST_TIME_ARGS (dec_info->timestamp), dec_info->offset, GST_TIME_ARGS (dec_info->timestamp),
GST_TIME_ARGS (dec_info->duration), GST_TIME_ARGS (ffmpegdec->next_ts)); GST_TIME_ARGS (dec_info->duration), GST_TIME_ARGS (ffmpegdec->next_out));
*outbuf = *outbuf =
new_aligned_buffer (AVCODEC_MAX_AUDIO_FRAME_SIZE, new_aligned_buffer (AVCODEC_MAX_AUDIO_FRAME_SIZE,
@ -2053,7 +2067,7 @@ gst_ffmpegdec_audio_frame (GstFFMpegDec * ffmpegdec,
if (GST_CLOCK_TIME_IS_VALID (dec_info->timestamp)) { if (GST_CLOCK_TIME_IS_VALID (dec_info->timestamp)) {
out_timestamp = dec_info->timestamp; out_timestamp = dec_info->timestamp;
} else { } else {
out_timestamp = ffmpegdec->next_ts; out_timestamp = ffmpegdec->next_out;
} }
/* /*
@ -2081,7 +2095,7 @@ gst_ffmpegdec_audio_frame (GstFFMpegDec * ffmpegdec,
GST_BUFFER_OFFSET (*outbuf) = out_offset; GST_BUFFER_OFFSET (*outbuf) = out_offset;
/* the next timestamp we'll use when interpolating */ /* the next timestamp we'll use when interpolating */
ffmpegdec->next_ts = out_timestamp + out_duration; ffmpegdec->next_out = out_timestamp + out_duration;
/* now see if we need to clip the buffer against the segment boundaries. */ /* now see if we need to clip the buffer against the segment boundaries. */
if (G_UNLIKELY (!clip_audio_buffer (ffmpegdec, *outbuf, out_timestamp, if (G_UNLIKELY (!clip_audio_buffer (ffmpegdec, *outbuf, out_timestamp,
@ -2302,8 +2316,7 @@ gst_ffmpegdec_sink_event (GstPad * pad, GstEvent * event)
if (ffmpegdec->opened) { if (ffmpegdec->opened) {
avcodec_flush_buffers (ffmpegdec->context); avcodec_flush_buffers (ffmpegdec->context);
} }
ffmpegdec->last_out = GST_CLOCK_TIME_NONE; gst_ffmpegdec_reset_ts (ffmpegdec);
ffmpegdec->next_ts = GST_CLOCK_TIME_NONE;
gst_ffmpegdec_reset_qos (ffmpegdec); gst_ffmpegdec_reset_qos (ffmpegdec);
gst_ffmpegdec_flush_pcache (ffmpegdec); gst_ffmpegdec_flush_pcache (ffmpegdec);
ffmpegdec->waiting_for_key = TRUE; ffmpegdec->waiting_for_key = TRUE;
@ -2436,8 +2449,7 @@ gst_ffmpegdec_chain (GstPad * pad, GstBuffer * inbuf)
gst_ffmpegdec_flush_pcache (ffmpegdec); gst_ffmpegdec_flush_pcache (ffmpegdec);
avcodec_flush_buffers (ffmpegdec->context); avcodec_flush_buffers (ffmpegdec->context);
ffmpegdec->discont = TRUE; ffmpegdec->discont = TRUE;
ffmpegdec->last_out = GST_CLOCK_TIME_NONE; gst_ffmpegdec_reset_ts (ffmpegdec);
ffmpegdec->next_ts = GST_CLOCK_TIME_NONE;
} }
/* by default we clear the input timestamp after decoding each frame so that /* by default we clear the input timestamp after decoding each frame so that
* interpollation can work. */ * interpollation can work. */
@ -2472,6 +2484,17 @@ gst_ffmpegdec_chain (GstPad * pad, GstBuffer * inbuf)
/* get handle to timestamp info, we can pass this around to ffmpeg */ /* get handle to timestamp info, we can pass this around to ffmpeg */
in_info = gst_ts_info_store (ffmpegdec, in_timestamp, in_duration, in_offset); in_info = gst_ts_info_store (ffmpegdec, in_timestamp, in_duration, in_offset);
if (in_timestamp != -1) {
/* check for increasing timestamps if they are jumping backwards, we
* probably are dealing with PTS as timestamps */
if (!ffmpegdec->reordered_in && ffmpegdec->last_in != -1
&& in_timestamp < ffmpegdec->last_in) {
GST_LOG_OBJECT (ffmpegdec, "detected reordered input timestamps");
ffmpegdec->reordered_in = TRUE;
}
ffmpegdec->last_in = in_timestamp;
}
GST_LOG_OBJECT (ffmpegdec, GST_LOG_OBJECT (ffmpegdec,
"Received new data of size %u, offset:%" G_GUINT64_FORMAT ", ts:%" "Received new data of size %u, offset:%" G_GUINT64_FORMAT ", ts:%"
GST_TIME_FORMAT ", dur:%" GST_TIME_FORMAT ", info %d", GST_TIME_FORMAT ", dur:%" GST_TIME_FORMAT ", info %d",