audio_converter_unittest.cc revision 868fa2fe829687343ffae624259930155e16dbd8
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/strings/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// InputCallback that zero's out the provided AudioBus.  Used for benchmarking.
238class NullInputProvider : public AudioConverter::InputCallback {
239 public:
240  NullInputProvider() {}
241  virtual ~NullInputProvider() {}
242
243  virtual double ProvideInput(AudioBus* audio_bus,
244                              base::TimeDelta buffer_delay) OVERRIDE {
245    audio_bus->Zero();
246    return 1;
247  }
248};
249
250// Benchmark for audio conversion.  Original benchmarks were run with
251// --audio-converter-iterations=50000.
252TEST(AudioConverterTest, ConvertBenchmark) {
253  int benchmark_iterations = kDefaultIterations;
254  std::string iterations(CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
255      kBenchmarkIterations));
256  base::StringToInt(iterations, &benchmark_iterations);
257  if (benchmark_iterations < kDefaultIterations)
258    benchmark_iterations = kDefaultIterations;
259
260  NullInputProvider fake_input1;
261  NullInputProvider fake_input2;
262  NullInputProvider fake_input3;
263
264  printf("Benchmarking %d iterations:\n", benchmark_iterations);
265
266  {
267    // Create input and output parameters to convert between the two most common
268    // sets of parameters (as indicated via UMA data).
269    AudioParameters input_params(
270        AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO,
271        48000, 16, 2048);
272    AudioParameters output_params(
273        AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO,
274        44100, 16, 440);
275    scoped_ptr<AudioBus> output_bus = AudioBus::Create(output_params);
276
277    scoped_ptr<AudioConverter> converter(
278        new AudioConverter(input_params, output_params, true));
279    converter->AddInput(&fake_input1);
280    converter->AddInput(&fake_input2);
281    converter->AddInput(&fake_input3);
282
283    // Benchmark Convert() w/ FIFO.
284    base::TimeTicks start = base::TimeTicks::HighResNow();
285    for (int i = 0; i < benchmark_iterations; ++i) {
286      converter->Convert(output_bus.get());
287    }
288    double total_time_ms =
289        (base::TimeTicks::HighResNow() - start).InMillisecondsF();
290    printf("Convert() w/ Resampling took %.2fms.\n", total_time_ms);
291  }
292
293  // Create input and output parameters to convert between common buffer sizes
294  // without any resampling for the FIFO vs no FIFO benchmarks.
295  AudioParameters input_params(
296      AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO,
297      44100, 16, 2048);
298  AudioParameters output_params(
299      AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_STEREO,
300      44100, 16, 440);
301  scoped_ptr<AudioBus> output_bus = AudioBus::Create(output_params);
302
303  {
304    scoped_ptr<AudioConverter> converter(
305        new AudioConverter(input_params, output_params, false));
306    converter->AddInput(&fake_input1);
307    converter->AddInput(&fake_input2);
308    converter->AddInput(&fake_input3);
309
310    // Benchmark Convert() w/ FIFO.
311    base::TimeTicks start = base::TimeTicks::HighResNow();
312    for (int i = 0; i < benchmark_iterations; ++i) {
313      converter->Convert(output_bus.get());
314    }
315    double total_time_ms =
316        (base::TimeTicks::HighResNow() - start).InMillisecondsF();
317    printf("Convert() w/ FIFO took %.2fms.\n", total_time_ms);
318  }
319
320  {
321    scoped_ptr<AudioConverter> converter(
322        new AudioConverter(input_params, output_params, true));
323    converter->AddInput(&fake_input1);
324    converter->AddInput(&fake_input2);
325    converter->AddInput(&fake_input3);
326
327    // Benchmark Convert() w/o FIFO.
328    base::TimeTicks start = base::TimeTicks::HighResNow();
329    for (int i = 0; i < benchmark_iterations; ++i) {
330      converter->Convert(output_bus.get());
331    }
332    double total_time_ms =
333        (base::TimeTicks::HighResNow() - start).InMillisecondsF();
334    printf("Convert() w/o FIFO took %.2fms.\n", total_time_ms);
335  }
336}
337
338TEST_P(AudioConverterTest, NoInputs) {
339  FillAudioData(1.0f);
340  EXPECT_TRUE(RenderAndValidateAudioData(0.0f));
341}
342
343TEST_P(AudioConverterTest, OneInput) {
344  RunTest(1);
345}
346
347TEST_P(AudioConverterTest, ManyInputs) {
348  RunTest(kConvertInputs);
349}
350
351INSTANTIATE_TEST_CASE_P(
352    AudioConverterTest, AudioConverterTest, testing::Values(
353        // No resampling. No channel mixing.
354        std::tr1::make_tuple(44100, 44100, CHANNEL_LAYOUT_STEREO, 0.00000048),
355
356        // Upsampling. Channel upmixing.
357        std::tr1::make_tuple(44100, 48000, CHANNEL_LAYOUT_QUAD, 0.033),
358
359        // Downsampling. Channel downmixing.
360        std::tr1::make_tuple(48000, 41000, CHANNEL_LAYOUT_MONO, 0.042)));
361
362}  // namespace media
363