Chromium Code Reviews| Index: media/base/audio_renderer_mixer_unittest.cc |
| diff --git a/media/base/audio_renderer_mixer_unittest.cc b/media/base/audio_renderer_mixer_unittest.cc |
| index e897bbf9399c8216fa94faf227c1d56a6e390a37..fc2267df130b64e864860069c7d19a696481fdba 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 AudioRendererMixerTestCase |
|
scherkus (not reviewing)
2012/07/14 01:45:02
this should be ARMTest, not ARMTestCase
the only
DaleCurtis
2012/07/14 02:10:03
Done.
|
| + : 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(); |
| - } |
| + AudioRendererMixerTestCase() |
| + : 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(&AudioRendererMixerTestCase::GetMixer, |
| + base::Unretained(this)), |
| + base::Bind(&AudioRendererMixerTestCase::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(); |
| @@ -270,90 +281,95 @@ class AudioRendererMixerTest : public ::testing::Test { |
| } |
| protected: |
| - virtual ~AudioRendererMixerTest() { |
| + virtual ~AudioRendererMixerTestCase() { |
| for (size_t i = 0; i < audio_data_.size(); ++i) |
| delete [] audio_data_[i]; |
| } |
| 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); |
| + DISALLOW_COPY_AND_ASSIGN(AudioRendererMixerTestCase); |
| }; |
| // Verify a mixer with no inputs returns silence for all requested frames. |
| -TEST_F(AudioRendererMixerTest, NoInputs) { |
| +TEST_P(AudioRendererMixerTestCase, 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(AudioRendererMixerTestCase, OneInputStart) { |
| StartTest(1); |
| } |
| // Test mixer output with many inputs in the pre-Start() and post-Start() state. |
| -TEST_F(AudioRendererMixerTest, ManyInputStart) { |
| +TEST_P(AudioRendererMixerTestCase, ManyInputStart) { |
| StartTest(kMixerInputs); |
| } |
| // Test mixer output with one input in the post-Play() state. |
| -TEST_F(AudioRendererMixerTest, OneInputPlay) { |
| +TEST_P(AudioRendererMixerTestCase, OneInputPlay) { |
| PlayTest(1); |
| } |
| // Test mixer output with many inputs in the post-Play() state. |
| -TEST_F(AudioRendererMixerTest, ManyInputPlay) { |
| +TEST_P(AudioRendererMixerTestCase, ManyInputPlay) { |
| PlayTest(kMixerInputs); |
| } |
| // Test volume adjusted mixer output with one input in the post-Play() state. |
| -TEST_F(AudioRendererMixerTest, OneInputPlayVolumeAdjusted) { |
| +TEST_P(AudioRendererMixerTestCase, OneInputPlayVolumeAdjusted) { |
| PlayVolumeAdjustedTest(1); |
| } |
| // Test volume adjusted mixer output with many inputs in the post-Play() state. |
| -TEST_F(AudioRendererMixerTest, ManyInputPlayVolumeAdjusted) { |
| +TEST_P(AudioRendererMixerTestCase, ManyInputPlayVolumeAdjusted) { |
| PlayVolumeAdjustedTest(kMixerInputs); |
| } |
| // Test mixer output with one input and partial Render() in post-Play() state. |
| -TEST_F(AudioRendererMixerTest, OneInputPlayPartialRender) { |
| +TEST_P(AudioRendererMixerTestCase, OneInputPlayPartialRender) { |
| PlayPartialRenderTest(1); |
| } |
| // Test mixer output with many inputs and partial Render() in post-Play() state. |
| -TEST_F(AudioRendererMixerTest, ManyInputPlayPartialRender) { |
| +TEST_P(AudioRendererMixerTestCase, ManyInputPlayPartialRender) { |
| PlayPartialRenderTest(kMixerInputs); |
| } |
| // Test mixer output with one input in the post-Pause() state. |
| -TEST_F(AudioRendererMixerTest, OneInputPause) { |
| +TEST_P(AudioRendererMixerTestCase, OneInputPause) { |
| PauseTest(1); |
| } |
| // Test mixer output with many inputs in the post-Pause() state. |
| -TEST_F(AudioRendererMixerTest, ManyInputPause) { |
| +TEST_P(AudioRendererMixerTestCase, ManyInputPause) { |
| PauseTest(kMixerInputs); |
| } |
| // Test mixer output with one input in the post-Stop() state. |
| -TEST_F(AudioRendererMixerTest, OneInputStop) { |
| +TEST_P(AudioRendererMixerTestCase, OneInputStop) { |
| StopTest(1); |
| } |
| // Test mixer output with many inputs in the post-Stop() state. |
| -TEST_F(AudioRendererMixerTest, ManyInputStop) { |
| +TEST_P(AudioRendererMixerTestCase, ManyInputStop) { |
| StopTest(kMixerInputs); |
| } |
| // Test mixer with many inputs in mixed post-Stop() and post-Play() states. |
| -TEST_F(AudioRendererMixerTest, ManyInputMixedStopPlay) { |
| +TEST_P(AudioRendererMixerTestCase, 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(AudioRendererMixerTestCase, 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, AudioRendererMixerTestCase, 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 |