| // 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 "cobalt/media/filters/audio_timestamp_validator.h" |
| |
| #include <tuple> |
| |
| #include "base/time.h" |
| #include "cobalt/media/base/audio_decoder_config.h" |
| #include "cobalt/media/base/media_util.h" |
| #include "cobalt/media/base/mock_media_log.h" |
| #include "cobalt/media/base/test_helpers.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::testing::HasSubstr; |
| |
| namespace cobalt { |
| namespace media { |
| |
| // Constants to specify the type of audio data used. |
| static const AudioCodec kCodec = kCodecVorbis; |
| static const SampleFormat kSampleFormat = kSampleFormatPlanarF32; |
| static const base::TimeDelta kSeekPreroll; |
| static const int kSamplesPerSecond = 10000; |
| static const base::TimeDelta kBufferDuration = |
| base::TimeDelta::FromMilliseconds(20); |
| static const ChannelLayout kChannelLayout = CHANNEL_LAYOUT_STEREO; |
| static const int kChannelCount = 2; |
| static const int kChannels = ChannelLayoutToChannelCount(kChannelLayout); |
| static const int kFramesPerBuffer = kBufferDuration.InMicroseconds() * |
| kSamplesPerSecond / |
| base::Time::kMicrosecondsPerSecond; |
| |
| // Params are: |
| // 1. Output delay: number of encoded buffers before first decoded output |
| // 2. Codec delay: number of frames of codec delay in decoder config |
| // 3. Front discard: front discard for the first buffer |
| using ValidatorTestParams = testing::tuple<int, int, base::TimeDelta>; |
| |
| class AudioTimestampValidatorTest |
| : public testing::Test, |
| public ::testing::WithParamInterface<ValidatorTestParams> { |
| public: |
| AudioTimestampValidatorTest() |
| : media_log_(new testing::StrictMock<MockMediaLog>()) {} |
| |
| protected: |
| void SetUp() override { |
| output_delay_ = testing::get<0>(GetParam()); |
| codec_delay_ = testing::get<1>(GetParam()); |
| front_discard_ = testing::get<2>(GetParam()); |
| } |
| |
| int output_delay_; |
| |
| int codec_delay_; |
| |
| base::TimeDelta front_discard_; |
| |
| scoped_refptr<testing::StrictMock<MockMediaLog>> media_log_; |
| }; |
| |
| TEST_P(AudioTimestampValidatorTest, WarnForEraticTimes) { |
| AudioDecoderConfig decoder_config; |
| decoder_config.Initialize(kCodec, kSampleFormat, kChannelLayout, |
| kSamplesPerSecond, EmptyExtraData(), Unencrypted(), |
| kSeekPreroll, codec_delay_); |
| |
| // Validator should fail to stabilize pattern for timestamp expectations. |
| EXPECT_MEDIA_LOG( |
| HasSubstr("Failed to reconcile encoded audio times " |
| "with decoded output.")); |
| |
| // No gap warnings should be emitted because the timestamps expectations never |
| // stabilized. |
| EXPECT_MEDIA_LOG(HasSubstr("timestamp gap detected")).Times(0); |
| |
| AudioTimestampValidator validator(decoder_config, media_log_); |
| |
| const base::TimeDelta kRandomOffsets[] = { |
| base::TimeDelta::FromMilliseconds(100), |
| base::TimeDelta::FromMilliseconds(350)}; |
| |
| for (int i = 0; i < 100; ++i) { |
| // Each buffer's timestamp is kBufferDuration from the previous buffer. |
| scoped_refptr<DecoderBuffer> encoded_buffer = new DecoderBuffer(0); |
| |
| // Ping-pong between two random offsets to prevent validator from |
| // stabilizing timestamp pattern. |
| base::TimeDelta randomOffset = |
| kRandomOffsets[i % arraysize(kRandomOffsets)]; |
| encoded_buffer->set_timestamp(i * kBufferDuration + randomOffset); |
| |
| if (i == 0) { |
| encoded_buffer->set_discard_padding( |
| std::make_pair(front_discard_, base::TimeDelta())); |
| } |
| |
| validator.CheckForTimestampGap(encoded_buffer); |
| |
| if (i >= output_delay_) { |
| // kFramesPerBuffer is derived to perfectly match kBufferDuration, so |
| // no gaps exists as long as timestamps are exactly kBufferDuration apart. |
| scoped_refptr<AudioBuffer> decoded_buffer = MakeAudioBuffer<float>( |
| kSampleFormat, kChannelLayout, kChannelCount, kSamplesPerSecond, 1.0f, |
| 0.0f, kFramesPerBuffer, i * kBufferDuration); |
| validator.RecordOutputDuration(decoded_buffer.get()); |
| } |
| } |
| } |
| |
| TEST_P(AudioTimestampValidatorTest, NoWarningForValidTimes) { |
| AudioDecoderConfig decoder_config; |
| decoder_config.Initialize(kCodec, kSampleFormat, kChannelLayout, |
| kSamplesPerSecond, EmptyExtraData(), Unencrypted(), |
| kSeekPreroll, codec_delay_); |
| |
| // Validator should quickly stabilize pattern for timestamp expectations. |
| EXPECT_MEDIA_LOG(HasSubstr("Failed to reconcile encoded audio times " |
| "with decoded output.")) |
| .Times(0); |
| |
| // Expect no gap warnings for series of buffers with valid timestamps. |
| EXPECT_MEDIA_LOG(HasSubstr("timestamp gap detected")).Times(0); |
| |
| AudioTimestampValidator validator(decoder_config, media_log_); |
| |
| for (int i = 0; i < 100; ++i) { |
| // Each buffer's timestamp is kBufferDuration from the previous buffer. |
| scoped_refptr<DecoderBuffer> encoded_buffer = new DecoderBuffer(0); |
| encoded_buffer->set_timestamp(i * kBufferDuration); |
| |
| if (i == 0) { |
| encoded_buffer->set_discard_padding( |
| std::make_pair(front_discard_, base::TimeDelta())); |
| } |
| |
| validator.CheckForTimestampGap(encoded_buffer); |
| |
| if (i >= output_delay_) { |
| // kFramesPerBuffer is derived to perfectly match kBufferDuration, so |
| // no gaps exists as long as timestamps are exactly kBufferDuration apart. |
| scoped_refptr<AudioBuffer> decoded_buffer = MakeAudioBuffer<float>( |
| kSampleFormat, kChannelLayout, kChannelCount, kSamplesPerSecond, 1.0f, |
| 0.0f, kFramesPerBuffer, i * kBufferDuration); |
| validator.RecordOutputDuration(decoded_buffer.get()); |
| } |
| } |
| } |
| |
| TEST_P(AudioTimestampValidatorTest, SingleWarnForSingleLargeGap) { |
| AudioDecoderConfig decoder_config; |
| decoder_config.Initialize(kCodec, kSampleFormat, kChannelLayout, |
| kSamplesPerSecond, EmptyExtraData(), Unencrypted(), |
| kSeekPreroll, codec_delay_); |
| |
| AudioTimestampValidator validator(decoder_config, media_log_); |
| |
| // Validator should quickly stabilize pattern for timestamp expectations. |
| EXPECT_MEDIA_LOG(HasSubstr("Failed to reconcile encoded audio times " |
| "with decoded output.")) |
| .Times(0); |
| |
| for (int i = 0; i < 100; ++i) { |
| // Halfway through the stream, introduce sudden gap of 50 milliseconds. |
| base::TimeDelta offset; |
| if (i >= 50) offset = base::TimeDelta::FromMilliseconds(100); |
| |
| // This gap never widens, so expect only a single warning when its first |
| // introduced. |
| if (i == 50) EXPECT_MEDIA_LOG(HasSubstr("timestamp gap detected")); |
| |
| scoped_refptr<DecoderBuffer> encoded_buffer = new DecoderBuffer(0); |
| encoded_buffer->set_timestamp(i * kBufferDuration + offset); |
| |
| if (i == 0) { |
| encoded_buffer->set_discard_padding( |
| std::make_pair(front_discard_, base::TimeDelta())); |
| } |
| |
| validator.CheckForTimestampGap(encoded_buffer); |
| |
| if (i >= output_delay_) { |
| // kFramesPerBuffer is derived to perfectly match kBufferDuration, so |
| // no gaps exists as long as timestamps are exactly kBufferDuration apart. |
| scoped_refptr<AudioBuffer> decoded_buffer = MakeAudioBuffer<float>( |
| kSampleFormat, kChannelLayout, kChannelCount, kSamplesPerSecond, 1.0f, |
| 0.0f, kFramesPerBuffer, i * kBufferDuration); |
| validator.RecordOutputDuration(decoded_buffer.get()); |
| } |
| } |
| } |
| |
| TEST_P(AudioTimestampValidatorTest, RepeatedWarnForSlowAccumulatingDrift) { |
| AudioDecoderConfig decoder_config; |
| decoder_config.Initialize(kCodec, kSampleFormat, kChannelLayout, |
| kSamplesPerSecond, EmptyExtraData(), Unencrypted(), |
| kSeekPreroll, codec_delay_); |
| |
| AudioTimestampValidator validator(decoder_config, media_log_); |
| |
| EXPECT_MEDIA_LOG(HasSubstr("Failed to reconcile encoded audio times " |
| "with decoded output.")) |
| .Times(0); |
| |
| for (int i = 0; i < 100; ++i) { |
| // Wait for delayed output to begin plus an additional two iterations to |
| // start using drift offset. The the two iterations without offset will |
| // allow the validator to stabilize the pattern of timestamps and begin |
| // checking for gaps. Once stable, increase offset by 1 millisecond for each |
| // iteration. |
| base::TimeDelta offset; |
| if (i >= output_delay_ + 2) |
| offset = i * base::TimeDelta::FromMilliseconds(1); |
| |
| scoped_refptr<DecoderBuffer> encoded_buffer = new DecoderBuffer(0); |
| encoded_buffer->set_timestamp((i * kBufferDuration) + offset); |
| |
| // Expect gap warnings to start when drift hits 50 milliseconds. Warnings |
| // should continue as the gap widens. |
| if (offset > base::TimeDelta::FromMilliseconds(50)) { |
| EXPECT_MEDIA_LOG(HasSubstr("timestamp gap detected")); |
| } |
| |
| validator.CheckForTimestampGap(encoded_buffer); |
| |
| if (i >= output_delay_) { |
| // kFramesPerBuffer is derived to perfectly match kBufferDuration, so |
| // no gaps exists as long as timestamps are exactly kBufferDuration apart. |
| scoped_refptr<AudioBuffer> decoded_buffer = MakeAudioBuffer<float>( |
| kSampleFormat, kChannelLayout, kChannelCount, kSamplesPerSecond, 1.0f, |
| 0.0f, kFramesPerBuffer, i * kBufferDuration); |
| validator.RecordOutputDuration(decoded_buffer.get()); |
| } |
| } |
| } |
| |
| // Test with cartesian product of various output delay, codec delay, and front |
| // discard values. These simulate configurations for different containers/codecs |
| // which present different challenges when building timestamp expectations. |
| INSTANTIATE_TEST_CASE_P( |
| , AudioTimestampValidatorTest, |
| ::testing::Combine( |
| ::testing::Values(0, 10), // output delay |
| ::testing::Values(0, 512), // codec delay |
| ::testing::Values(base::TimeDelta(), // front discard |
| base::TimeDelta::FromMilliseconds(65)))); |
| |
| } // namespace media |
| } // namespace cobalt |