audio_converter_unittest.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5// MSVC++ requires this to be set before any other includes to get M_PI. 6#define _USE_MATH_DEFINES 7 8#include <cmath> 9 10#include "base/command_line.h" 11#include "base/logging.h" 12#include "base/memory/scoped_ptr.h" 13#include "base/memory/scoped_vector.h" 14#include "base/string_number_conversions.h" 15#include "base/time.h" 16#include "media/base/audio_converter.h" 17#include "media/base/fake_audio_render_callback.h" 18#include "testing/gmock/include/gmock/gmock.h" 19#include "testing/gtest/include/gtest/gtest.h" 20 21namespace media { 22 23// Command line switch for runtime adjustment of benchmark iterations. 24static const char kBenchmarkIterations[] = "audio-converter-iterations"; 25static const int kDefaultIterations = 10; 26 27// Parameters which control the many input case tests. 28static const int kConvertInputs = 8; 29static const int kConvertCycles = 3; 30 31// Parameters used for testing. 32static const int kBitsPerChannel = 32; 33static const ChannelLayout kChannelLayout = CHANNEL_LAYOUT_STEREO; 34static const int kHighLatencyBufferSize = 2048; 35static const int kLowLatencyBufferSize = 256; 36static const int kSampleRate = 48000; 37 38// Number of full sine wave cycles for each Render() call. 39static const int kSineCycles = 4; 40 41// Tuple of <input rate, output rate, output channel layout, epsilon>. 42typedef std::tr1::tuple<int, int, ChannelLayout, double> AudioConverterTestData; 43class AudioConverterTest 44 : public testing::TestWithParam<AudioConverterTestData> { 45 public: 46 AudioConverterTest() 47 : epsilon_(std::tr1::get<3>(GetParam())) { 48 // Create input and output parameters based on test parameters. 49 input_parameters_ = AudioParameters( 50 AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout, 51 std::tr1::get<0>(GetParam()), kBitsPerChannel, kHighLatencyBufferSize); 52 output_parameters_ = AudioParameters( 53 AudioParameters::AUDIO_PCM_LOW_LATENCY, std::tr1::get<2>(GetParam()), 54 std::tr1::get<1>(GetParam()), 16, kLowLatencyBufferSize); 55 56 converter_.reset(new AudioConverter( 57 input_parameters_, output_parameters_, false)); 58 59 audio_bus_ = AudioBus::Create(output_parameters_); 60 expected_audio_bus_ = AudioBus::Create(output_parameters_); 61 62 // Allocate one callback for generating expected results. 63 double step = kSineCycles / static_cast<double>( 64 output_parameters_.frames_per_buffer()); 65 expected_callback_.reset(new FakeAudioRenderCallback(step)); 66 } 67 68 // Creates |count| input callbacks to be used for conversion testing. 69 void InitializeInputs(int count) { 70 // Setup FakeAudioRenderCallback step to compensate for resampling. 71 double scale_factor = input_parameters_.sample_rate() / 72 static_cast<double>(output_parameters_.sample_rate()); 73 double step = kSineCycles / (scale_factor * 74 static_cast<double>(output_parameters_.frames_per_buffer())); 75 76 for (int i = 0; i < count; ++i) { 77 fake_callbacks_.push_back(new FakeAudioRenderCallback(step)); 78 converter_->AddInput(fake_callbacks_[i]); 79 } 80 } 81 82 // Resets all input callbacks to a pristine state. 83 void Reset() { 84 converter_->Reset(); 85 for (size_t i = 0; i < fake_callbacks_.size(); ++i) 86 fake_callbacks_[i]->reset(); 87 expected_callback_->reset(); 88 } 89 90 // Sets the volume on all input callbacks to |volume|. 91 void SetVolume(float volume) { 92 for (size_t i = 0; i < fake_callbacks_.size(); ++i) 93 fake_callbacks_[i]->set_volume(volume); 94 } 95 96 // Validates audio data between |audio_bus_| and |expected_audio_bus_| from 97 // |index|..|frames| after |scale| is applied to the expected audio data. 98 bool ValidateAudioData(int index, int frames, float scale) { 99 for (int i = 0; i < audio_bus_->channels(); ++i) { 100 for (int j = index; j < frames; ++j) { 101 double error = fabs(audio_bus_->channel(i)[j] - 102 expected_audio_bus_->channel(i)[j] * scale); 103 if (error > epsilon_) { 104 EXPECT_NEAR(expected_audio_bus_->channel(i)[j] * scale, 105 audio_bus_->channel(i)[j], epsilon_) 106 << " i=" << i << ", j=" << j; 107 return false; 108 } 109 } 110 } 111 return true; 112 } 113 114 // Runs a single Convert() stage, fills |expected_audio_bus_| appropriately, 115 // and validates equality with |audio_bus_| after |scale| is applied. 116 bool RenderAndValidateAudioData(float scale) { 117 // Render actual audio data. 118 converter_->Convert(audio_bus_.get()); 119 120 // Render expected audio data. 121 expected_callback_->Render(expected_audio_bus_.get(), 0); 122 123 // Zero out unused channels in the expected AudioBus just as AudioConverter 124 // would during channel mixing. 125 for (int i = input_parameters_.channels(); 126 i < output_parameters_.channels(); ++i) { 127 memset(expected_audio_bus_->channel(i), 0, 128 audio_bus_->frames() * sizeof(*audio_bus_->channel(i))); 129 } 130 131 return ValidateAudioData(0, audio_bus_->frames(), scale); 132 } 133 134 // Fills |audio_bus_| fully with |value|. 135 void FillAudioData(float value) { 136 for (int i = 0; i < audio_bus_->channels(); ++i) { 137 std::fill(audio_bus_->channel(i), 138 audio_bus_->channel(i) + audio_bus_->frames(), value); 139 } 140 } 141 142 // Verifies converter output with a |inputs| number of transform inputs. 143 void RunTest(int inputs) { 144 InitializeInputs(inputs); 145 146 SetVolume(0); 147 for (int i = 0; i < kConvertCycles; ++i) 148 ASSERT_TRUE(RenderAndValidateAudioData(0)); 149 150 Reset(); 151 152 // Set a different volume for each input and verify the results. 153 float total_scale = 0; 154 for (size_t i = 0; i < fake_callbacks_.size(); ++i) { 155 float volume = static_cast<float>(i) / fake_callbacks_.size(); 156 total_scale += volume; 157 fake_callbacks_[i]->set_volume(volume); 158 } 159 for (int i = 0; i < kConvertCycles; ++i) 160 ASSERT_TRUE(RenderAndValidateAudioData(total_scale)); 161 162 Reset(); 163 164 // Remove every other input. 165 for (size_t i = 1; i < fake_callbacks_.size(); i += 2) 166 converter_->RemoveInput(fake_callbacks_[i]); 167 168 SetVolume(1); 169 float scale = inputs > 1 ? inputs / 2.0f : inputs; 170 for (int i = 0; i < kConvertCycles; ++i) 171 ASSERT_TRUE(RenderAndValidateAudioData(scale)); 172 } 173 174 protected: 175 virtual ~AudioConverterTest() {} 176 177 // Converter under test. 178 scoped_ptr<AudioConverter> converter_; 179 180 // Input and output parameters used for AudioConverter construction. 181 AudioParameters input_parameters_; 182 AudioParameters output_parameters_; 183 184 // Destination AudioBus for AudioConverter output. 185 scoped_ptr<AudioBus> audio_bus_; 186 187 // AudioBus containing expected results for comparison with |audio_bus_|. 188 scoped_ptr<AudioBus> expected_audio_bus_; 189 190 // Vector of all input callbacks used to drive AudioConverter::Convert(). 191 ScopedVector<FakeAudioRenderCallback> fake_callbacks_; 192 193 // Parallel input callback which generates the expected output. 194 scoped_ptr<FakeAudioRenderCallback> expected_callback_; 195 196 // Epsilon value with which to perform comparisons between |audio_bus_| and 197 // |expected_audio_bus_|. 198 double epsilon_; 199 200 DISALLOW_COPY_AND_ASSIGN(AudioConverterTest); 201}; 202 203// Ensure the buffer delay provided by AudioConverter is accurate. 204TEST(AudioConverterTest, AudioDelay) { 205 // Choose input and output parameters such that the transform must make 206 // multiple calls to fill the buffer. 207 AudioParameters input_parameters = AudioParameters( 208 AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout, kSampleRate, 209 kBitsPerChannel, kLowLatencyBufferSize); 210 AudioParameters output_parameters = AudioParameters( 211 AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout, kSampleRate * 2, 212 kBitsPerChannel, kHighLatencyBufferSize); 213 214 AudioConverter converter(input_parameters, output_parameters, false); 215 FakeAudioRenderCallback callback(0.2); 216 scoped_ptr<AudioBus> audio_bus = AudioBus::Create(output_parameters); 217 converter.AddInput(&callback); 218 converter.Convert(audio_bus.get()); 219 220 // Calculate the expected buffer delay for given AudioParameters. 221 double input_sample_rate = input_parameters.sample_rate(); 222 int fill_count = 223 (output_parameters.frames_per_buffer() * input_sample_rate / 224 output_parameters.sample_rate()) / input_parameters.frames_per_buffer(); 225 226 base::TimeDelta input_frame_duration = base::TimeDelta::FromMicroseconds( 227 base::Time::kMicrosecondsPerSecond / input_sample_rate); 228 229 int expected_last_delay_milliseconds = 230 fill_count * input_parameters.frames_per_buffer() * 231 input_frame_duration.InMillisecondsF(); 232 233 EXPECT_EQ(expected_last_delay_milliseconds, 234 callback.last_audio_delay_milliseconds()); 235} 236 237// Benchmark for audio conversion. Original benchmarks were run with 238// --audio-converter-iterations=50000. 239TEST(AudioConverterTest, ConvertBenchmark) { 240 int benchmark_iterations = kDefaultIterations; 241 std::string iterations(CommandLine::ForCurrentProcess()->GetSwitchValueASCII( 242 kBenchmarkIterations)); 243 base::StringToInt(iterations, &benchmark_iterations); 244 if (benchmark_iterations < kDefaultIterations) 245 benchmark_iterations = kDefaultIterations; 246 247 // Create input and output parameters to convert between the two most common 248 // sets of parameters (as indicated via UMA data). 249 AudioParameters input_params( 250 AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO, 48000, 16, 2048); 251 AudioParameters output_params( 252 AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO, 44100, 16, 440); 253 scoped_ptr<AudioConverter> converter( 254 new AudioConverter(input_params, output_params, false)); 255 256 scoped_ptr<AudioBus> output_bus = AudioBus::Create(output_params); 257 FakeAudioRenderCallback fake_input1(0.2); 258 FakeAudioRenderCallback fake_input2(0.4); 259 FakeAudioRenderCallback fake_input3(0.6); 260 converter->AddInput(&fake_input1); 261 converter->AddInput(&fake_input2); 262 converter->AddInput(&fake_input3); 263 264 printf("Benchmarking %d iterations:\n", benchmark_iterations); 265 266 // Benchmark Convert() w/ FIFO. 267 base::TimeTicks start = base::TimeTicks::HighResNow(); 268 for (int i = 0; i < benchmark_iterations; ++i) { 269 converter->Convert(output_bus.get()); 270 } 271 double total_time_ms = 272 (base::TimeTicks::HighResNow() - start).InMillisecondsF(); 273 printf("Convert() w/ FIFO took %.2fms.\n", total_time_ms); 274 275 converter.reset(new AudioConverter(input_params, output_params, true)); 276 converter->AddInput(&fake_input1); 277 converter->AddInput(&fake_input2); 278 converter->AddInput(&fake_input3); 279 280 // Benchmark Convert() w/o FIFO. 281 start = base::TimeTicks::HighResNow(); 282 for (int i = 0; i < benchmark_iterations; ++i) { 283 converter->Convert(output_bus.get()); 284 } 285 total_time_ms = 286 (base::TimeTicks::HighResNow() - start).InMillisecondsF(); 287 printf("Convert() w/o FIFO took %.2fms.\n", total_time_ms); 288} 289 290TEST_P(AudioConverterTest, NoInputs) { 291 FillAudioData(1.0f); 292 EXPECT_TRUE(RenderAndValidateAudioData(0.0f)); 293} 294 295TEST_P(AudioConverterTest, OneInput) { 296 RunTest(1); 297} 298 299TEST_P(AudioConverterTest, ManyInputs) { 300 RunTest(kConvertInputs); 301} 302 303INSTANTIATE_TEST_CASE_P( 304 AudioConverterTest, AudioConverterTest, testing::Values( 305 // No resampling. No channel mixing. 306 std::tr1::make_tuple(44100, 44100, CHANNEL_LAYOUT_STEREO, 0.00000048), 307 308 // Upsampling. Channel upmixing. 309 std::tr1::make_tuple(44100, 48000, CHANNEL_LAYOUT_QUAD, 0.033), 310 311 // Downsampling. Channel downmixing. 312 std::tr1::make_tuple(48000, 41000, CHANNEL_LAYOUT_MONO, 0.042))); 313 314} // namespace media 315