| // 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 <stddef.h> |
| |
| #include <cmath> |
| #include <memory> |
| #include <vector> |
| |
| #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; |
| |
| class AudioShifterTest : |
| public ::testing::TestWithParam<::testing::tuple<int, int, int, bool> > { |
| 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; |
| } |
| |
| std::unique_ptr<AudioBus> CreateTestInput() { |
| std::unique_ptr<AudioBus> 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; |
| } |
| |
| 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<size_t>(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_; |
| std::unique_ptr<AudioBus> test_input_; |
| std::unique_ptr<AudioBus> test_output_; |
| std::vector<base::TimeTicks> silent_outputs_; |
| std::vector<base::TimeTicks> skip_outputs_; |
| std::vector<base::TimeTicks> 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(), 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())); |
| |
| } // namespace media |