summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
Diffstat (limited to 'media')
-rw-r--r--media/base/audio_renderer_mixer.cc101
-rw-r--r--media/base/audio_renderer_mixer.h62
-rw-r--r--media/base/audio_renderer_mixer_input.cc75
-rw-r--r--media/base/audio_renderer_mixer_input.h61
-rw-r--r--media/base/audio_renderer_mixer_input_unittest.cc100
-rw-r--r--media/base/audio_renderer_mixer_unittest.cc397
-rw-r--r--media/base/fake_audio_render_callback.cc44
-rw-r--r--media/base/fake_audio_render_callback.h48
-rw-r--r--media/media.gyp8
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',