Index: media/filters/audio_timestamp_validator.cc |
diff --git a/media/filters/audio_timestamp_validator.cc b/media/filters/audio_timestamp_validator.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..23306237f8c216c6f2a446bd774440444749a703 |
--- /dev/null |
+++ b/media/filters/audio_timestamp_validator.cc |
@@ -0,0 +1,146 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "media/filters/audio_timestamp_validator.h" |
+ |
+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; |
+ |
+AudioTimestampValidator::AudioTimestampValidator( |
+ const AudioDecoderConfig& decoder_config, |
+ const scoped_refptr<MediaLog>& media_log) |
+ : has_codec_delay_(decoder_config.codec_delay() > 0), |
+ media_log_(media_log), |
+ audio_base_ts_(kNoTimestamp()), |
+ reached_stable_state_(false), |
+ num_unstable_audio_tries_(0), |
+ limit_unstable_audio_tries_(kLimitTriesForStableTiming), |
+ drift_warning_threshold_msec_(kGapWarningThresholdMsec) { |
+ DCHECK(decoder_config.IsValidConfig()); |
+} |
+ |
+AudioTimestampValidator::~AudioTimestampValidator() {} |
+ |
+void AudioTimestampValidator::CheckForTimestampGap( |
+ const scoped_refptr<DecoderBuffer>& buffer) { |
+ if (buffer->end_of_stream()) |
+ return; |
+ DCHECK_NE(kNoTimestamp(), buffer->timestamp()); |
+ |
+ // If audio_base_ts_ == kNoTimestamp(), we are processing our first buffer. |
+ // If stream has neither codec delay nor discard padding, we should expect |
+ // timestamps and output durations to line up from the start (i.e. be stable). |
+ if (audio_base_ts_ == kNoTimestamp() && !has_codec_delay_ && |
+ buffer->discard_padding().first == base::TimeDelta() && |
+ buffer->discard_padding().second == base::TimeDelta()) { |
+ DVLOG(3) << __FUNCTION__ |
+ << " Expecting stable timestamps - stream has neither codec delay" |
+ << " nor discard padding."; |
+ limit_unstable_audio_tries_ = 0; |
+ } |
+ |
+ // 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_ > limit_unstable_audio_tries_) |
+ 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(); |
+ return; |
+ } |
+ |
+ base::TimeDelta expected_ts = audio_output_ts_helper_->GetTimestamp(); |
+ base::TimeDelta ts_delta = buffer->timestamp() - expected_ts; |
+ |
+ // Reconciling encoded buffer timestamps with decoded output often requires |
+ // 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 (!reached_stable_state_) { |
+ if (std::abs(ts_delta.InMilliseconds()) < kStableTimeGapThrsholdMsec) { |
+ reached_stable_state_ = true; |
+ DVLOG(3) << __FUNCTION__ |
+ << " stabilized! tries:" << num_unstable_audio_tries_ |
+ << " offset:" |
+ << audio_output_ts_helper_->base_timestamp().InMicroseconds(); |
+ } else { |
+ base::TimeDelta orig_offset = audio_output_ts_helper_->base_timestamp(); |
+ |
+ // Save since this gets reset when we set new base time. |
+ int64_t decoded_frame_count = audio_output_ts_helper_->frame_count(); |
+ audio_output_ts_helper_->SetBaseTimestamp(orig_offset + ts_delta); |
+ audio_output_ts_helper_->AddFrames(decoded_frame_count); |
+ |
+ DVLOG(3) << __FUNCTION__ |
+ << " NOT stabilized. tries:" << num_unstable_audio_tries_ |
+ << " offset was:" << orig_offset.InMicroseconds() << " now:" |
+ << audio_output_ts_helper_->base_timestamp().InMicroseconds(); |
+ num_unstable_audio_tries_++; |
+ |
+ // Let developers know if their files timestamps are way off from |
+ if (num_unstable_audio_tries_ > limit_unstable_audio_tries_) { |
+ 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().InMilliseconds() |
+ << " 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_output_ts_helper_->base_timestamp().InMicroseconds(); |
+} |
+ |
+void AudioTimestampValidator::RecordOutputDuration( |
+ const scoped_refptr<AudioBuffer>& audio_buffer) { |
+ if (!audio_output_ts_helper_) { |
+ DCHECK_NE(audio_base_ts_, kNoTimestamp()); |
+ // SUBTLE: deliberately creating this with output buffer sample rate because |
+ // demuxer stream config is potentially stale for implicit AAC. |
+ 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()); |
+} |
+ |
+} // namespace media |