Home | History | Annotate | Download | only in base
      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/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 
     21 namespace media {
     22 
     23 // Command line switch for runtime adjustment of benchmark iterations.
     24 static const char kBenchmarkIterations[] = "audio-converter-iterations";
     25 static const int kDefaultIterations = 10;
     26 
     27 // Parameters which control the many input case tests.
     28 static const int kConvertInputs = 8;
     29 static const int kConvertCycles = 3;
     30 
     31 // Parameters used for testing.
     32 static const int kBitsPerChannel = 32;
     33 static const ChannelLayout kChannelLayout = CHANNEL_LAYOUT_STEREO;
     34 static const int kHighLatencyBufferSize = 2048;
     35 static const int kLowLatencyBufferSize = 256;
     36 static const int kSampleRate = 48000;
     37 
     38 // Number of full sine wave cycles for each Render() call.
     39 static const int kSineCycles = 4;
     40 
     41 // Tuple of <input rate, output rate, output channel layout, epsilon>.
     42 typedef std::tr1::tuple<int, int, ChannelLayout, double> AudioConverterTestData;
     43 class 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.
    204 TEST(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.
    238 class 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.
    252 TEST(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 
    338 TEST_P(AudioConverterTest, NoInputs) {
    339   FillAudioData(1.0f);
    340   EXPECT_TRUE(RenderAndValidateAudioData(0.0f));
    341 }
    342 
    343 TEST_P(AudioConverterTest, OneInput) {
    344   RunTest(1);
    345 }
    346 
    347 TEST_P(AudioConverterTest, ManyInputs) {
    348   RunTest(kConvertInputs);
    349 }
    350 
    351 INSTANTIATE_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