diff options
Diffstat (limited to 'media')
-rw-r--r-- | media/base/audio_renderer_mixer.cc | 85 | ||||
-rw-r--r-- | media/base/audio_renderer_mixer.h | 35 | ||||
-rw-r--r-- | media/base/audio_renderer_mixer_input.cc | 23 | ||||
-rw-r--r-- | media/base/audio_renderer_mixer_input.h | 29 | ||||
-rw-r--r-- | media/base/audio_renderer_mixer_input_unittest.cc | 65 | ||||
-rw-r--r-- | media/base/audio_renderer_mixer_unittest.cc | 306 | ||||
-rw-r--r-- | media/base/fake_audio_render_callback.cc | 29 | ||||
-rw-r--r-- | media/base/fake_audio_render_callback.h | 23 | ||||
-rw-r--r-- | media/base/mock_audio_renderer_sink.cc | 17 | ||||
-rw-r--r-- | media/base/mock_audio_renderer_sink.h | 41 | ||||
-rw-r--r-- | media/filters/audio_renderer_impl_unittest.cc | 23 | ||||
-rw-r--r-- | media/media.gyp | 6 |
12 files changed, 394 insertions, 288 deletions
diff --git a/media/base/audio_renderer_mixer.cc b/media/base/audio_renderer_mixer.cc index 5b89aa4..6d23faa 100644 --- a/media/base/audio_renderer_mixer.cc +++ b/media/base/audio_renderer_mixer.cc @@ -4,17 +4,35 @@ #include "media/base/audio_renderer_mixer.h" +#include "base/bind.h" +#include "base/bind_helpers.h" #include "base/logging.h" +#include "media/audio/audio_util.h" +#include "media/base/limits.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); + const AudioParameters& input_params, const AudioParameters& output_params, + const scoped_refptr<AudioRendererSink>& sink) + : audio_sink_(sink), + current_audio_delay_milliseconds_(0) { + // Sanity check sample rates. + DCHECK_LE(input_params.sample_rate(), limits::kMaxSampleRate); + DCHECK_GE(input_params.sample_rate(), limits::kMinSampleRate); + DCHECK_LE(output_params.sample_rate(), limits::kMaxSampleRate); + DCHECK_GE(output_params.sample_rate(), limits::kMinSampleRate); + + // Only resample if necessary since it's expensive. + if (input_params.sample_rate() != output_params.sample_rate()) { + resampler_.reset(new MultiChannelResampler( + output_params.channels(), + input_params.sample_rate() / static_cast<double>( + output_params.sample_rate()), + base::Bind(&AudioRendererMixer::ProvideInput, base::Unretained(this)))); + } + + audio_sink_->Initialize(output_params, this); audio_sink_->Start(); } @@ -22,6 +40,11 @@ AudioRendererMixer::~AudioRendererMixer() { // AudioRendererSinks must be stopped before being destructed. audio_sink_->Stop(); + // Clean up |mixer_input_audio_data_|. + for (size_t i = 0; i < mixer_input_audio_data_.size(); ++i) + delete [] mixer_input_audio_data_[i]; + mixer_input_audio_data_.clear(); + // Ensures that all mixer inputs have stopped themselves prior to destruction // and have called RemoveMixerInput(). DCHECK_EQ(mixer_inputs_.size(), 0U); @@ -42,11 +65,40 @@ void AudioRendererMixer::RemoveMixerInput( int AudioRendererMixer::Render(const std::vector<float*>& audio_data, int number_of_frames, int audio_delay_milliseconds) { + current_audio_delay_milliseconds_ = audio_delay_milliseconds; + + if (resampler_.get()) + resampler_->Resample(audio_data, number_of_frames); + else + ProvideInput(audio_data, number_of_frames); + + // Always return the full number of frames requested, ProvideInput() will pad + // with silence if it wasn't able to acquire enough data. + return number_of_frames; +} + +void AudioRendererMixer::ProvideInput(const std::vector<float*>& audio_data, + int number_of_frames) { base::AutoLock auto_lock(mixer_inputs_lock_); + // Allocate staging area for each mixer input's audio data on first call. We + // won't know how much to allocate until here because of resampling. + if (mixer_input_audio_data_.size() == 0) { + // 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. + mixer_input_audio_data_.reserve(audio_data.size()); + for (size_t i = 0; i < audio_data.size(); ++i) + mixer_input_audio_data_.push_back(new float[number_of_frames]); + mixer_input_audio_data_size_ = number_of_frames; + } + + // Sanity check our inputs. + DCHECK_LE(number_of_frames, mixer_input_audio_data_size_); + DCHECK_EQ(audio_data.size(), mixer_input_audio_data_.size()); + // 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) + for (size_t i = 0; i < audio_data.size(); ++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. @@ -57,24 +109,21 @@ int AudioRendererMixer::Render(const std::vector<float*>& audio_data, 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) + // Nothing to do if the input isn't playing. + if (!input->playing()) 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); + mixer_input_audio_data_, number_of_frames, + current_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) { + for (size_t j = 0; j < audio_data.size(); ++j) { float* dest = audio_data[j]; - float* source = mixer_input_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); } @@ -82,10 +131,6 @@ int AudioRendererMixer::Render(const std::vector<float*>& audio_data, // 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() { diff --git a/media/base/audio_renderer_mixer.h b/media/base/audio_renderer_mixer.h index 32102ff..76e68bc 100644 --- a/media/base/audio_renderer_mixer.h +++ b/media/base/audio_renderer_mixer.h @@ -8,32 +8,30 @@ #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" +#include "media/base/multi_channel_resampler.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. +// side resources. Resampling is done post-mixing as it is the most expensive +// process. If the input sample rate matches the audio hardware sample rate, no +// resampling is done. class MEDIA_EXPORT AudioRendererMixer - : public base::RefCountedThreadSafe<AudioRendererMixer>, - NON_EXPORTED_BASE(public AudioRendererSink::RenderCallback) { + : NON_EXPORTED_BASE(public AudioRendererSink::RenderCallback) { public: - AudioRendererMixer(const AudioParameters& params, + AudioRendererMixer(const AudioParameters& input_params, + const AudioParameters& output_params, const scoped_refptr<AudioRendererSink>& sink); + virtual ~AudioRendererMixer(); // 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, @@ -41,8 +39,11 @@ class MEDIA_EXPORT AudioRendererMixer int audio_delay_milliseconds) OVERRIDE; virtual void OnRenderError() OVERRIDE; - // AudioParameters this mixer was constructed with. - AudioParameters audio_parameters_; + // Handles mixing and volume adjustment. Renders |number_of_frames| into + // |audio_data|. When resampling is necessary, ProvideInput() will be called + // by MultiChannelResampler when more data is necessary. + void ProvideInput(const std::vector<float*>& audio_data, + int number_of_frames); // Output sink for this mixer. scoped_refptr<AudioRendererSink> audio_sink_; @@ -54,6 +55,16 @@ class MEDIA_EXPORT AudioRendererMixer AudioRendererMixerInputSet mixer_inputs_; base::Lock mixer_inputs_lock_; + // Vector for rendering audio data from each mixer input. + int mixer_input_audio_data_size_; + std::vector<float*> mixer_input_audio_data_; + + // Handles resampling post-mixing. + scoped_ptr<MultiChannelResampler> resampler_; + + // The audio delay in milliseconds received by the last Render() call. + int current_audio_delay_milliseconds_; + DISALLOW_COPY_AND_ASSIGN(AudioRendererMixer); }; diff --git a/media/base/audio_renderer_mixer_input.cc b/media/base/audio_renderer_mixer_input.cc index 8eef689..ec0c019 100644 --- a/media/base/audio_renderer_mixer_input.cc +++ b/media/base/audio_renderer_mixer_input.cc @@ -10,35 +10,26 @@ namespace media { AudioRendererMixerInput::AudioRendererMixerInput( - const scoped_refptr<AudioRendererMixer>& mixer) + const GetMixerCB& get_mixer_cb, const RemoveMixerCB& remove_mixer_cb) : playing_(false), initialized_(false), volume_(1.0f), - mixer_(mixer), + get_mixer_cb_(get_mixer_cb), + remove_mixer_cb_(remove_mixer_cb), callback_(NULL) { } 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(); + // Mixer is no longer safe to use after |remove_mixer_cb_| has been called. + remove_mixer_cb_.Run(params_); } 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()]); - + params_ = params; + mixer_ = get_mixer_cb_.Run(params_); callback_ = callback; initialized_ = true; } diff --git a/media/base/audio_renderer_mixer_input.h b/media/base/audio_renderer_mixer_input.h index 7e8c9f1..5813a97 100644 --- a/media/base/audio_renderer_mixer_input.h +++ b/media/base/audio_renderer_mixer_input.h @@ -7,6 +7,7 @@ #include <vector> +#include "base/callback.h" #include "media/base/audio_renderer_sink.h" namespace media { @@ -16,12 +17,13 @@ class AudioRendererMixer; class MEDIA_EXPORT AudioRendererMixerInput : NON_EXPORTED_BASE(public AudioRendererSink) { public: - explicit AudioRendererMixerInput( - const scoped_refptr<AudioRendererMixer>& mixer); + typedef base::Callback<AudioRendererMixer*( + const AudioParameters& params)> GetMixerCB; + typedef base::Callback<void(const AudioParameters& params)> RemoveMixerCB; + + AudioRendererMixerInput( + const GetMixerCB& get_mixer_cb, const RemoveMixerCB& remove_mixer_cb); - // 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_; } @@ -43,16 +45,21 @@ class MEDIA_EXPORT AudioRendererMixerInput 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_; + // Callbacks provided during construction which allow AudioRendererMixerInput + // to retrieve a mixer during Initialize() and notify when it's done with it. + GetMixerCB get_mixer_cb_; + RemoveMixerCB remove_mixer_cb_; + + // AudioParameters received during Initialize(). + AudioParameters params_; + + // AudioRendererMixer provided through |get_mixer_cb_| during Initialize(), + // guaranteed to live (at least) until |remove_mixer_cb_| is called. + 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); }; diff --git a/media/base/audio_renderer_mixer_input_unittest.cc b/media/base/audio_renderer_mixer_input_unittest.cc index b57b7cc..48b2232 100644 --- a/media/base/audio_renderer_mixer_input_unittest.cc +++ b/media/base/audio_renderer_mixer_input_unittest.cc @@ -2,11 +2,12 @@ // 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 "base/bind.h" +#include "base/bind_helpers.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 "media/base/mock_audio_renderer_sink.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -14,46 +15,56 @@ namespace media { static const int kBitsPerChannel = 16; static const int kSampleRate = 48000; +static const int kBufferSize = 8192; static const ChannelLayout kChannelLayout = CHANNEL_LAYOUT_STEREO; -class AudioRendererMixerInputTest : public ::testing::Test { +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_)); + kBitsPerChannel, kBufferSize); + + mixer_input_ = new AudioRendererMixerInput( + base::Bind( + &AudioRendererMixerInputTest::GetMixer, base::Unretained(this)), + base::Bind( + &AudioRendererMixerInputTest::RemoveMixer, base::Unretained(this))); + fake_callback_.reset(new FakeAudioRenderCallback(0)); mixer_input_->Initialize(audio_parameters_, fake_callback_.get()); + EXPECT_CALL(*this, RemoveMixer(testing::_)); } - // 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()); + AudioRendererMixer* GetMixer(const AudioParameters& params) { + if (!mixer_.get()) { + scoped_refptr<MockAudioRendererSink> sink = new MockAudioRendererSink(); + EXPECT_CALL(*sink, Start()); + EXPECT_CALL(*sink, Stop()); - 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]); + mixer_.reset(new AudioRendererMixer( + audio_parameters_, audio_parameters_, sink)); + } + return mixer_.get(); } + MOCK_METHOD1(RemoveMixer, void(const AudioParameters&)); + protected: virtual ~AudioRendererMixerInputTest() {} AudioParameters audio_parameters_; - scoped_refptr<AudioRendererMixer> mixer_; + scoped_ptr<AudioRendererMixer> mixer_; scoped_refptr<AudioRendererMixerInput> mixer_input_; scoped_ptr<FakeAudioRenderCallback> fake_callback_; DISALLOW_COPY_AND_ASSIGN(AudioRendererMixerInputTest); }; +// Test callback() works as expected. +TEST_F(AudioRendererMixerInputTest, GetCallback) { + EXPECT_EQ(mixer_input_->callback(), fake_callback_.get()); +} + // Test that getting and setting the volume work as expected. TEST_F(AudioRendererMixerInputTest, GetSetVolume) { // Starting volume should be 0. @@ -67,20 +78,6 @@ TEST_F(AudioRendererMixerInputTest, GetSetVolume) { 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. diff --git a/media/base/audio_renderer_mixer_unittest.cc b/media/base/audio_renderer_mixer_unittest.cc index e897bbf..9f46dc1 100644 --- a/media/base/audio_renderer_mixer_unittest.cc +++ b/media/base/audio_renderer_mixer_unittest.cc @@ -2,99 +2,117 @@ // 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 <cmath> +#include "base/bind.h" +#include "base/bind_helpers.h" #include "base/memory/scoped_ptr.h" -#include "media/audio/audio_util.h" +#include "base/memory/scoped_vector.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 "media/base/mock_audio_renderer_sink.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 kMixerInputs = 8; +static const int kMixerCycles = 3; +// Parameters used for testing. static const int kBitsPerChannel = 16; -static const int kSampleRate = 48000; static const ChannelLayout kChannelLayout = CHANNEL_LAYOUT_STEREO; +static const int kHighLatencyBufferSize = 8192; +static const int kLowLatencyBufferSize = 256; -// 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; +// Number of full sine wave cycles for each Render() call. +static const int kSineCycles = 4; -class MockAudioRendererSink : public AudioRendererSink { +// Tuple of <input sampling rate, output sampling rate, epsilon>. +typedef std::tr1::tuple<int, int, double> AudioRendererMixerTestData; +class AudioRendererMixerTest + : public testing::TestWithParam<AudioRendererMixerTestData> { 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(); - } + AudioRendererMixerTest() + : epsilon_(std::tr1::get<2>(GetParam())), + half_fill_(false) { + // Create input and output parameters based on test parameters. + input_parameters_ = AudioParameters( + AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout, + std::tr1::get<0>(GetParam()), kBitsPerChannel, kHighLatencyBufferSize); + output_parameters_ = AudioParameters( + AudioParameters::AUDIO_PCM_LOW_LATENCY, kChannelLayout, + std::tr1::get<1>(GetParam()), 16, kLowLatencyBufferSize); - 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_.reset(new AudioRendererMixer( + input_parameters_, output_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()]); + audio_data_.reserve(output_parameters_.channels()); + for (int i = 0; i < output_parameters_.channels(); ++i) + audio_data_.push_back(new float[output_parameters_.frames_per_buffer()]); + + // 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. + expected_audio_data_.reserve(output_parameters_.channels()); + for (int i = 0; i < output_parameters_.channels(); ++i) { + expected_audio_data_.push_back( + new float[output_parameters_.frames_per_buffer()]); + } + + // Allocate one callback for generating expected results. + double step = kSineCycles / static_cast<double>( + output_parameters_.frames_per_buffer()); + expected_callback_.reset(new FakeAudioRenderCallback(step)); + } - fake_callback_.reset(new FakeAudioRenderCallback(audio_parameters_)); + AudioRendererMixer* GetMixer(const AudioParameters& params) { + return mixer_.get(); } + MOCK_METHOD1(RemoveMixer, void(const AudioParameters&)); + void InitializeInputs(int count) { + mixer_inputs_.reserve(count); + fake_callbacks_.reserve(count); + + // Setup FakeAudioRenderCallback step to compensate for resampling. + double scale_factor = input_parameters_.sample_rate() + / static_cast<double>(output_parameters_.sample_rate()); + double step = kSineCycles / (scale_factor * + static_cast<double>(output_parameters_.frames_per_buffer())); + 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); + fake_callbacks_.push_back(new FakeAudioRenderCallback(step)); + mixer_inputs_.push_back(new AudioRendererMixerInput( + base::Bind(&AudioRendererMixerTest::GetMixer, + base::Unretained(this)), + base::Bind(&AudioRendererMixerTest::RemoveMixer, + base::Unretained(this)))); + mixer_inputs_[i]->Initialize(input_parameters_, fake_callbacks_[i]); + mixer_inputs_[i]->SetVolume(1.0f); } + EXPECT_CALL(*this, RemoveMixer(testing::_)).Times(count); } - bool ValidateAudioData(int start_index, int frames, float check_value) { + bool ValidateAudioData(int index, int frames, float scale) { 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) + for (int j = index; j < frames; j++) { + double error = fabs( + audio_data_[i][j] - expected_audio_data_[i][j] * scale); + if (error > epsilon_) { + EXPECT_NEAR( + expected_audio_data_[i][j] * scale, audio_data_[i][j], epsilon_) << " i=" << i << ", j=" << j; return false; } @@ -103,20 +121,47 @@ class AudioRendererMixerTest : public ::testing::Test { 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); + bool RenderAndValidateAudioData(float scale) { + int request_frames = output_parameters_.frames_per_buffer(); + + // Half fill won't be exactly half when resampling since the resampler + // will have enough data to fill out more of the buffer based on its + // internal buffer and kernel size. So special case some of the checks. + bool resampling = input_parameters_.sample_rate() + != output_parameters_.sample_rate(); + + if (half_fill_) { + for (size_t i = 0; i < fake_callbacks_.size(); ++i) + fake_callbacks_[i]->set_half_fill(true); + expected_callback_->set_half_fill(true); + } + + // Render actual audio data. + int frames = mixer_callback_->Render(audio_data_, request_frames, 0); + if (frames != request_frames) + return false; + + // Render expected audio data (without scaling). + expected_callback_->Render(expected_audio_data_, request_frames, 0); + + if (half_fill_) { + // Verify first half of audio data for both resampling and non-resampling. + if (!ValidateAudioData(0, frames / 2, scale)) + return false; + // Verify silence in the second half if we're not resampling. + if (!resampling) + return ValidateAudioData(frames / 2, frames, 0); + return true; + } else { + return ValidateAudioData(0, frames, scale); + } } // 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); + audio_data_[i] + output_parameters_.frames_per_buffer(), value); } // Verify silence when mixer inputs are in pre-Start() and post-Start(). @@ -148,27 +193,14 @@ class AudioRendererMixerTest : public ::testing::Test { void PlayTest(int inputs) { InitializeInputs(inputs); - for (size_t i = 0; i < mixer_inputs_.size(); ++i) + // Play() all mixer inputs and ensure we get the right values. + for (size_t i = 0; i < mixer_inputs_.size(); ++i) { mixer_inputs_[i]->Start(); - - // 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 (int i = 0; i < kMixerCycles; ++i) + ASSERT_TRUE(RenderAndValidateAudioData(mixer_inputs_.size())); for (size_t i = 0; i < mixer_inputs_.size(); ++i) mixer_inputs_[i]->Stop(); @@ -190,11 +222,8 @@ class AudioRendererMixerTest : public ::testing::Test { 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 (int i = 0; i < kMixerCycles; ++i) + ASSERT_TRUE(RenderAndValidateAudioData(total_scale)); for (size_t i = 0; i < mixer_inputs_.size(); ++i) mixer_inputs_[i]->Stop(); @@ -203,7 +232,6 @@ class AudioRendererMixerTest : public ::testing::Test { // 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(); @@ -211,16 +239,8 @@ class AudioRendererMixerTest : public ::testing::Test { } // 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); + half_fill_ = true; + ASSERT_TRUE(RenderAndValidateAudioData(mixer_inputs_.size())); for (size_t i = 0; i < mixer_inputs_.size(); ++i) mixer_inputs_[i]->Stop(); @@ -238,17 +258,8 @@ class AudioRendererMixerTest : public ::testing::Test { // 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 (int i = 0; i < kMixerCycles; ++i) + ASSERT_TRUE(RenderAndValidateAudioData(mixer_inputs_.size() / 2)); for (size_t i = 0; i < mixer_inputs_.size(); ++i) mixer_inputs_[i]->Stop(); @@ -276,84 +287,89 @@ class AudioRendererMixerTest : public ::testing::Test { } scoped_refptr<MockAudioRendererSink> sink_; - scoped_refptr<AudioRendererMixer> mixer_; + scoped_ptr<AudioRendererMixer> mixer_; AudioRendererSink::RenderCallback* mixer_callback_; - scoped_ptr<FakeAudioRenderCallback> fake_callback_; - AudioParameters audio_parameters_; + AudioParameters input_parameters_; + AudioParameters output_parameters_; std::vector<float*> audio_data_; + std::vector<float*> expected_audio_data_; std::vector< scoped_refptr<AudioRendererMixerInput> > mixer_inputs_; + ScopedVector<FakeAudioRenderCallback> fake_callbacks_; + scoped_ptr<FakeAudioRenderCallback> expected_callback_; + double epsilon_; + bool half_fill_; DISALLOW_COPY_AND_ASSIGN(AudioRendererMixerTest); }; // Verify a mixer with no inputs returns silence for all requested frames. -TEST_F(AudioRendererMixerTest, NoInputs) { +TEST_P(AudioRendererMixerTest, NoInputs) { FillAudioData(1.0f); EXPECT_TRUE(RenderAndValidateAudioData(0.0f)); } // Test mixer output with one input in the pre-Start() and post-Start() state. -TEST_F(AudioRendererMixerTest, OneInputStart) { +TEST_P(AudioRendererMixerTest, OneInputStart) { StartTest(1); } // Test mixer output with many inputs in the pre-Start() and post-Start() state. -TEST_F(AudioRendererMixerTest, ManyInputStart) { +TEST_P(AudioRendererMixerTest, ManyInputStart) { StartTest(kMixerInputs); } // Test mixer output with one input in the post-Play() state. -TEST_F(AudioRendererMixerTest, OneInputPlay) { +TEST_P(AudioRendererMixerTest, OneInputPlay) { PlayTest(1); } // Test mixer output with many inputs in the post-Play() state. -TEST_F(AudioRendererMixerTest, ManyInputPlay) { +TEST_P(AudioRendererMixerTest, ManyInputPlay) { PlayTest(kMixerInputs); } // Test volume adjusted mixer output with one input in the post-Play() state. -TEST_F(AudioRendererMixerTest, OneInputPlayVolumeAdjusted) { +TEST_P(AudioRendererMixerTest, OneInputPlayVolumeAdjusted) { PlayVolumeAdjustedTest(1); } // Test volume adjusted mixer output with many inputs in the post-Play() state. -TEST_F(AudioRendererMixerTest, ManyInputPlayVolumeAdjusted) { +TEST_P(AudioRendererMixerTest, ManyInputPlayVolumeAdjusted) { PlayVolumeAdjustedTest(kMixerInputs); } // Test mixer output with one input and partial Render() in post-Play() state. -TEST_F(AudioRendererMixerTest, OneInputPlayPartialRender) { +TEST_P(AudioRendererMixerTest, OneInputPlayPartialRender) { PlayPartialRenderTest(1); } // Test mixer output with many inputs and partial Render() in post-Play() state. -TEST_F(AudioRendererMixerTest, ManyInputPlayPartialRender) { +TEST_P(AudioRendererMixerTest, ManyInputPlayPartialRender) { PlayPartialRenderTest(kMixerInputs); } // Test mixer output with one input in the post-Pause() state. -TEST_F(AudioRendererMixerTest, OneInputPause) { +TEST_P(AudioRendererMixerTest, OneInputPause) { PauseTest(1); } // Test mixer output with many inputs in the post-Pause() state. -TEST_F(AudioRendererMixerTest, ManyInputPause) { +TEST_P(AudioRendererMixerTest, ManyInputPause) { PauseTest(kMixerInputs); } // Test mixer output with one input in the post-Stop() state. -TEST_F(AudioRendererMixerTest, OneInputStop) { +TEST_P(AudioRendererMixerTest, OneInputStop) { StopTest(1); } // Test mixer output with many inputs in the post-Stop() state. -TEST_F(AudioRendererMixerTest, ManyInputStop) { +TEST_P(AudioRendererMixerTest, ManyInputStop) { StopTest(kMixerInputs); } // Test mixer with many inputs in mixed post-Stop() and post-Play() states. -TEST_F(AudioRendererMixerTest, ManyInputMixedStopPlay) { +TEST_P(AudioRendererMixerTest, ManyInputMixedStopPlay) { InitializeInputs(kMixerInputs); // Start() all inputs. @@ -366,32 +382,34 @@ TEST_F(AudioRendererMixerTest, ManyInputMixedStopPlay) { 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)))); - } + ASSERT_TRUE(RenderAndValidateAudioData(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); +TEST_P(AudioRendererMixerTest, OnRenderError) { + InitializeInputs(kMixerInputs); + for (size_t i = 0; i < mixer_inputs_.size(); ++i) { + mixer_inputs_[i]->Start(); + EXPECT_CALL(*fake_callbacks_[i], OnRenderError()).Times(1); } - EXPECT_CALL(*fake_callback_, OnRenderError()).Times(kMixerInputs); - sink_->SimulateRenderError(); - for (int i = 0; i < kMixerInputs; ++i) + mixer_callback_->OnRenderError(); + for (size_t i = 0; i < mixer_inputs_.size(); ++i) mixer_inputs_[i]->Stop(); } +INSTANTIATE_TEST_CASE_P( + AudioRendererMixerTest, AudioRendererMixerTest, testing::Values( + // No resampling. + std::tr1::make_tuple(44100, 44100, 0.000000477), + + // Upsampling. + std::tr1::make_tuple(44100, 48000, 0.0329405), + + // Downsampling. + std::tr1::make_tuple(48000, 41000, 0.0410239))); + } // namespace media diff --git a/media/base/fake_audio_render_callback.cc b/media/base/fake_audio_render_callback.cc index 3fc711b4..15986fd 100644 --- a/media/base/fake_audio_render_callback.cc +++ b/media/base/fake_audio_render_callback.cc @@ -11,13 +11,10 @@ namespace media { -// Arbitrarily chosen prime value for the fake random number generator. -static const int kFakeRandomSeed = 9440671; - -FakeAudioRenderCallback::FakeAudioRenderCallback(const AudioParameters& params) +FakeAudioRenderCallback::FakeAudioRenderCallback(double step) : half_fill_(false), - fill_value_(1.0f), - audio_parameters_(params) { + step_(step) { + reset(); } FakeAudioRenderCallback::~FakeAudioRenderCallback() {} @@ -27,18 +24,18 @@ int FakeAudioRenderCallback::Render(const std::vector<float*>& audio_data, 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; -} + // Fill first channel with a sine wave. + for (int i = 0; i < number_of_frames; ++i) + audio_data[0][i] = sin(2 * M_PI * (x_ + step_ * i)); + x_ += number_of_frames * step_; -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; + // Copy first channel into the rest of the channels. + for (size_t i = 1; i < audio_data.size(); ++i) + memcpy(audio_data[i], audio_data[0], + number_of_frames * sizeof(*audio_data[0])); + + return number_of_frames; } } // namespace media diff --git a/media/base/fake_audio_render_callback.h b/media/base/fake_audio_render_callback.h index f80c942..b1a4e44 100644 --- a/media/base/fake_audio_render_callback.h +++ b/media/base/fake_audio_render_callback.h @@ -7,19 +7,22 @@ #include <vector> -#include "base/time.h" #include "media/base/audio_renderer_sink.h" #include "testing/gmock/include/gmock/gmock.h" namespace media { +// Fake RenderCallback which will fill each request with a sine wave. Sine +// state is kept across callbacks. State can be reset to default via reset(). class FakeAudioRenderCallback : public AudioRendererSink::RenderCallback { public: - // Initializes |fill_value_| to a random value seeded by current time. - explicit FakeAudioRenderCallback(const AudioParameters& params); + // The function used to fulfill Render() is f(x) = sin(2 * PI * x * |step|), + // where x = [|number_of_frames| * m, |number_of_frames| * (m + 1)] and m = + // the number of Render() calls fulfilled thus far. + explicit FakeAudioRenderCallback(double step); virtual ~FakeAudioRenderCallback(); - // Renders |fill_value_| into the provided audio data buffer. If |half_fill_| + // Renders a sine wave 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; @@ -28,17 +31,13 @@ class FakeAudioRenderCallback : public AudioRendererSink::RenderCallback { // 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(); + // Reset the sine state to initial value. + void reset() { x_ = 0; } private: bool half_fill_; - float fill_value_; - AudioParameters audio_parameters_; + double x_; + double step_; DISALLOW_COPY_AND_ASSIGN(FakeAudioRenderCallback); }; diff --git a/media/base/mock_audio_renderer_sink.cc b/media/base/mock_audio_renderer_sink.cc new file mode 100644 index 0000000..b21eb19 --- /dev/null +++ b/media/base/mock_audio_renderer_sink.cc @@ -0,0 +1,17 @@ +// 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/mock_audio_renderer_sink.h" + +namespace media { + +MockAudioRendererSink::MockAudioRendererSink() {} +MockAudioRendererSink::~MockAudioRendererSink() {} + +void MockAudioRendererSink::Initialize(const AudioParameters& params, + RenderCallback* renderer) { + callback_ = renderer; +} + +} // namespace media diff --git a/media/base/mock_audio_renderer_sink.h b/media/base/mock_audio_renderer_sink.h new file mode 100644 index 0000000..903d1fc --- /dev/null +++ b/media/base/mock_audio_renderer_sink.h @@ -0,0 +1,41 @@ +// 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_MOCK_AUDIO_RENDERER_SINK_H_ +#define MEDIA_BASE_MOCK_AUDIO_RENDERER_SINK_H_ + +#include "media/audio/audio_parameters.h" +#include "media/base/audio_renderer_sink.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace media { + +class MockAudioRendererSink : public AudioRendererSink { + public: + MockAudioRendererSink(); + + 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)); + + virtual void Initialize(const AudioParameters& params, + RenderCallback* renderer) OVERRIDE; + AudioRendererSink::RenderCallback* callback() { return callback_; } + + protected: + virtual ~MockAudioRendererSink(); + + private: + RenderCallback* callback_; + + DISALLOW_COPY_AND_ASSIGN(MockAudioRendererSink); +}; + +} // namespace media + +#endif // MEDIA_BASE_MOCK_AUDIO_RENDERER_SINK_H_ diff --git a/media/filters/audio_renderer_impl_unittest.cc b/media/filters/audio_renderer_impl_unittest.cc index 1633087..909bbb0 100644 --- a/media/filters/audio_renderer_impl_unittest.cc +++ b/media/filters/audio_renderer_impl_unittest.cc @@ -6,6 +6,7 @@ #include "base/gtest_prod_util.h" #include "base/stl_util.h" #include "media/base/data_buffer.h" +#include "media/base/mock_audio_renderer_sink.h" #include "media/base/mock_callback.h" #include "media/base/mock_filter_host.h" #include "media/base/mock_filters.h" @@ -19,26 +20,6 @@ using ::testing::Return; using ::testing::NiceMock; using ::testing::StrictMock; -namespace { - -class MockAudioSink : public media::AudioRendererSink { - public: - MOCK_METHOD2(Initialize, void(const media::AudioParameters& params, - RenderCallback* callback)); - 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)); - - protected: - virtual ~MockAudioSink() {} -}; - -} // namespace - namespace media { // Constants for distinguishing between muted audio and playing audio when using @@ -50,7 +31,7 @@ class AudioRendererImplTest : public ::testing::Test { public: // Give the decoder some non-garbage media properties. AudioRendererImplTest() - : renderer_(new AudioRendererImpl(new NiceMock<MockAudioSink>())), + : renderer_(new AudioRendererImpl(new NiceMock<MockAudioRendererSink>())), decoder_(new MockAudioDecoder()) { renderer_->SetHost(&host_); diff --git a/media/media.gyp b/media/media.gyp index 54bca0a..d0f889b 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -678,8 +678,6 @@ '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/multi_channel_resampler_unittest.cc', 'base/pipeline_unittest.cc', @@ -793,6 +791,10 @@ 'audio/mock_audio_manager.h', 'audio/test_audio_input_controller_factory.cc', 'audio/test_audio_input_controller_factory.h', + 'base/fake_audio_render_callback.cc', + 'base/fake_audio_render_callback.h', + 'base/mock_audio_renderer_sink.cc', + 'base/mock_audio_renderer_sink.h', 'base/mock_callback.cc', 'base/mock_callback.h', 'base/mock_data_source_host.cc', |