diff options
author | dalecurtis@chromium.org <dalecurtis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-06-28 22:42:05 +0000 |
---|---|---|
committer | dalecurtis@chromium.org <dalecurtis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-06-28 22:42:05 +0000 |
commit | 7953ef8eea2001287dbd233421226ad3f0e16448 (patch) | |
tree | 542fc1849b60665e2a3df6e4295ffaa2f0093ece | |
parent | 57ec6e9bd7de2a4c9cb1c795a1fc913f984d5098 (diff) | |
download | chromium_src-7953ef8eea2001287dbd233421226ad3f0e16448.zip chromium_src-7953ef8eea2001287dbd233421226ad3f0e16448.tar.gz chromium_src-7953ef8eea2001287dbd233421226ad3f0e16448.tar.bz2 |
Lays the base work and initial unit tests for renderer side audio mixing! Right
now nothing except unit tests are plumbed since mixing has poor performance with
the large buffer sizes we're currently using; I.e., for a 140ms clip we attempt
to render the full 170ms buffer, adding 30ms of overhead to the pipeline.
Once another CL introduces resampling we can switch over to the low latency path
for audio and turn on mixing at the same time. Chris also has some other ideas
on how we might fix this prior to introducing resampling.
Design revolves around each renderer creating an AudioRendererMixerInput which
funnels into a common AudioRendererMixer (based on the AudioParameters each
AudioRendererMixerInput is configured with). Each AudioRendererMixer owns a
single AudioDevice which output from each AudioRendererMixerInput is mixed into.
As mentioned above, for the initial landing of this CL the code which creates an
AudioRendererMixerInput in RenderAudioSourceProvider has been disabled. Please
look at https://chromiumcodereview.appspot.com/10636036/ if you're interested in
what this looks like.
BUG=133637
TEST=unit tests. audio_latency_perf. drum machine. manual testing. asan.
Review URL: https://chromiumcodereview.appspot.com/10544130
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@144819 0039d316-1c4b-4281-b951-d872f2087c98
-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', |