diff options
Diffstat (limited to 'media')
-rw-r--r-- | media/base/audio_renderer_mixer.cc | 101 | ||||
-rw-r--r-- | media/base/audio_renderer_mixer.h | 62 | ||||
-rw-r--r-- | media/base/audio_renderer_mixer_input.cc | 75 | ||||
-rw-r--r-- | media/base/audio_renderer_mixer_input.h | 61 | ||||
-rw-r--r-- | media/base/audio_renderer_mixer_input_unittest.cc | 100 | ||||
-rw-r--r-- | media/base/audio_renderer_mixer_unittest.cc | 397 | ||||
-rw-r--r-- | media/base/fake_audio_render_callback.cc | 44 | ||||
-rw-r--r-- | media/base/fake_audio_render_callback.h | 48 | ||||
-rw-r--r-- | media/media.gyp | 8 |
9 files changed, 896 insertions, 0 deletions
diff --git a/media/base/audio_renderer_mixer.cc b/media/base/audio_renderer_mixer.cc new file mode 100644 index 0000000..5b89aa4 --- /dev/null +++ b/media/base/audio_renderer_mixer.cc @@ -0,0 +1,101 @@ +// 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 "base/logging.h" + +namespace media { + +AudioRendererMixer::AudioRendererMixer( + const AudioParameters& params, const scoped_refptr<AudioRendererSink>& sink) + : audio_parameters_(params), + audio_sink_(sink) { + // TODO(dalecurtis): Once we have resampling we'll need to pass on a different + // set of AudioParameters than the ones we're given. + audio_sink_->Initialize(audio_parameters_, this); + audio_sink_->Start(); +} + +AudioRendererMixer::~AudioRendererMixer() { + // AudioRendererSinks must be stopped before being destructed. + audio_sink_->Stop(); + + // Ensures that all mixer inputs have stopped themselves prior to destruction + // and have called RemoveMixerInput(). + DCHECK_EQ(mixer_inputs_.size(), 0U); +} + +void AudioRendererMixer::AddMixerInput( + const scoped_refptr<AudioRendererMixerInput>& input) { + base::AutoLock auto_lock(mixer_inputs_lock_); + mixer_inputs_.insert(input); +} + +void AudioRendererMixer::RemoveMixerInput( + const scoped_refptr<AudioRendererMixerInput>& input) { + base::AutoLock auto_lock(mixer_inputs_lock_); + mixer_inputs_.erase(input); +} + +int AudioRendererMixer::Render(const std::vector<float*>& audio_data, + int number_of_frames, + int audio_delay_milliseconds) { + base::AutoLock auto_lock(mixer_inputs_lock_); + + // Zero |audio_data| so we're mixing into a clean buffer and return silence if + // we couldn't get enough data from our inputs. + for (int i = 0; i < audio_parameters_.channels(); ++i) + memset(audio_data[i], 0, number_of_frames * sizeof(*audio_data[i])); + + // Have each mixer render its data into an output buffer then mix the result. + for (AudioRendererMixerInputSet::iterator it = mixer_inputs_.begin(); + it != mixer_inputs_.end(); ++it) { + const scoped_refptr<AudioRendererMixerInput>& input = *it; + + double volume; + input->GetVolume(&volume); + + // Nothing to do if the input isn't playing or the volume is zero. + if (!input->playing() || volume == 0.0f) + continue; + + const std::vector<float*>& mixer_input_audio_data = input->audio_data(); + + int frames_filled = input->callback()->Render( + mixer_input_audio_data, number_of_frames, audio_delay_milliseconds); + if (frames_filled == 0) + continue; + + // TODO(dalecurtis): Resample audio data. + + // Volume adjust and mix each mixer input into |audio_data| after rendering. + // TODO(dalecurtis): Optimize with NEON/SSE/AVX vector_fmac from FFmpeg. + for (int j = 0; j < audio_parameters_.channels(); ++j) { + float* dest = audio_data[j]; + float* source = mixer_input_audio_data[j]; + for (int k = 0; k < frames_filled; ++k) + dest[k] += source[k] * static_cast<float>(volume); + } + + // No need to clamp values as InterleaveFloatToInt() will take care of this + // for us later when data is transferred to the browser process. + } + + // Always return the full number of frames requested, padded with silence if + // we couldn't get enough data. + return number_of_frames; +} + +void AudioRendererMixer::OnRenderError() { + base::AutoLock auto_lock(mixer_inputs_lock_); + + // Call each mixer input and signal an error. + for (AudioRendererMixerInputSet::iterator it = mixer_inputs_.begin(); + it != mixer_inputs_.end(); ++it) { + (*it)->callback()->OnRenderError(); + } +} + +} // namespace media diff --git a/media/base/audio_renderer_mixer.h b/media/base/audio_renderer_mixer.h new file mode 100644 index 0000000..32102ff --- /dev/null +++ b/media/base/audio_renderer_mixer.h @@ -0,0 +1,62 @@ +// 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. + +#ifndef MEDIA_BASE_AUDIO_RENDERER_MIXER_H_ +#define MEDIA_BASE_AUDIO_RENDERER_MIXER_H_ + +#include <set> +#include <vector> + +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "media/base/audio_renderer_mixer_input.h" +#include "media/base/audio_renderer_sink.h" + +namespace media { + +// Mixes a set of AudioRendererMixerInputs into a single output stream which is +// funneled into a single shared AudioRendererSink; saving a bundle on renderer +// side resources. +// TODO(dalecurtis): Update documentation once resampling is available. +class MEDIA_EXPORT AudioRendererMixer + : public base::RefCountedThreadSafe<AudioRendererMixer>, + NON_EXPORTED_BASE(public AudioRendererSink::RenderCallback) { + public: + AudioRendererMixer(const AudioParameters& params, + const scoped_refptr<AudioRendererSink>& sink); + + // Add or remove a mixer input from mixing; called by AudioRendererMixerInput. + void AddMixerInput(const scoped_refptr<AudioRendererMixerInput>& input); + void RemoveMixerInput(const scoped_refptr<AudioRendererMixerInput>& input); + + protected: + friend class base::RefCountedThreadSafe<AudioRendererMixer>; + virtual ~AudioRendererMixer(); + + private: + // AudioRendererSink::RenderCallback implementation. + virtual int Render(const std::vector<float*>& audio_data, + int number_of_frames, + int audio_delay_milliseconds) OVERRIDE; + virtual void OnRenderError() OVERRIDE; + + // AudioParameters this mixer was constructed with. + AudioParameters audio_parameters_; + + // Output sink for this mixer. + scoped_refptr<AudioRendererSink> audio_sink_; + + // Set of mixer inputs to be mixed by this mixer. Access is thread-safe + // through |mixer_inputs_lock_|. + typedef std::set< scoped_refptr<AudioRendererMixerInput> > + AudioRendererMixerInputSet; + AudioRendererMixerInputSet mixer_inputs_; + base::Lock mixer_inputs_lock_; + + DISALLOW_COPY_AND_ASSIGN(AudioRendererMixer); +}; + +} // namespace media + +#endif // MEDIA_BASE_AUDIO_RENDERER_MIXER_H_ diff --git a/media/base/audio_renderer_mixer_input.cc b/media/base/audio_renderer_mixer_input.cc new file mode 100644 index 0000000..fa34678 --- /dev/null +++ b/media/base/audio_renderer_mixer_input.cc @@ -0,0 +1,75 @@ +// 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_input.h" + +#include "base/logging.h" +#include "media/base/audio_renderer_mixer.h" + +namespace media { + +AudioRendererMixerInput::AudioRendererMixerInput( + const scoped_refptr<AudioRendererMixer>& mixer) + : playing_(false), + initialized_(false), + volume_(1.0f), + mixer_(mixer) { +} + +AudioRendererMixerInput::~AudioRendererMixerInput() { + if (!initialized_) + return; + + // Clean up |audio_data_|. + for (size_t i = 0; i < audio_data_.size(); ++i) + delete [] audio_data_[i]; + audio_data_.clear(); +} + +void AudioRendererMixerInput::Initialize( + const AudioParameters& params, + AudioRendererSink::RenderCallback* callback) { + DCHECK(!initialized_); + + // TODO(dalecurtis): If we switch to AVX/SSE optimization, we'll need to + // allocate these on 32-byte boundaries and ensure they're sized % 32 bytes. + audio_data_.reserve(params.channels()); + for (int i = 0; i < params.channels(); ++i) + audio_data_.push_back(new float[params.frames_per_buffer()]); + + callback_ = callback; + initialized_ = true; +} + +void AudioRendererMixerInput::Start() { + DCHECK(initialized_); + mixer_->AddMixerInput(this); +} + +void AudioRendererMixerInput::Stop() { + DCHECK(initialized_); + playing_ = false; + mixer_->RemoveMixerInput(this); +} + +void AudioRendererMixerInput::Play() { + playing_ = true; +} + +void AudioRendererMixerInput::Pause(bool /* flush */) { + // We don't care about flush since Pause() simply indicates we should send + // silence to AudioRendererMixer. + playing_ = false; +} + +bool AudioRendererMixerInput::SetVolume(double volume) { + volume_ = volume; + return true; +} + +void AudioRendererMixerInput::GetVolume(double* volume) { + *volume = volume_; +} + +} // namespace media diff --git a/media/base/audio_renderer_mixer_input.h b/media/base/audio_renderer_mixer_input.h new file mode 100644 index 0000000..7e8c9f1 --- /dev/null +++ b/media/base/audio_renderer_mixer_input.h @@ -0,0 +1,61 @@ +// 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. + +#ifndef MEDIA_BASE_AUDIO_RENDERER_MIXER_INPUT_H_ +#define MEDIA_BASE_AUDIO_RENDERER_MIXER_INPUT_H_ + +#include <vector> + +#include "media/base/audio_renderer_sink.h" + +namespace media { + +class AudioRendererMixer; + +class MEDIA_EXPORT AudioRendererMixerInput + : NON_EXPORTED_BASE(public AudioRendererSink) { + public: + explicit AudioRendererMixerInput( + const scoped_refptr<AudioRendererMixer>& mixer); + + // Each input should manage its own data buffer. The mixer will call this + // method when it needs a buffer for rendering. + const std::vector<float*>& audio_data() { return audio_data_; } + AudioRendererSink::RenderCallback* callback() { return callback_; } + bool playing() { return playing_; } + + // AudioRendererSink implementation. + virtual void Start() OVERRIDE; + virtual void Stop() OVERRIDE; + virtual void Play() OVERRIDE; + virtual void Pause(bool flush) OVERRIDE; + virtual bool SetVolume(double volume) OVERRIDE; + virtual void GetVolume(double* volume) OVERRIDE; + virtual void Initialize(const AudioParameters& params, + AudioRendererSink::RenderCallback* renderer) OVERRIDE; + + protected: + virtual ~AudioRendererMixerInput(); + + private: + bool playing_; + bool initialized_; + double volume_; + + // AudioRendererMixer is reference counted by all its AudioRendererMixerInputs + // and is destroyed when all AudioRendererMixerInputs have called RemoveMixer. + scoped_refptr<AudioRendererMixer> mixer_; + + // Source of audio data which is provided to the mixer. + AudioRendererSink::RenderCallback* callback_; + + // Vector for rendering audio data which will be used by the mixer. + std::vector<float*> audio_data_; + + DISALLOW_COPY_AND_ASSIGN(AudioRendererMixerInput); +}; + +} // namespace media + +#endif // MEDIA_BASE_AUDIO_RENDERER_MIXER_INPUT_H_ diff --git a/media/base/audio_renderer_mixer_input_unittest.cc b/media/base/audio_renderer_mixer_input_unittest.cc new file mode 100644 index 0000000..b57b7cc --- /dev/null +++ b/media/base/audio_renderer_mixer_input_unittest.cc @@ -0,0 +1,100 @@ +// 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/audio/audio_util.h" +#include "media/audio/null_audio_sink.h" +#include "media/base/audio_renderer_mixer.h" +#include "media/base/audio_renderer_mixer_input.h" +#include "media/base/fake_audio_render_callback.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +static const int kBitsPerChannel = 16; +static const int kSampleRate = 48000; +static const ChannelLayout kChannelLayout = CHANNEL_LAYOUT_STEREO; + +class AudioRendererMixerInputTest : public ::testing::Test { + public: + AudioRendererMixerInputTest() { + audio_parameters_ = AudioParameters( + AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout, kSampleRate, + kBitsPerChannel, GetHighLatencyOutputBufferSize(kSampleRate)); + + mixer_ = new AudioRendererMixer(audio_parameters_, new NullAudioSink()); + mixer_input_ = new AudioRendererMixerInput(mixer_); + fake_callback_.reset(new FakeAudioRenderCallback(audio_parameters_)); + mixer_input_->Initialize(audio_parameters_, fake_callback_.get()); + } + + // Render audio_parameters_.frames_per_buffer() frames into |audio_data_| and + // verify the result against |check_value|. + void RenderAndValidateAudioData(float check_value) { + const std::vector<float*>& audio_data = mixer_input_->audio_data(); + + ASSERT_EQ(fake_callback_->Render( + audio_data, audio_parameters_.frames_per_buffer(), 0), + audio_parameters_.frames_per_buffer()); + + for (size_t i = 0; i < audio_data.size(); ++i) + for (int j = 0; j < audio_parameters_.frames_per_buffer(); j++) + ASSERT_FLOAT_EQ(check_value, audio_data[i][j]); + } + + protected: + virtual ~AudioRendererMixerInputTest() {} + + AudioParameters audio_parameters_; + scoped_refptr<AudioRendererMixer> mixer_; + scoped_refptr<AudioRendererMixerInput> mixer_input_; + scoped_ptr<FakeAudioRenderCallback> fake_callback_; + + DISALLOW_COPY_AND_ASSIGN(AudioRendererMixerInputTest); +}; + +// Test that getting and setting the volume work as expected. +TEST_F(AudioRendererMixerInputTest, GetSetVolume) { + // Starting volume should be 0. + double volume = 1.0f; + mixer_input_->GetVolume(&volume); + EXPECT_EQ(volume, 1.0f); + + const double kVolume = 0.5f; + EXPECT_TRUE(mixer_input_->SetVolume(kVolume)); + mixer_input_->GetVolume(&volume); + EXPECT_EQ(volume, kVolume); +} + +// Test audio_data() is allocated correctly. +TEST_F(AudioRendererMixerInputTest, GetAudioData) { + RenderAndValidateAudioData(fake_callback_->fill_value()); + + // TODO(dalecurtis): Perform alignment and size checks when we switch over to + // FFmpeg optimized vector_fmac. +} + +// Test callback() works as expected. +TEST_F(AudioRendererMixerInputTest, GetCallback) { + EXPECT_EQ(mixer_input_->callback(), fake_callback_.get()); + RenderAndValidateAudioData(fake_callback_->fill_value()); +} + +// Test Start()/Play()/Pause()/Stop()/playing() all work as expected. Also +// implicitly tests that AddMixerInput() and RemoveMixerInput() work without +// crashing; functional tests for these methods are in AudioRendererMixerTest. +TEST_F(AudioRendererMixerInputTest, StartPlayPauseStopPlaying) { + mixer_input_->Start(); + EXPECT_FALSE(mixer_input_->playing()); + mixer_input_->Play(); + EXPECT_TRUE(mixer_input_->playing()); + mixer_input_->Pause(false); + EXPECT_FALSE(mixer_input_->playing()); + mixer_input_->Play(); + EXPECT_TRUE(mixer_input_->playing()); + mixer_input_->Stop(); + EXPECT_FALSE(mixer_input_->playing()); +} + +} // namespace media diff --git a/media/base/audio_renderer_mixer_unittest.cc b/media/base/audio_renderer_mixer_unittest.cc new file mode 100644 index 0000000..e897bbf --- /dev/null +++ b/media/base/audio_renderer_mixer_unittest.cc @@ -0,0 +1,397 @@ +// 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 <cmath> + +#include "base/memory/scoped_ptr.h" +#include "media/audio/audio_util.h" +#include "media/base/audio_renderer_mixer.h" +#include "media/base/audio_renderer_mixer_input.h" +#include "media/base/fake_audio_render_callback.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +// Parameters which control the many input case tests. +static const int kMixerInputs = 64; +static const int kMixerCycles = 32; + +static const int kBitsPerChannel = 16; +static const int kSampleRate = 48000; +static const ChannelLayout kChannelLayout = CHANNEL_LAYOUT_STEREO; + +// Multiple rounds of addition result in precision loss with float values, so we +// need an epsilon such that if for all x f(x) = sum(x, m) and g(x) = m * x then +// fabs(f - g) < kEpsilon. The kEpsilon below has been tested with m < 128. +static const float kEpsilon = 0.00015f; + +class MockAudioRendererSink : public AudioRendererSink { + public: + MOCK_METHOD0(Start, void()); + MOCK_METHOD0(Stop, void()); + MOCK_METHOD1(Pause, void(bool flush)); + MOCK_METHOD0(Play, void()); + MOCK_METHOD1(SetPlaybackRate, void(float rate)); + MOCK_METHOD1(SetVolume, bool(double volume)); + MOCK_METHOD1(GetVolume, void(double* volume)); + + void Initialize(const media::AudioParameters& params, + AudioRendererSink::RenderCallback* renderer) OVERRIDE { + // TODO(dalecurtis): Once we have resampling we need to ensure we are given + // an AudioParameters which reflects the hardware settings. + callback_ = renderer; + }; + + AudioRendererSink::RenderCallback* callback() { + return callback_; + } + + void SimulateRenderError() { + callback_->OnRenderError(); + } + + protected: + virtual ~MockAudioRendererSink() {} + + AudioRendererSink::RenderCallback* callback_; +}; + +class AudioRendererMixerTest : public ::testing::Test { + public: + AudioRendererMixerTest() { + audio_parameters_ = AudioParameters( + AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout, kSampleRate, + kBitsPerChannel, GetHighLatencyOutputBufferSize(kSampleRate)); + sink_ = new MockAudioRendererSink(); + EXPECT_CALL(*sink_, Start()); + EXPECT_CALL(*sink_, Stop()); + + mixer_ = new AudioRendererMixer(audio_parameters_, sink_); + mixer_callback_ = sink_->callback(); + + // TODO(dalecurtis): If we switch to AVX/SSE optimization, we'll need to + // allocate these on 32-byte boundaries and ensure they're sized % 32 bytes. + audio_data_.reserve(audio_parameters_.channels()); + for (int i = 0; i < audio_parameters_.channels(); ++i) + audio_data_.push_back(new float[audio_parameters_.frames_per_buffer()]); + + fake_callback_.reset(new FakeAudioRenderCallback(audio_parameters_)); + } + + void InitializeInputs(int count) { + for (int i = 0; i < count; ++i) { + scoped_refptr<AudioRendererMixerInput> mixer_input( + new AudioRendererMixerInput(mixer_)); + mixer_input->Initialize(audio_parameters_, fake_callback_.get()); + mixer_input->SetVolume(1.0f); + mixer_inputs_.push_back(mixer_input); + } + } + + bool ValidateAudioData(int start_index, int frames, float check_value) { + for (size_t i = 0; i < audio_data_.size(); ++i) { + for (int j = start_index; j < frames; j++) { + if (fabs(audio_data_[i][j] - check_value) > kEpsilon) { + EXPECT_NEAR(check_value, audio_data_[i][j], kEpsilon) + << " i=" << i << ", j=" << j; + return false; + } + } + } + return true; + } + + // Render audio_parameters_.frames_per_buffer() frames into |audio_data_| and + // verify the result against |check_value|. + bool RenderAndValidateAudioData(float check_value) { + int frames = mixer_callback_->Render( + audio_data_, audio_parameters_.frames_per_buffer(), 0); + return frames == audio_parameters_.frames_per_buffer() && ValidateAudioData( + 0, audio_parameters_.frames_per_buffer(), check_value); + } + + // Fill |audio_data_| fully with |value|. + void FillAudioData(float value) { + for (size_t i = 0; i < audio_data_.size(); ++i) + std::fill(audio_data_[i], + audio_data_[i] + audio_parameters_.frames_per_buffer(), 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) + 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); + + for (size_t i = 0; i < mixer_inputs_.size(); ++i) + mixer_inputs_[i]->Start(); + + // Play() 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]->Play(); + for (int i = 0; i < kMixerCycles; ++i) { + fake_callback_->NextFillValue(); + ASSERT_TRUE(RenderAndValidateAudioData( + fake_callback_->fill_value() * std::max( + mixer_inputs_.size() / 2, static_cast<size_t>(1)))); + } + + // Play() all mixer inputs and ensure we still get the right values. + for (size_t i = 1; i < mixer_inputs_.size(); i += 2) + mixer_inputs_[i]->Play(); + for (int i = 0; i < kMixerCycles; ++i) { + fake_callback_->NextFillValue(); + ASSERT_TRUE(RenderAndValidateAudioData( + fake_callback_->fill_value() * 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) { + fake_callback_->NextFillValue(); + ASSERT_TRUE(RenderAndValidateAudioData( + fake_callback_->fill_value() * 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); + int frames = audio_parameters_.frames_per_buffer(); + + 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). + fake_callback_->set_half_fill(true); + for (int i = 0; i < kMixerCycles; ++i) { + fake_callback_->NextFillValue(); + ASSERT_EQ(mixer_callback_->Render(audio_data_, frames, 0), frames); + ASSERT_TRUE(ValidateAudioData( + 0, frames / 2, fake_callback_->fill_value() * mixer_inputs_.size())); + ASSERT_TRUE(ValidateAudioData( + frames / 2, frames, 0.0f)); + } + fake_callback_->set_half_fill(false); + + 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(false); + for (int i = 0; i < kMixerCycles; ++i) { + fake_callback_->NextFillValue(); + ASSERT_TRUE(RenderAndValidateAudioData( + fake_callback_->fill_value() * (mixer_inputs_.size() / 2))); + } + + // Pause() all the inputs and verify we get silence back. + for (size_t i = 1; i < mixer_inputs_.size(); i += 2) + mixer_inputs_[i]->Pause(false); + 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-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_data_| before hand to be sure. + FillAudioData(1.0f); + EXPECT_TRUE(RenderAndValidateAudioData(0.0f)); + } + + protected: + virtual ~AudioRendererMixerTest() { + for (size_t i = 0; i < audio_data_.size(); ++i) + delete [] audio_data_[i]; + } + + scoped_refptr<MockAudioRendererSink> sink_; + scoped_refptr<AudioRendererMixer> mixer_; + AudioRendererSink::RenderCallback* mixer_callback_; + scoped_ptr<FakeAudioRenderCallback> fake_callback_; + AudioParameters audio_parameters_; + std::vector<float*> audio_data_; + std::vector< scoped_refptr<AudioRendererMixerInput> > mixer_inputs_; + + DISALLOW_COPY_AND_ASSIGN(AudioRendererMixerTest); +}; + +// Verify a mixer with no inputs returns silence for all requested frames. +TEST_F(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_F(AudioRendererMixerTest, OneInputStart) { + StartTest(1); +} + +// Test mixer output with many inputs in the pre-Start() and post-Start() state. +TEST_F(AudioRendererMixerTest, ManyInputStart) { + StartTest(kMixerInputs); +} + +// Test mixer output with one input in the post-Play() state. +TEST_F(AudioRendererMixerTest, OneInputPlay) { + PlayTest(1); +} + +// Test mixer output with many inputs in the post-Play() state. +TEST_F(AudioRendererMixerTest, ManyInputPlay) { + PlayTest(kMixerInputs); +} + +// Test volume adjusted mixer output with one input in the post-Play() state. +TEST_F(AudioRendererMixerTest, OneInputPlayVolumeAdjusted) { + PlayVolumeAdjustedTest(1); +} + +// Test volume adjusted mixer output with many inputs in the post-Play() state. +TEST_F(AudioRendererMixerTest, ManyInputPlayVolumeAdjusted) { + PlayVolumeAdjustedTest(kMixerInputs); +} + +// Test mixer output with one input and partial Render() in post-Play() state. +TEST_F(AudioRendererMixerTest, OneInputPlayPartialRender) { + PlayPartialRenderTest(1); +} + +// Test mixer output with many inputs and partial Render() in post-Play() state. +TEST_F(AudioRendererMixerTest, ManyInputPlayPartialRender) { + PlayPartialRenderTest(kMixerInputs); +} + +// Test mixer output with one input in the post-Pause() state. +TEST_F(AudioRendererMixerTest, OneInputPause) { + PauseTest(1); +} + +// Test mixer output with many inputs in the post-Pause() state. +TEST_F(AudioRendererMixerTest, ManyInputPause) { + PauseTest(kMixerInputs); +} + +// Test mixer output with one input in the post-Stop() state. +TEST_F(AudioRendererMixerTest, OneInputStop) { + StopTest(1); +} + +// Test mixer output with many inputs in the post-Stop() state. +TEST_F(AudioRendererMixerTest, ManyInputStop) { + StopTest(kMixerInputs); +} + +// Test mixer with many inputs in mixed post-Stop() and post-Play() states. +TEST_F(AudioRendererMixerTest, ManyInputMixedStopPlay) { + InitializeInputs(kMixerInputs); + + // 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(); + } + for (int i = 0; i < kMixerCycles; ++i) { + fake_callback_->NextFillValue(); + ASSERT_TRUE(RenderAndValidateAudioData( + fake_callback_->fill_value() * std::max( + mixer_inputs_.size() / 2, static_cast<size_t>(1)))); + } + + for (size_t i = 1; i < mixer_inputs_.size(); i += 2) + mixer_inputs_[i]->Stop(); +} + +TEST_F(AudioRendererMixerTest, OnRenderError) { + std::vector< scoped_refptr<AudioRendererMixerInput> > mixer_inputs; + for (int i = 0; i < kMixerInputs; ++i) { + scoped_refptr<AudioRendererMixerInput> mixer_input( + new AudioRendererMixerInput(mixer_)); + mixer_input->Initialize(audio_parameters_, fake_callback_.get()); + mixer_input->SetVolume(1.0f); + mixer_input->Start(); + mixer_inputs_.push_back(mixer_input); + } + + EXPECT_CALL(*fake_callback_, OnRenderError()).Times(kMixerInputs); + sink_->SimulateRenderError(); + for (int i = 0; i < kMixerInputs; ++i) + mixer_inputs_[i]->Stop(); +} + +} // namespace media diff --git a/media/base/fake_audio_render_callback.cc b/media/base/fake_audio_render_callback.cc new file mode 100644 index 0000000..3fc711b4 --- /dev/null +++ b/media/base/fake_audio_render_callback.cc @@ -0,0 +1,44 @@ +// 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. + +// MSVC++ requires this to be set before any other includes to get M_PI. +#define _USE_MATH_DEFINES + +#include "media/base/fake_audio_render_callback.h" + +#include <cmath> + +namespace media { + +// Arbitrarily chosen prime value for the fake random number generator. +static const int kFakeRandomSeed = 9440671; + +FakeAudioRenderCallback::FakeAudioRenderCallback(const AudioParameters& params) + : half_fill_(false), + fill_value_(1.0f), + audio_parameters_(params) { +} + +FakeAudioRenderCallback::~FakeAudioRenderCallback() {} + +int FakeAudioRenderCallback::Render(const std::vector<float*>& audio_data, + int number_of_frames, + int audio_delay_milliseconds) { + if (half_fill_) + number_of_frames /= 2; + for (size_t i = 0; i < audio_data.size(); ++i) + std::fill(audio_data[i], audio_data[i] + number_of_frames, fill_value_); + + return number_of_frames; +} + +void FakeAudioRenderCallback::NextFillValue() { + // Use irrationality of PI to fake random numbers; square fill_value_ to keep + // numbers between [0, 1) so range extension to [-1, 1) by 2 * x - 1 works. + fill_value_ = 2.0f * (static_cast<int>( + M_PI * fill_value_ * fill_value_ * kFakeRandomSeed) % kFakeRandomSeed) / + static_cast<float>(kFakeRandomSeed) - 1.0f; +} + +} // namespace media diff --git a/media/base/fake_audio_render_callback.h b/media/base/fake_audio_render_callback.h new file mode 100644 index 0000000..f80c942 --- /dev/null +++ b/media/base/fake_audio_render_callback.h @@ -0,0 +1,48 @@ +// 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. + +#ifndef MEDIA_BASE_FAKE_AUDIO_RENDER_CALLBACK_H_ +#define MEDIA_BASE_FAKE_AUDIO_RENDER_CALLBACK_H_ + +#include <vector> + +#include "base/time.h" +#include "media/base/audio_renderer_sink.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace media { + +class FakeAudioRenderCallback : public AudioRendererSink::RenderCallback { + public: + // Initializes |fill_value_| to a random value seeded by current time. + explicit FakeAudioRenderCallback(const AudioParameters& params); + virtual ~FakeAudioRenderCallback(); + + // Renders |fill_value_| into the provided audio data buffer. If |half_fill_| + // is set, will only fill half the buffer. + int Render(const std::vector<float*>& audio_data, int number_of_frames, + int audio_delay_milliseconds) OVERRIDE; + MOCK_METHOD0(OnRenderError, void()); + + // Toggles only filling half the requested amount during Render(). + void set_half_fill(bool half_fill) { half_fill_ = half_fill; } + + float fill_value() { return fill_value_; } + + // Generates a fill value between [-1, 1). NextFillValue() is deterministic + // based on fill_value_. Which means it will generate the same sequence of + // fill values every time unless |fill_value_| is set using set_fill_value(). + void NextFillValue(); + + private: + bool half_fill_; + float fill_value_; + AudioParameters audio_parameters_; + + DISALLOW_COPY_AND_ASSIGN(FakeAudioRenderCallback); +}; + +} // namespace media + +#endif // MEDIA_BASE_FAKE_AUDIO_RENDER_CALLBACK_H_ diff --git a/media/media.gyp b/media/media.gyp index 7ac3890..29300fe 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -129,6 +129,10 @@ 'base/audio_decoder_config.cc', 'base/audio_decoder_config.h', 'base/audio_renderer.h', + 'base/audio_renderer_mixer.cc', + 'base/audio_renderer_mixer.h', + 'base/audio_renderer_mixer_input.cc', + 'base/audio_renderer_mixer_input.h', 'base/bitstream_buffer.h', 'base/buffers.cc', 'base/buffers.h', @@ -659,12 +663,16 @@ 'audio/win/audio_low_latency_input_win_unittest.cc', 'audio/win/audio_low_latency_output_win_unittest.cc', 'audio/win/audio_output_win_unittest.cc', + 'base/audio_renderer_mixer_unittest.cc', + 'base/audio_renderer_mixer_input_unittest.cc', 'base/buffers_unittest.cc', 'base/clock_unittest.cc', 'base/composite_filter_unittest.cc', 'base/data_buffer_unittest.cc', 'base/decoder_buffer_unittest.cc', 'base/djb2_unittest.cc', + 'base/fake_audio_render_callback.cc', + 'base/fake_audio_render_callback.h', 'base/filter_collection_unittest.cc', 'base/h264_bitstream_converter_unittest.cc', 'base/pipeline_unittest.cc', |