| // Copyright (c) 2012 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/base/audio_renderer_mixer.h" |
| |
| #include <stddef.h> |
| |
| #include <algorithm> |
| #include <limits> |
| #include <memory> |
| #include <tuple> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/stl_util.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "base/threading/platform_thread.h" |
| #include "media/base/audio_renderer_mixer_input.h" |
| #include "media/base/audio_renderer_mixer_pool.h" |
| #include "media/base/fake_audio_render_callback.h" |
| #include "media/base/mock_audio_renderer_sink.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace { |
| void LogUma(int value) {} |
| } |
| |
| namespace media { |
| |
| // Parameters which control the many input case tests. |
| const int kMixerInputs = 8; |
| const int kOddMixerInputs = 7; |
| const int kMixerCycles = 3; |
| |
| // Parameters used for testing. |
| const ChannelLayout kChannelLayout = CHANNEL_LAYOUT_STEREO; |
| const int kHighLatencyBufferSize = 8192; |
| const int kLowLatencyBufferSize = 256; |
| |
| // Number of full sine wave cycles for each Render() call. |
| const int kSineCycles = 4; |
| |
| // Input sample frequencies for testing. |
| const int kTestInputLower = 44100; |
| const int kTestInputHigher = 48000; |
| const int kTestInput3Rates[] = {22050, 44100, 48000}; |
| |
| // Tuple of <input sampling rates, number of input sample rates, |
| // output sampling rate, epsilon>. |
| using AudioRendererMixerTestData = |
| std::tuple<const int* const, size_t, int, double>; |
| |
| class AudioRendererMixerTest |
| : public testing::TestWithParam<AudioRendererMixerTestData>, |
| public AudioRendererMixerPool { |
| public: |
| AudioRendererMixerTest() |
| : epsilon_(std::get<3>(GetParam())), half_fill_(false) { |
| // Create input parameters based on test parameters. |
| const int* const sample_rates = std::get<0>(GetParam()); |
| size_t sample_rates_count = std::get<1>(GetParam()); |
| for (size_t i = 0; i < sample_rates_count; ++i) |
| input_parameters_.push_back( |
| AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout, |
| sample_rates[i], kHighLatencyBufferSize)); |
| |
| // Create output parameters based on test parameters. |
| output_parameters_ = |
| AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, kChannelLayout, |
| std::get<2>(GetParam()), kLowLatencyBufferSize); |
| |
| sink_ = new MockAudioRendererSink(); |
| EXPECT_CALL(*sink_.get(), Start()); |
| EXPECT_CALL(*sink_.get(), Stop()); |
| |
| mixer_.reset( |
| new AudioRendererMixer(output_parameters_, sink_, base::Bind(&LogUma))); |
| mixer_callback_ = sink_->callback(); |
| |
| audio_bus_ = AudioBus::Create(output_parameters_); |
| expected_audio_bus_ = AudioBus::Create(output_parameters_); |
| |
| // Allocate one callback for generating expected results. |
| double step = kSineCycles / static_cast<double>( |
| output_parameters_.frames_per_buffer()); |
| expected_callback_.reset( |
| new FakeAudioRenderCallback(step, output_parameters_.sample_rate())); |
| } |
| |
| AudioRendererMixer* GetMixer(int owner_id, |
| const AudioParameters& params, |
| AudioLatency::LatencyType latency, |
| const OutputDeviceInfo& sink_info, |
| scoped_refptr<AudioRendererSink> sink) final { |
| return mixer_.get(); |
| }; |
| |
| void ReturnMixer(AudioRendererMixer* mixer) override { |
| EXPECT_EQ(mixer_.get(), mixer); |
| } |
| |
| scoped_refptr<AudioRendererSink> GetSink( |
| int owner_id, |
| const std::string& device_id) override { |
| return sink_; |
| } |
| |
| void InitializeInputs(int inputs_per_sample_rate) { |
| mixer_inputs_.reserve(inputs_per_sample_rate * input_parameters_.size()); |
| fake_callbacks_.reserve(inputs_per_sample_rate * input_parameters_.size()); |
| |
| for (size_t i = 0, input = 0; i < input_parameters_.size(); ++i) { |
| // Setup FakeAudioRenderCallback step to compensate for resampling. |
| double scale_factor = |
| input_parameters_[i].sample_rate() / |
| static_cast<double>(output_parameters_.sample_rate()); |
| double step = |
| kSineCycles / |
| (scale_factor * |
| static_cast<double>(output_parameters_.frames_per_buffer())); |
| |
| for (int j = 0; j < inputs_per_sample_rate; ++j, ++input) { |
| fake_callbacks_.push_back(std::make_unique<FakeAudioRenderCallback>( |
| step, output_parameters_.sample_rate())); |
| mixer_inputs_.push_back(CreateMixerInput()); |
| mixer_inputs_[input]->Initialize(input_parameters_[i], |
| fake_callbacks_[input].get()); |
| mixer_inputs_[input]->SetVolume(1.0f); |
| } |
| } |
| } |
| |
| bool ValidateAudioData(int index, int frames, float scale, double epsilon) { |
| for (int i = 0; i < audio_bus_->channels(); ++i) { |
| for (int j = index; j < frames; j++) { |
| double error = fabs(audio_bus_->channel(i)[j] - |
| expected_audio_bus_->channel(i)[j] * scale); |
| // The second comparison is for the case when scale is set to 0 |
| // (and less that 1 in general) |
| if ((error > epsilon * scale) && (error > epsilon)) { |
| EXPECT_NEAR(expected_audio_bus_->channel(i)[j] * scale, |
| audio_bus_->channel(i)[j], epsilon * scale) |
| << " i=" << i << ", j=" << j; |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| bool ValidateAudioData(int index, int frames, float scale) { |
| return ValidateAudioData(index, frames, scale, epsilon_); |
| } |
| |
| bool RenderAndValidateAudioData(float scale) { |
| if (half_fill_) { |
| for (size_t i = 0; i < fake_callbacks_.size(); ++i) |
| fake_callbacks_[i]->set_half_fill(true); |
| expected_callback_->set_half_fill(true); |
| // Initialize the AudioBus completely or we'll run into Valgrind problems |
| // during the verification step below. |
| expected_audio_bus_->Zero(); |
| } |
| |
| // Render actual audio data. |
| int frames = mixer_callback_->Render( |
| base::TimeDelta(), base::TimeTicks::Now(), 0, audio_bus_.get()); |
| if (frames != audio_bus_->frames()) |
| return false; |
| |
| // Render expected audio data (without scaling). |
| expected_callback_->Render(base::TimeDelta(), base::TimeTicks::Now(), 0, |
| expected_audio_bus_.get()); |
| |
| if (half_fill_) { |
| // In this case, just verify that every frame was initialized, this will |
| // only fail under tooling such as valgrind. |
| return ValidateAudioData( |
| 0, frames, 0, std::numeric_limits<double>::max()); |
| } else { |
| return ValidateAudioData(0, frames, scale); |
| } |
| } |
| |
| // Fill |audio_bus_| fully with |value|. |
| void FillAudioData(float value) { |
| for (int i = 0; i < audio_bus_->channels(); ++i) { |
| std::fill(audio_bus_->channel(i), |
| audio_bus_->channel(i) + audio_bus_->frames(), value); |
| } |
| } |
| |
| // Verify silence when mixer inputs are in pre-Start() and post-Start(). |
| void StartTest(int inputs) { |
| InitializeInputs(inputs); |
| |
| // Verify silence before any inputs have been started. Fill the buffer |
| // before hand with non-zero data to ensure we get zeros back. |
| FillAudioData(1.0f); |
| EXPECT_TRUE(RenderAndValidateAudioData(0.0f)); |
| |
| // Start() all even numbered mixer inputs and ensure we still get silence. |
| for (size_t i = 0; i < mixer_inputs_.size(); i += 2) |
| mixer_inputs_[i]->Start(); |
| FillAudioData(1.0f); |
| EXPECT_TRUE(RenderAndValidateAudioData(0.0f)); |
| |
| // Start() all mixer inputs and ensure we still get silence. |
| for (size_t i = 1; i < mixer_inputs_.size(); i += 2) |
| mixer_inputs_[i]->Start(); |
| FillAudioData(1.0f); |
| EXPECT_TRUE(RenderAndValidateAudioData(0.0f)); |
| |
| for (size_t i = 0; i < mixer_inputs_.size(); ++i) |
| mixer_inputs_[i]->Stop(); |
| } |
| |
| // Verify output when mixer inputs are in post-Play() state. |
| void PlayTest(int inputs) { |
| InitializeInputs(inputs); |
| |
| // Play() all mixer inputs and ensure we get the right values. |
| for (size_t i = 0; i < mixer_inputs_.size(); ++i) { |
| mixer_inputs_[i]->Start(); |
| mixer_inputs_[i]->Play(); |
| } |
| |
| for (int i = 0; i < kMixerCycles; ++i) |
| ASSERT_TRUE(RenderAndValidateAudioData(mixer_inputs_.size())); |
| |
| for (size_t i = 0; i < mixer_inputs_.size(); ++i) |
| mixer_inputs_[i]->Stop(); |
| } |
| |
| // Verify volume adjusted output when mixer inputs are in post-Play() state. |
| void PlayVolumeAdjustedTest(int inputs) { |
| InitializeInputs(inputs); |
| |
| for (size_t i = 0; i < mixer_inputs_.size(); ++i) { |
| mixer_inputs_[i]->Start(); |
| mixer_inputs_[i]->Play(); |
| } |
| |
| // Set a different volume for each mixer input and verify the results. |
| float total_scale = 0; |
| for (size_t i = 0; i < mixer_inputs_.size(); ++i) { |
| float volume = static_cast<float>(i) / mixer_inputs_.size(); |
| total_scale += volume; |
| EXPECT_TRUE(mixer_inputs_[i]->SetVolume(volume)); |
| } |
| for (int i = 0; i < kMixerCycles; ++i) |
| ASSERT_TRUE(RenderAndValidateAudioData(total_scale)); |
| |
| for (size_t i = 0; i < mixer_inputs_.size(); ++i) |
| mixer_inputs_[i]->Stop(); |
| } |
| |
| // Verify output when mixer inputs can only partially fulfill a Render(). |
| void PlayPartialRenderTest(int inputs) { |
| InitializeInputs(inputs); |
| |
| for (size_t i = 0; i < mixer_inputs_.size(); ++i) { |
| mixer_inputs_[i]->Start(); |
| mixer_inputs_[i]->Play(); |
| } |
| |
| // Verify a properly filled buffer when half filled (remainder zeroed). |
| half_fill_ = true; |
| ASSERT_TRUE(RenderAndValidateAudioData(mixer_inputs_.size())); |
| |
| for (size_t i = 0; i < mixer_inputs_.size(); ++i) |
| mixer_inputs_[i]->Stop(); |
| } |
| |
| // Verify output when mixer inputs are in Pause() state. |
| void PauseTest(int inputs) { |
| InitializeInputs(inputs); |
| |
| for (size_t i = 0; i < mixer_inputs_.size(); ++i) { |
| mixer_inputs_[i]->Start(); |
| mixer_inputs_[i]->Play(); |
| } |
| |
| // Pause() all even numbered mixer inputs and ensure we get the right value. |
| for (size_t i = 0; i < mixer_inputs_.size(); i += 2) |
| mixer_inputs_[i]->Pause(); |
| for (int i = 0; i < kMixerCycles; ++i) |
| ASSERT_TRUE(RenderAndValidateAudioData(mixer_inputs_.size() / 2)); |
| |
| for (size_t i = 0; i < mixer_inputs_.size(); ++i) |
| mixer_inputs_[i]->Stop(); |
| } |
| |
| // Verify output when mixer inputs are in post-Stop() state. |
| void StopTest(int inputs) { |
| InitializeInputs(inputs); |
| |
| // Start() and Stop() all inputs. |
| for (size_t i = 0; i < mixer_inputs_.size(); ++i) { |
| mixer_inputs_[i]->Start(); |
| mixer_inputs_[i]->Stop(); |
| } |
| |
| // Verify we get silence back; fill |audio_bus_| before hand to be sure. |
| FillAudioData(1.0f); |
| EXPECT_TRUE(RenderAndValidateAudioData(0.0f)); |
| } |
| |
| // Verify output when mixer inputs in mixed post-Stop() and post-Play() |
| // states. |
| void MixedStopPlayTest(int inputs) { |
| InitializeInputs(inputs); |
| |
| // Start() all inputs. |
| for (size_t i = 0; i < mixer_inputs_.size(); ++i) |
| mixer_inputs_[i]->Start(); |
| |
| // Stop() all even numbered mixer inputs and Play() all odd numbered inputs |
| // and ensure we get the right value. |
| for (size_t i = 1; i < mixer_inputs_.size(); i += 2) { |
| mixer_inputs_[i - 1]->Stop(); |
| mixer_inputs_[i]->Play(); |
| } |
| |
| // Stop the last input in case the number of inputs is odd |
| if (mixer_inputs_.size() % 2) |
| mixer_inputs_.back()->Stop(); |
| |
| ASSERT_TRUE(RenderAndValidateAudioData( |
| std::max(1.f, static_cast<float>(floor(mixer_inputs_.size() / 2.f))))); |
| |
| for (size_t i = 1; i < mixer_inputs_.size(); i += 2) |
| mixer_inputs_[i]->Stop(); |
| } |
| |
| scoped_refptr<AudioRendererMixerInput> CreateMixerInput() { |
| auto input = base::MakeRefCounted<AudioRendererMixerInput>( |
| this, |
| // Zero frame id, default device ID. |
| 0, std::string(), AudioLatency::LATENCY_PLAYBACK); |
| input->GetOutputDeviceInfoAsync( |
| base::DoNothing()); // Primes input, needed for tests. |
| task_env_.RunUntilIdle(); |
| return input; |
| } |
| |
| protected: |
| virtual ~AudioRendererMixerTest() = default; |
| |
| base::test::ScopedTaskEnvironment task_env_; |
| scoped_refptr<MockAudioRendererSink> sink_; |
| std::unique_ptr<AudioRendererMixer> mixer_; |
| AudioRendererSink::RenderCallback* mixer_callback_; |
| std::vector<AudioParameters> input_parameters_; |
| AudioParameters output_parameters_; |
| std::unique_ptr<AudioBus> audio_bus_; |
| std::unique_ptr<AudioBus> expected_audio_bus_; |
| std::vector<scoped_refptr<AudioRendererMixerInput>> mixer_inputs_; |
| std::vector<std::unique_ptr<FakeAudioRenderCallback>> fake_callbacks_; |
| std::unique_ptr<FakeAudioRenderCallback> expected_callback_; |
| double epsilon_; |
| bool half_fill_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(AudioRendererMixerTest); |
| }; |
| |
| class AudioRendererMixerBehavioralTest : public AudioRendererMixerTest {}; |
| |
| ACTION_P(SignalEvent, event) { |
| event->Signal(); |
| } |
| |
| // Verify a mixer with no inputs returns silence for all requested frames. |
| TEST_P(AudioRendererMixerTest, NoInputs) { |
| FillAudioData(1.0f); |
| EXPECT_TRUE(RenderAndValidateAudioData(0.0f)); |
| } |
| |
| // Test mixer output with one input in the pre-Start() and post-Start() state. |
| TEST_P(AudioRendererMixerTest, OneInputStart) { |
| StartTest(1); |
| } |
| |
| // Test mixer output with many inputs in the pre-Start() and post-Start() state. |
| TEST_P(AudioRendererMixerTest, ManyInputStart) { |
| StartTest(kMixerInputs); |
| } |
| |
| // Test mixer output with one input in the post-Play() state. |
| TEST_P(AudioRendererMixerTest, OneInputPlay) { |
| PlayTest(1); |
| } |
| |
| // Test mixer output with many inputs in the post-Play() state. |
| TEST_P(AudioRendererMixerTest, ManyInputPlay) { |
| PlayTest(kMixerInputs); |
| } |
| |
| // Test volume adjusted mixer output with one input in the post-Play() state. |
| TEST_P(AudioRendererMixerTest, OneInputPlayVolumeAdjusted) { |
| PlayVolumeAdjustedTest(1); |
| } |
| |
| // Test volume adjusted mixer output with many inputs in the post-Play() state. |
| TEST_P(AudioRendererMixerTest, ManyInputPlayVolumeAdjusted) { |
| PlayVolumeAdjustedTest(kMixerInputs); |
| } |
| |
| // Test mixer output with one input and partial Render() in post-Play() state. |
| TEST_P(AudioRendererMixerTest, OneInputPlayPartialRender) { |
| PlayPartialRenderTest(1); |
| } |
| |
| // Test mixer output with many inputs and partial Render() in post-Play() state. |
| TEST_P(AudioRendererMixerTest, ManyInputPlayPartialRender) { |
| PlayPartialRenderTest(kMixerInputs); |
| } |
| |
| // Test mixer output with one input in the post-Pause() state. |
| TEST_P(AudioRendererMixerTest, OneInputPause) { |
| PauseTest(1); |
| } |
| |
| // Test mixer output with many inputs in the post-Pause() state. |
| TEST_P(AudioRendererMixerTest, ManyInputPause) { |
| PauseTest(kMixerInputs); |
| } |
| |
| // Test mixer output with one input in the post-Stop() state. |
| TEST_P(AudioRendererMixerTest, OneInputStop) { |
| StopTest(1); |
| } |
| |
| // Test mixer output with many inputs in the post-Stop() state. |
| TEST_P(AudioRendererMixerTest, ManyInputStop) { |
| StopTest(kMixerInputs); |
| } |
| |
| // Test mixer with many inputs in mixed post-Stop() and post-Play() states. |
| TEST_P(AudioRendererMixerTest, ManyInputMixedStopPlay) { |
| MixedStopPlayTest(kMixerInputs); |
| } |
| |
| // Test mixer with many inputs in mixed post-Stop() and post-Play() states. |
| TEST_P(AudioRendererMixerTest, ManyInputMixedStopPlayOdd) { |
| // Odd number of inputs per sample rate, to stop them unevenly. |
| MixedStopPlayTest(kOddMixerInputs); |
| } |
| |
| TEST_P(AudioRendererMixerBehavioralTest, OnRenderError) { |
| InitializeInputs(kMixerInputs); |
| for (size_t i = 0; i < mixer_inputs_.size(); ++i) { |
| mixer_inputs_[i]->Start(); |
| mixer_inputs_[i]->Play(); |
| EXPECT_CALL(*fake_callbacks_[i], OnRenderError()).Times(1); |
| } |
| |
| mixer_callback_->OnRenderError(); |
| for (size_t i = 0; i < mixer_inputs_.size(); ++i) |
| mixer_inputs_[i]->Stop(); |
| } |
| |
| TEST_P(AudioRendererMixerBehavioralTest, OnRenderErrorPausedInput) { |
| InitializeInputs(kMixerInputs); |
| |
| for (size_t i = 0; i < mixer_inputs_.size(); ++i) { |
| mixer_inputs_[i]->Start(); |
| EXPECT_CALL(*fake_callbacks_[i], OnRenderError()).Times(1); |
| } |
| |
| // Fire the error before attaching any inputs. Ensure an error is recieved |
| // even if the input is not connected. |
| mixer_callback_->OnRenderError(); |
| |
| for (size_t i = 0; i < mixer_inputs_.size(); ++i) |
| mixer_inputs_[i]->Stop(); |
| } |
| |
| // Ensure the physical stream is paused after a certain amount of time with no |
| // inputs playing. The test will hang if the behavior is incorrect. |
| TEST_P(AudioRendererMixerBehavioralTest, MixerPausesStream) { |
| const base::TimeDelta kPauseTime = base::TimeDelta::FromMilliseconds(500); |
| // This value can't be too low or valgrind, tsan will timeout on the bots. |
| const base::TimeDelta kTestTimeout = 10 * kPauseTime; |
| mixer_->SetPauseDelayForTesting(kPauseTime); |
| |
| base::WaitableEvent pause_event( |
| base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| EXPECT_CALL(*sink_.get(), Pause()).Times(2) |
| .WillRepeatedly(SignalEvent(&pause_event)); |
| InitializeInputs(1); |
| |
| // Ensure never playing the input results in a sink pause. |
| const base::TimeDelta kSleepTime = base::TimeDelta::FromMilliseconds(100); |
| base::TimeTicks start_time = base::TimeTicks::Now(); |
| while (!pause_event.IsSignaled()) { |
| mixer_callback_->Render(base::TimeDelta(), base::TimeTicks::Now(), 0, |
| audio_bus_.get()); |
| base::PlatformThread::Sleep(kSleepTime); |
| ASSERT_TRUE(base::TimeTicks::Now() - start_time < kTestTimeout); |
| } |
| pause_event.Reset(); |
| |
| // Playing the input for the first time should cause a sink play. |
| mixer_inputs_[0]->Start(); |
| EXPECT_CALL(*sink_.get(), Play()); |
| mixer_inputs_[0]->Play(); |
| mixer_inputs_[0]->Pause(); |
| |
| // Ensure once the input is paused the sink eventually pauses. |
| start_time = base::TimeTicks::Now(); |
| while (!pause_event.IsSignaled()) { |
| mixer_callback_->Render(base::TimeDelta(), base::TimeTicks::Now(), 0, |
| audio_bus_.get()); |
| base::PlatformThread::Sleep(kSleepTime); |
| ASSERT_TRUE(base::TimeTicks::Now() - start_time < kTestTimeout); |
| } |
| |
| mixer_inputs_[0]->Stop(); |
| } |
| |
| INSTANTIATE_TEST_CASE_P( |
| /* no prefix */, |
| AudioRendererMixerTest, |
| testing::Values( |
| // No resampling, 1 input sample rate. |
| std::make_tuple(&kTestInputLower, 1, kTestInputLower, 0.00000048), |
| |
| // Upsampling, 1 input sample rate. |
| std::make_tuple(&kTestInputLower, 1, kTestInputHigher, 0.01), |
| |
| // Downsampling, 1 input sample rate. |
| std::make_tuple(&kTestInputHigher, 1, kTestInputLower, 0.01), |
| |
| // Downsampling, multuple input sample rates. |
| std::make_tuple(static_cast<const int* const>(kTestInput3Rates), |
| base::size(kTestInput3Rates), |
| kTestInput3Rates[0], |
| 0.01), |
| |
| // Upsampling, multiple sinput sample rates. |
| std::make_tuple(static_cast<const int* const>(kTestInput3Rates), |
| base::size(kTestInput3Rates), |
| kTestInput3Rates[2], |
| 0.01), |
| |
| // Both downsampling and upsampling, multiple input sample rates |
| std::make_tuple(static_cast<const int* const>(kTestInput3Rates), |
| base::size(kTestInput3Rates), |
| kTestInput3Rates[1], |
| 0.01))); |
| |
| // Test cases for behavior which is independent of parameters. Values() doesn't |
| // support single item lists and we don't want these test cases to run for every |
| // parameter set. |
| INSTANTIATE_TEST_CASE_P( |
| /* no prefix */, |
| AudioRendererMixerBehavioralTest, |
| testing::ValuesIn(std::vector<AudioRendererMixerTestData>( |
| 1, |
| std::make_tuple(&kTestInputLower, 1, kTestInputLower, 0.00000048)))); |
| } // namespace media |