// Copyright (c) 2014 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 #include #include "media/base/audio_bus.h" #include "media/base/audio_shifter.h" #include "testing/gtest/include/gtest/gtest.h" namespace media { const int kSampleRate = 48000; const int kInputPacketSize = 48; const int kOutputPacketSize = 24; #if GTEST_HAS_COMBINE class AudioShifterTest : public ::testing::TestWithParam<::testing::tuple > { public: AudioShifterTest() : shifter_(base::TimeDelta::FromMilliseconds(2000), base::TimeDelta::FromMilliseconds(3), base::TimeDelta::FromMilliseconds(100), kSampleRate, 2), end2end_latency_(base::TimeDelta::FromMilliseconds(30)), playback_latency_(base::TimeDelta::FromMilliseconds(10)), tag_input_(false), expect_smooth_output_(true), input_sample_n_(0), output_sample_(0) { } void SetupInput(int size, base::TimeDelta rate) { input_size_ = size; input_rate_ = rate; } scoped_ptr CreateTestInput() { scoped_ptr input(AudioBus::Create(2, input_size_)); for (size_t i = 0; i < input_size_; i++) { input->channel(0)[i] = input->channel(1)[i] = input_sample_n_; input_sample_n_++; } if (tag_input_) { input->channel(0)[0] = 10000000.0; tag_input_ = false; expect_smooth_output_ = false; } return input.Pass(); } void SetupOutput(int size, base::TimeDelta rate) { test_output_ = AudioBus::Create(2, size); output_rate_ = rate; } void SetUp() override { SetupInput( kInputPacketSize + ::testing::get<0>(GetParam()) - 1, base::TimeDelta::FromMicroseconds( 1000 + ::testing::get<1>(GetParam()) * 5 - 5)); SetupOutput( kOutputPacketSize, base::TimeDelta::FromMicroseconds( 500 + ::testing::get<2>(GetParam()) * 3 - 3)); if (::testing::get<3>(GetParam())) { end2end_latency_ = -end2end_latency_; } } void Run(size_t loops) { for (size_t i = 0; i < loops;) { if (now_ >= time_to_push_) { shifter_.Push(CreateTestInput(), now_ + end2end_latency_); time_to_push_ += input_rate_; i++; } if (now_ >= time_to_pull_) { shifter_.Pull(test_output_.get(), now_ + playback_latency_); bool silence = true; for (size_t j = 0; j < static_cast(test_output_->frames()); j++) { if (test_output_->channel(0)[j] != 0.0) { silence = false; if (test_output_->channel(0)[j] > 3000000.0) { marker_outputs_.push_back( now_ + playback_latency_ + base::TimeDelta::FromSeconds(j) / kSampleRate); } else { // We don't expect smooth output once we insert a tag, // or in the very beginning. if (expect_smooth_output_ && output_sample_ > 500.0) { EXPECT_GT(test_output_->channel(0)[j], output_sample_ - 3) << "j = " << j; if (test_output_->channel(0)[j] > output_sample_ + kOutputPacketSize / 2) { skip_outputs_.push_back(now_ + playback_latency_); } } output_sample_ = test_output_->channel(0)[j]; } } } if (silence) { silent_outputs_.push_back(now_); } time_to_pull_ += output_rate_; } now_ += std::min(time_to_push_ - now_, time_to_pull_ - now_); } } void RunAndCheckSync(size_t loops) { Run(100); size_t expected_silent_outputs = silent_outputs_.size(); Run(loops); tag_input_ = true; CHECK(marker_outputs_.empty()); base::TimeTicks expected_mark_time = time_to_push_ + end2end_latency_; Run(100); if (end2end_latency_ > base::TimeDelta()) { CHECK(!marker_outputs_.empty()); base::TimeDelta actual_offset = marker_outputs_[0] - expected_mark_time; EXPECT_LT(actual_offset, base::TimeDelta::FromMicroseconds(100)); EXPECT_GT(actual_offset, base::TimeDelta::FromMicroseconds(-100)); } else { EXPECT_GT(marker_outputs_.size(), 0UL); } EXPECT_EQ(expected_silent_outputs, silent_outputs_.size()); } protected: AudioShifter shifter_; base::TimeDelta input_rate_; base::TimeDelta output_rate_; base::TimeDelta end2end_latency_; base::TimeDelta playback_latency_; base::TimeTicks time_to_push_; base::TimeTicks time_to_pull_; base::TimeTicks now_; scoped_ptr test_input_; scoped_ptr test_output_; std::vector silent_outputs_; std::vector skip_outputs_; std::vector marker_outputs_; size_t input_size_; bool tag_input_; bool expect_smooth_output_; size_t input_sample_n_; double output_sample_; }; TEST_P(AudioShifterTest, TestSync) { RunAndCheckSync(1000); EXPECT_EQ(0UL, skip_outputs_.size()); } TEST_P(AudioShifterTest, TestSyncWithPush) { // Push some extra audio. shifter_.Push(CreateTestInput().Pass(), now_ - base::TimeDelta(input_rate_)); RunAndCheckSync(1000); EXPECT_LE(skip_outputs_.size(), 2UL); } TEST_P(AudioShifterTest, TestSyncWithPull) { // Output should smooth out eventually, but that is not tested yet. expect_smooth_output_ = false; Run(100); for (int i = 0; i < 100; i++) { shifter_.Pull(test_output_.get(), now_ + base::TimeDelta::FromMilliseconds(i)); } RunAndCheckSync(1000); EXPECT_LE(skip_outputs_.size(), 1UL); } TEST_P(AudioShifterTest, UnderOverFlow) { expect_smooth_output_ = false; SetupInput( kInputPacketSize + ::testing::get<0>(GetParam()) * 10 - 10, base::TimeDelta::FromMicroseconds( 1000 + ::testing::get<1>(GetParam()) * 100 - 100)); SetupOutput( kOutputPacketSize, base::TimeDelta::FromMicroseconds( 500 + ::testing::get<2>(GetParam()) * 50 - 50)); // Sane output is not expected, but let's make sure we don't crash. Run(1000); } // Note: First argument is optional and intentionally left blank. // (it's a prefix for the generated test cases) INSTANTIATE_TEST_CASE_P( , AudioShifterTest, ::testing::Combine(::testing::Range(0, 3), ::testing::Range(0, 3), ::testing::Range(0, 3), ::testing::Bool())); #endif } // namespace media