| Index: media/filters/audio_timestamp_validator_unittest.cc
|
| diff --git a/media/filters/audio_timestamp_validator_unittest.cc b/media/filters/audio_timestamp_validator_unittest.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..de2f47449f88e64900b4f6fc3062afb88e97139a
|
| --- /dev/null
|
| +++ b/media/filters/audio_timestamp_validator_unittest.cc
|
| @@ -0,0 +1,252 @@
|
| +// 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"
|
| +
|
| +#include <tuple>
|
| +
|
| +#include "base/time/time.h"
|
| +#include "media/base/audio_decoder_config.h"
|
| +#include "media/base/media_util.h"
|
| +#include "media/base/mock_media_log.h"
|
| +#include "media/base/test_helpers.h"
|
| +#include "testing/gtest/include/gtest/gtest.h"
|
| +
|
| +using ::testing::HasSubstr;
|
| +
|
| +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
|
|
|