Chromium Code Reviews| Index: media/filters/decoder_stream.cc |
| diff --git a/media/filters/decoder_stream.cc b/media/filters/decoder_stream.cc |
| index 039b2b95f2276517e89902fc4815227d040b6cd4..757692bebb18bec12b951ae347c73d45d59393c5 100644 |
| --- a/media/filters/decoder_stream.cc |
| +++ b/media/filters/decoder_stream.cc |
| @@ -24,6 +24,23 @@ |
| namespace media { |
| +// Defines how many milliseconds of DecoderBuffer timestamp gap will be allowed |
| +// before warning the user. See CheckForTimestampGap(). Value of 50 chosen, as |
| +// this is low enough to catch issues early, but high enough to avoid noise for |
| +// containers like WebM that default to low granularity timestamp precision. |
| +const int kGapWarningThresholdMsec = 50; |
| + |
| +// Limits the number of adjustments to |audio_ts_offset_| in order to reach a |
| +// stable state where gaps between encoded timestamps match decoded output |
| +// intervals. See CheckForTimestampGap(). |
| +const int kLimitTriesForStableTiming = 5; |
| + |
| +// Limits the milliseconds of difference between expected and actual timestamps |
| +// gaps to consider timestamp expectations "stable". 1 chosen because some |
| +// containers (WebM) default to millisecond timestamp precision. See |
| +// CheckForTimestampGap(). |
| +const int kStableTimeGapThrsholdMsec = 1; |
| + |
| // TODO(rileya): Devise a better way of specifying trace/UMA/etc strings for |
| // templated classes such as this. |
| template <DemuxerStream::Type StreamType> |
| @@ -51,6 +68,10 @@ DecoderStream<StreamType>::DecoderStream( |
| media_log_(media_log), |
| state_(STATE_UNINITIALIZED), |
| stream_(NULL), |
| + audio_base_ts_(kNoTimestamp()), |
| + stable_audio_times_(false), |
| + num_unstable_audio_tries_(0), |
| + drift_warning_threshold_msec_(kGapWarningThresholdMsec), |
| decoder_selector_(new DecoderSelector<StreamType>(task_runner, |
| std::move(decoders), |
| media_log)), |
| @@ -165,6 +186,12 @@ void DecoderStream<StreamType>::Reset(const base::Closure& closure) { |
| } |
| ready_outputs_.clear(); |
| + audio_base_ts_ = kNoTimestamp(); |
| + audio_ts_offset_ = base::TimeDelta(); |
| + audio_output_ts_helper_.reset(); |
| + num_unstable_audio_tries_ = 0; |
| + stable_audio_times_ = false; |
| + drift_warning_threshold_msec_ = kGapWarningThresholdMsec; |
| // It's possible to have received a DECODE_ERROR and entered STATE_ERROR right |
| // before a Reset() is executed. If we are still waiting for a demuxer read, |
| @@ -348,6 +375,8 @@ void DecoderStream<StreamType>::DecodeInternal( |
| DCHECK(reset_cb_.is_null()); |
| DCHECK(buffer.get()); |
| + CheckForTimestampGap(buffer); |
| + |
| int buffer_size = buffer->end_of_stream() ? 0 : buffer->data_size(); |
| TRACE_EVENT_ASYNC_BEGIN2( |
| @@ -476,6 +505,8 @@ void DecoderStream<StreamType>::OnDecodeOutputReady( |
| if (!reset_cb_.is_null()) |
| return; |
| + RecordOutputDuration(output); |
| + |
| ++decoded_frames_since_fallback_; |
| // |decoder_| sucessfully decoded a frame. No need to keep buffers for a |
| @@ -772,6 +803,106 @@ void DecoderStream<StreamType>::OnDecoderReset() { |
| ReinitializeDecoder(); |
| } |
| +template <> |
| +void DecoderStream<DemuxerStream::VIDEO>::CheckForTimestampGap( |
| + const scoped_refptr<DecoderBuffer>& buffer) {} |
| + |
| +template <> |
| +void DecoderStream<DemuxerStream::VIDEO>::RecordOutputDuration( |
| + const scoped_refptr<Output>& output) {} |
| + |
| +template <> |
| +void DecoderStream<DemuxerStream::AUDIO>::CheckForTimestampGap( |
| + const scoped_refptr<DecoderBuffer>& buffer) { |
| + if (buffer->end_of_stream()) |
| + return; |
| + DCHECK_NE(kNoTimestamp(), buffer->timestamp()); |
| + |
| + // Don't continue checking timestamps if we've exhausted tries to reach stable |
| + // state. This suggests the media's encoded timestamps are way off. |
| + if (num_unstable_audio_tries_ > kLimitTriesForStableTiming) |
| + return; |
| + |
| + // Keep resetting encode base ts until we start getting decode output. Some |
| + // codecs/containers (e.g. chained Ogg) will take several encoded buffers |
| + // before producing the first decoded output. |
| + if (!audio_output_ts_helper_) { |
| + audio_base_ts_ = buffer->timestamp(); |
| + DVLOG(3) << __FUNCTION__ |
| + << " setting audio_base:" << audio_base_ts_.InMicroseconds(); |
| + // Wait for decoded output to form expectations to verify timestamps. |
| + return; |
| + } |
| + |
| + base::TimeDelta expected_ts = |
| + audio_output_ts_helper_->GetTimestamp() + audio_ts_offset_; |
| + base::TimeDelta ts_delta = buffer->timestamp() - expected_ts; |
| + |
| + // Reconciling encoded buffer timestamps with decoded output often requires |
|
DaleCurtis
2016/05/17 21:32:54
Notably this also hides errors at the beginning of
chcunningham
2016/05/18 00:18:00
Will try to do what we discussed, where we expect
|
| + // adjusting expectations by some offset. This accounts for varied (and at |
| + // this point unknown) handling of front trimming and codec delay. Codec delay |
| + // and skip trimming may or may not be accounted for in the encoded timestamps |
| + // depending on the codec (e.g. MP3 vs Opus) and demuxers used (e.g. FFmpeg |
| + // vs MSE stream parsers). |
| + if (!stable_audio_times_) { |
| + if (std::abs(ts_delta.InMilliseconds()) < kStableTimeGapThrsholdMsec) { |
| + stable_audio_times_ = true; |
| + DVLOG(3) << __FUNCTION__ |
| + << " stabilized! tries:" << num_unstable_audio_tries_ |
| + << " offset:" << audio_ts_offset_.InMicroseconds(); |
| + } else { |
| + base::TimeDelta orig_offset = audio_ts_offset_; |
| + audio_ts_offset_ += ts_delta; |
|
DaleCurtis
2016/05/17 21:32:54
Seems like you should just set the base timestamp
chcunningham
2016/05/18 00:18:00
Done.
|
| + DVLOG(3) << __FUNCTION__ |
| + << " NOT stabilized. tries:" << num_unstable_audio_tries_ |
| + << " offset was:" << orig_offset.InMicroseconds() |
| + << " now:" << audio_ts_offset_.InMicroseconds(); |
| + num_unstable_audio_tries_++; |
| + |
| + // Let developers know if their files timestamps are way off from |
| + if (num_unstable_audio_tries_ > kLimitTriesForStableTiming) |
|
DaleCurtis
2016/05/17 21:32:54
Needs {}
chcunningham
2016/05/18 00:18:00
Done.
|
| + MEDIA_LOG(ERROR, media_log_) |
| + << "Failed to reconcile encoded audio times with decoded output."; |
| + } |
| + |
| + // Don't bother with further checking until we reach stable state. |
| + return; |
| + } |
| + |
| + if (std::abs(ts_delta.InMilliseconds()) > drift_warning_threshold_msec_) { |
| + MEDIA_LOG(ERROR, media_log_) |
| + << " Large timestamp gap detected; may cause AV sync to drift." |
| + << " time:" << buffer->timestamp() |
| + << " expected:" << expected_ts.InMicroseconds() |
| + << " delta:" << ts_delta.InMicroseconds(); |
| + // Increase threshold to avoid log spam but, let us know if gap widens. |
| + drift_warning_threshold_msec_ = std::abs(ts_delta.InMilliseconds()); |
| + } |
| + DVLOG(3) << __FUNCTION__ << " delta:" << ts_delta.InMicroseconds() |
| + << " expected_ts:" << expected_ts.InMicroseconds() |
| + << " actual_ts:" << buffer->timestamp().InMicroseconds() |
| + << " audio_ts_offset:" << audio_ts_offset_.InMicroseconds() |
| + << " base_ts:" << audio_base_ts_.InMicroseconds(); |
| +} |
| + |
| +template <> |
| +void DecoderStream<DemuxerStream::AUDIO>::RecordOutputDuration( |
| + const scoped_refptr<Output>& output) { |
| + if (output.get()) { |
| + AudioBuffer* audio_buffer = static_cast<AudioBuffer*>(output.get()); |
| + |
| + if (!audio_output_ts_helper_) { |
| + DCHECK_NE(audio_base_ts_, kNoTimestamp()); |
| + audio_output_ts_helper_.reset( |
| + new AudioTimestampHelper(audio_buffer->sample_rate())); |
| + audio_output_ts_helper_->SetBaseTimestamp(audio_base_ts_); |
| + } |
| + |
| + DVLOG(3) << __FUNCTION__ << " " << audio_buffer->frame_count() << " frames"; |
| + audio_output_ts_helper_->AddFrames(audio_buffer->frame_count()); |
| + } |
| +} |
| + |
| template class DecoderStream<DemuxerStream::VIDEO>; |
| template class DecoderStream<DemuxerStream::AUDIO>; |