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/bind.h"
     11 #include "base/bind_helpers.h"
     12 #include "base/command_line.h"
     13 #include "base/cpu.h"
     14 #include "base/logging.h"
     15 #include "base/strings/string_number_conversions.h"
     16 #include "base/strings/stringize_macros.h"
     17 #include "base/time/time.h"
     18 #include "build/build_config.h"
     19 #include "media/base/sinc_resampler.h"
     20 #include "testing/gmock/include/gmock/gmock.h"
     21 #include "testing/gtest/include/gtest/gtest.h"
     22 
     23 using testing::_;
     24 
     25 namespace media {
     26 
     27 static const double kSampleRateRatio = 192000.0 / 44100.0;
     28 static const double kKernelInterpolationFactor = 0.5;
     29 
     30 // Command line switch for runtime adjustment of ConvolveBenchmark iterations.
     31 static const char kConvolveIterations[] = "convolve-iterations";
     32 
     33 // Helper class to ensure ChunkedResample() functions properly.
     34 class MockSource {
     35  public:
     36   MOCK_METHOD2(ProvideInput, void(int frames, float* destination));
     37 };
     38 
     39 ACTION(ClearBuffer) {
     40   memset(arg1, 0, arg0 * sizeof(float));
     41 }
     42 
     43 ACTION(FillBuffer) {
     44   // Value chosen arbitrarily such that SincResampler resamples it to something
     45   // easily representable on all platforms; e.g., using kSampleRateRatio this
     46   // becomes 1.81219.
     47   memset(arg1, 64, arg0 * sizeof(float));
     48 }
     49 
     50 // Test requesting multiples of ChunkSize() frames results in the proper number
     51 // of callbacks.
     52 TEST(SincResamplerTest, ChunkedResample) {
     53   MockSource mock_source;
     54 
     55   // Choose a high ratio of input to output samples which will result in quick
     56   // exhaustion of SincResampler's internal buffers.
     57   SincResampler resampler(
     58       kSampleRateRatio, SincResampler::kDefaultRequestSize,
     59       base::Bind(&MockSource::ProvideInput, base::Unretained(&mock_source)));
     60 
     61   static const int kChunks = 2;
     62   int max_chunk_size = resampler.ChunkSize() * kChunks;
     63   scoped_ptr<float[]> resampled_destination(new float[max_chunk_size]);
     64 
     65   // Verify requesting ChunkSize() frames causes a single callback.
     66   EXPECT_CALL(mock_source, ProvideInput(_, _))
     67       .Times(1).WillOnce(ClearBuffer());
     68   resampler.Resample(resampler.ChunkSize(), resampled_destination.get());
     69 
     70   // Verify requesting kChunks * ChunkSize() frames causes kChunks callbacks.
     71   testing::Mock::VerifyAndClear(&mock_source);
     72   EXPECT_CALL(mock_source, ProvideInput(_, _))
     73       .Times(kChunks).WillRepeatedly(ClearBuffer());
     74   resampler.Resample(max_chunk_size, resampled_destination.get());
     75 }
     76 
     77 // Test flush resets the internal state properly.
     78 TEST(SincResamplerTest, Flush) {
     79   MockSource mock_source;
     80   SincResampler resampler(
     81       kSampleRateRatio, SincResampler::kDefaultRequestSize,
     82       base::Bind(&MockSource::ProvideInput, base::Unretained(&mock_source)));
     83   scoped_ptr<float[]> resampled_destination(new float[resampler.ChunkSize()]);
     84 
     85   // Fill the resampler with junk data.
     86   EXPECT_CALL(mock_source, ProvideInput(_, _))
     87       .Times(1).WillOnce(FillBuffer());
     88   resampler.Resample(resampler.ChunkSize() / 2, resampled_destination.get());
     89   ASSERT_NE(resampled_destination[0], 0);
     90 
     91   // Flush and request more data, which should all be zeros now.
     92   resampler.Flush();
     93   testing::Mock::VerifyAndClear(&mock_source);
     94   EXPECT_CALL(mock_source, ProvideInput(_, _))
     95       .Times(1).WillOnce(ClearBuffer());
     96   resampler.Resample(resampler.ChunkSize() / 2, resampled_destination.get());
     97   for (int i = 0; i < resampler.ChunkSize() / 2; ++i)
     98     ASSERT_FLOAT_EQ(resampled_destination[i], 0);
     99 }
    100 
    101 // Test flush resets the internal state properly.
    102 TEST(SincResamplerTest, DISABLED_SetRatioBench) {
    103   MockSource mock_source;
    104   SincResampler resampler(
    105       kSampleRateRatio, SincResampler::kDefaultRequestSize,
    106       base::Bind(&MockSource::ProvideInput, base::Unretained(&mock_source)));
    107 
    108   base::TimeTicks start = base::TimeTicks::HighResNow();
    109   for (int i = 1; i < 10000; ++i)
    110     resampler.SetRatio(1.0 / i);
    111   double total_time_c_ms =
    112       (base::TimeTicks::HighResNow() - start).InMillisecondsF();
    113   printf("SetRatio() took %.2fms.\n", total_time_c_ms);
    114 }
    115 
    116 
    117 // Define platform independent function name for Convolve* tests.
    118 #if defined(ARCH_CPU_X86_FAMILY)
    119 #define CONVOLVE_FUNC Convolve_SSE
    120 #elif defined(ARCH_CPU_ARM_FAMILY) && defined(USE_NEON)
    121 #define CONVOLVE_FUNC Convolve_NEON
    122 #endif
    123 
    124 // Ensure various optimized Convolve() methods return the same value.  Only run
    125 // this test if other optimized methods exist, otherwise the default Convolve()
    126 // will be tested by the parameterized SincResampler tests below.
    127 #if defined(CONVOLVE_FUNC)
    128 TEST(SincResamplerTest, Convolve) {
    129 #if defined(ARCH_CPU_X86_FAMILY)
    130   ASSERT_TRUE(base::CPU().has_sse());
    131 #endif
    132 
    133   // Initialize a dummy resampler.
    134   MockSource mock_source;
    135   SincResampler resampler(
    136       kSampleRateRatio, SincResampler::kDefaultRequestSize,
    137       base::Bind(&MockSource::ProvideInput, base::Unretained(&mock_source)));
    138 
    139   // The optimized Convolve methods are slightly more precise than Convolve_C(),
    140   // so comparison must be done using an epsilon.
    141   static const double kEpsilon = 0.00000005;
    142 
    143   // Use a kernel from SincResampler as input and kernel data, this has the
    144   // benefit of already being properly sized and aligned for Convolve_SSE().
    145   double result = resampler.Convolve_C(
    146       resampler.kernel_storage_.get(), resampler.kernel_storage_.get(),
    147       resampler.kernel_storage_.get(), kKernelInterpolationFactor);
    148   double result2 = resampler.CONVOLVE_FUNC(
    149       resampler.kernel_storage_.get(), resampler.kernel_storage_.get(),
    150       resampler.kernel_storage_.get(), kKernelInterpolationFactor);
    151   EXPECT_NEAR(result2, result, kEpsilon);
    152 
    153   // Test Convolve() w/ unaligned input pointer.
    154   result = resampler.Convolve_C(
    155       resampler.kernel_storage_.get() + 1, resampler.kernel_storage_.get(),
    156       resampler.kernel_storage_.get(), kKernelInterpolationFactor);
    157   result2 = resampler.CONVOLVE_FUNC(
    158       resampler.kernel_storage_.get() + 1, resampler.kernel_storage_.get(),
    159       resampler.kernel_storage_.get(), kKernelInterpolationFactor);
    160   EXPECT_NEAR(result2, result, kEpsilon);
    161 }
    162 #endif
    163 
    164 // Benchmark for the various Convolve() methods.  Make sure to build with
    165 // branding=Chrome so that DCHECKs are compiled out when benchmarking.  Original
    166 // benchmarks were run with --convolve-iterations=50000000.
    167 TEST(SincResamplerTest, ConvolveBenchmark) {
    168   // Initialize a dummy resampler.
    169   MockSource mock_source;
    170   SincResampler resampler(
    171       kSampleRateRatio, SincResampler::kDefaultRequestSize,
    172       base::Bind(&MockSource::ProvideInput, base::Unretained(&mock_source)));
    173 
    174   // Retrieve benchmark iterations from command line.
    175   int convolve_iterations = 10;
    176   std::string iterations(CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
    177       kConvolveIterations));
    178   if (!iterations.empty())
    179     base::StringToInt(iterations, &convolve_iterations);
    180 
    181   printf("Benchmarking %d iterations:\n", convolve_iterations);
    182 
    183   // Benchmark Convolve_C().
    184   base::TimeTicks start = base::TimeTicks::HighResNow();
    185   for (int i = 0; i < convolve_iterations; ++i) {
    186     resampler.Convolve_C(
    187         resampler.kernel_storage_.get(), resampler.kernel_storage_.get(),
    188         resampler.kernel_storage_.get(), kKernelInterpolationFactor);
    189   }
    190   double total_time_c_ms =
    191       (base::TimeTicks::HighResNow() - start).InMillisecondsF();
    192   printf("Convolve_C took %.2fms.\n", total_time_c_ms);
    193 
    194 #if defined(CONVOLVE_FUNC)
    195 #if defined(ARCH_CPU_X86_FAMILY)
    196   ASSERT_TRUE(base::CPU().has_sse());
    197 #endif
    198 
    199   // Benchmark with unaligned input pointer.
    200   start = base::TimeTicks::HighResNow();
    201   for (int j = 0; j < convolve_iterations; ++j) {
    202     resampler.CONVOLVE_FUNC(
    203         resampler.kernel_storage_.get() + 1, resampler.kernel_storage_.get(),
    204         resampler.kernel_storage_.get(), kKernelInterpolationFactor);
    205   }
    206   double total_time_optimized_unaligned_ms =
    207       (base::TimeTicks::HighResNow() - start).InMillisecondsF();
    208   printf(STRINGIZE(CONVOLVE_FUNC) " (unaligned) took %.2fms; which is %.2fx "
    209          "faster than Convolve_C.\n", total_time_optimized_unaligned_ms,
    210          total_time_c_ms / total_time_optimized_unaligned_ms);
    211 
    212   // Benchmark with aligned input pointer.
    213   start = base::TimeTicks::HighResNow();
    214   for (int j = 0; j < convolve_iterations; ++j) {
    215     resampler.CONVOLVE_FUNC(
    216         resampler.kernel_storage_.get(), resampler.kernel_storage_.get(),
    217         resampler.kernel_storage_.get(), kKernelInterpolationFactor);
    218   }
    219   double total_time_optimized_aligned_ms =
    220       (base::TimeTicks::HighResNow() - start).InMillisecondsF();
    221   printf(STRINGIZE(CONVOLVE_FUNC) " (aligned) took %.2fms; which is %.2fx "
    222          "faster than Convolve_C and %.2fx faster than "
    223          STRINGIZE(CONVOLVE_FUNC) " (unaligned).\n",
    224          total_time_optimized_aligned_ms,
    225          total_time_c_ms / total_time_optimized_aligned_ms,
    226          total_time_optimized_unaligned_ms / total_time_optimized_aligned_ms);
    227 #endif
    228 }
    229 
    230 #undef CONVOLVE_FUNC
    231 
    232 // Fake audio source for testing the resampler.  Generates a sinusoidal linear
    233 // chirp (http://en.wikipedia.org/wiki/Chirp) which can be tuned to stress the
    234 // resampler for the specific sample rate conversion being used.
    235 class SinusoidalLinearChirpSource {
    236  public:
    237   SinusoidalLinearChirpSource(int sample_rate,
    238                               int samples,
    239                               double max_frequency)
    240       : sample_rate_(sample_rate),
    241         total_samples_(samples),
    242         max_frequency_(max_frequency),
    243         current_index_(0) {
    244     // Chirp rate.
    245     double duration = static_cast<double>(total_samples_) / sample_rate_;
    246     k_ = (max_frequency_ - kMinFrequency) / duration;
    247   }
    248 
    249   virtual ~SinusoidalLinearChirpSource() {}
    250 
    251   void ProvideInput(int frames, float* destination) {
    252     for (int i = 0; i < frames; ++i, ++current_index_) {
    253       // Filter out frequencies higher than Nyquist.
    254       if (Frequency(current_index_) > 0.5 * sample_rate_) {
    255         destination[i] = 0;
    256       } else {
    257         // Calculate time in seconds.
    258         double t = static_cast<double>(current_index_) / sample_rate_;
    259 
    260         // Sinusoidal linear chirp.
    261         destination[i] = sin(2 * M_PI * (kMinFrequency * t + (k_ / 2) * t * t));
    262       }
    263     }
    264   }
    265 
    266   double Frequency(int position) {
    267     return kMinFrequency + position * (max_frequency_ - kMinFrequency)
    268         / total_samples_;
    269   }
    270 
    271  private:
    272   enum {
    273     kMinFrequency = 5
    274   };
    275 
    276   double sample_rate_;
    277   int total_samples_;
    278   double max_frequency_;
    279   double k_;
    280   int current_index_;
    281 
    282   DISALLOW_COPY_AND_ASSIGN(SinusoidalLinearChirpSource);
    283 };
    284 
    285 typedef std::tr1::tuple<int, int, double, double> SincResamplerTestData;
    286 class SincResamplerTest
    287     : public testing::TestWithParam<SincResamplerTestData> {
    288  public:
    289   SincResamplerTest()
    290       : input_rate_(std::tr1::get<0>(GetParam())),
    291         output_rate_(std::tr1::get<1>(GetParam())),
    292         rms_error_(std::tr1::get<2>(GetParam())),
    293         low_freq_error_(std::tr1::get<3>(GetParam())) {
    294   }
    295 
    296   virtual ~SincResamplerTest() {}
    297 
    298  protected:
    299   int input_rate_;
    300   int output_rate_;
    301   double rms_error_;
    302   double low_freq_error_;
    303 };
    304 
    305 // Tests resampling using a given input and output sample rate.
    306 TEST_P(SincResamplerTest, Resample) {
    307   // Make comparisons using one second of data.
    308   static const double kTestDurationSecs = 1;
    309   int input_samples = kTestDurationSecs * input_rate_;
    310   int output_samples = kTestDurationSecs * output_rate_;
    311 
    312   // Nyquist frequency for the input sampling rate.
    313   double input_nyquist_freq = 0.5 * input_rate_;
    314 
    315   // Source for data to be resampled.
    316   SinusoidalLinearChirpSource resampler_source(
    317       input_rate_, input_samples, input_nyquist_freq);
    318 
    319   const double io_ratio = input_rate_ / static_cast<double>(output_rate_);
    320   SincResampler resampler(
    321       io_ratio, SincResampler::kDefaultRequestSize,
    322       base::Bind(&SinusoidalLinearChirpSource::ProvideInput,
    323                  base::Unretained(&resampler_source)));
    324 
    325   // Force an update to the sample rate ratio to ensure dyanmic sample rate
    326   // changes are working correctly.
    327   scoped_ptr<float[]> kernel(new float[SincResampler::kKernelStorageSize]);
    328   memcpy(kernel.get(), resampler.get_kernel_for_testing(),
    329          SincResampler::kKernelStorageSize);
    330   resampler.SetRatio(M_PI);
    331   ASSERT_NE(0, memcmp(kernel.get(), resampler.get_kernel_for_testing(),
    332                       SincResampler::kKernelStorageSize));
    333   resampler.SetRatio(io_ratio);
    334   ASSERT_EQ(0, memcmp(kernel.get(), resampler.get_kernel_for_testing(),
    335                       SincResampler::kKernelStorageSize));
    336 
    337   // TODO(dalecurtis): If we switch to AVX/SSE optimization, we'll need to
    338   // allocate these on 32-byte boundaries and ensure they're sized % 32 bytes.
    339   scoped_ptr<float[]> resampled_destination(new float[output_samples]);
    340   scoped_ptr<float[]> pure_destination(new float[output_samples]);
    341 
    342   // Generate resampled signal.
    343   resampler.Resample(output_samples, resampled_destination.get());
    344 
    345   // Generate pure signal.
    346   SinusoidalLinearChirpSource pure_source(
    347       output_rate_, output_samples, input_nyquist_freq);
    348   pure_source.ProvideInput(output_samples, pure_destination.get());
    349 
    350   // Range of the Nyquist frequency (0.5 * min(input rate, output_rate)) which
    351   // we refer to as low and high.
    352   static const double kLowFrequencyNyquistRange = 0.7;
    353   static const double kHighFrequencyNyquistRange = 0.9;
    354 
    355   // Calculate Root-Mean-Square-Error and maximum error for the resampling.
    356   double sum_of_squares = 0;
    357   double low_freq_max_error = 0;
    358   double high_freq_max_error = 0;
    359   int minimum_rate = std::min(input_rate_, output_rate_);
    360   double low_frequency_range = kLowFrequencyNyquistRange * 0.5 * minimum_rate;
    361   double high_frequency_range = kHighFrequencyNyquistRange * 0.5 * minimum_rate;
    362   for (int i = 0; i < output_samples; ++i) {
    363     double error = fabs(resampled_destination[i] - pure_destination[i]);
    364 
    365     if (pure_source.Frequency(i) < low_frequency_range) {
    366       if (error > low_freq_max_error)
    367         low_freq_max_error = error;
    368     } else if (pure_source.Frequency(i) < high_frequency_range) {
    369       if (error > high_freq_max_error)
    370         high_freq_max_error = error;
    371     }
    372     // TODO(dalecurtis): Sanity check frequencies > kHighFrequencyNyquistRange.
    373 
    374     sum_of_squares += error * error;
    375   }
    376 
    377   double rms_error = sqrt(sum_of_squares / output_samples);
    378 
    379   // Convert each error to dbFS.
    380   #define DBFS(x) 20 * log10(x)
    381   rms_error = DBFS(rms_error);
    382   low_freq_max_error = DBFS(low_freq_max_error);
    383   high_freq_max_error = DBFS(high_freq_max_error);
    384 
    385   EXPECT_LE(rms_error, rms_error_);
    386   EXPECT_LE(low_freq_max_error, low_freq_error_);
    387 
    388   // All conversions currently have a high frequency error around -6 dbFS.
    389   static const double kHighFrequencyMaxError = -6.02;
    390   EXPECT_LE(high_freq_max_error, kHighFrequencyMaxError);
    391 }
    392 
    393 // Almost all conversions have an RMS error of around -14 dbFS.
    394 static const double kResamplingRMSError = -14.58;
    395 
    396 // Thresholds chosen arbitrarily based on what each resampling reported during
    397 // testing.  All thresholds are in dbFS, http://en.wikipedia.org/wiki/DBFS.
    398 INSTANTIATE_TEST_CASE_P(
    399     SincResamplerTest, SincResamplerTest, testing::Values(
    400         // To 44.1kHz
    401         std::tr1::make_tuple(8000, 44100, kResamplingRMSError, -62.73),
    402         std::tr1::make_tuple(11025, 44100, kResamplingRMSError, -72.19),
    403         std::tr1::make_tuple(16000, 44100, kResamplingRMSError, -62.54),
    404         std::tr1::make_tuple(22050, 44100, kResamplingRMSError, -73.53),
    405         std::tr1::make_tuple(32000, 44100, kResamplingRMSError, -63.32),
    406         std::tr1::make_tuple(44100, 44100, kResamplingRMSError, -73.53),
    407         std::tr1::make_tuple(48000, 44100, -15.01, -64.04),
    408         std::tr1::make_tuple(96000, 44100, -18.49, -25.51),
    409         std::tr1::make_tuple(192000, 44100, -20.50, -13.31),
    410 
    411         // To 48kHz
    412         std::tr1::make_tuple(8000, 48000, kResamplingRMSError, -63.43),
    413         std::tr1::make_tuple(11025, 48000, kResamplingRMSError, -62.61),
    414         std::tr1::make_tuple(16000, 48000, kResamplingRMSError, -63.96),
    415         std::tr1::make_tuple(22050, 48000, kResamplingRMSError, -62.42),
    416         std::tr1::make_tuple(32000, 48000, kResamplingRMSError, -64.04),
    417         std::tr1::make_tuple(44100, 48000, kResamplingRMSError, -62.63),
    418         std::tr1::make_tuple(48000, 48000, kResamplingRMSError, -73.52),
    419         std::tr1::make_tuple(96000, 48000, -18.40, -28.44),
    420         std::tr1::make_tuple(192000, 48000, -20.43, -14.11),
    421 
    422         // To 96kHz
    423         std::tr1::make_tuple(8000, 96000, kResamplingRMSError, -63.19),
    424         std::tr1::make_tuple(11025, 96000, kResamplingRMSError, -62.61),
    425         std::tr1::make_tuple(16000, 96000, kResamplingRMSError, -63.39),
    426         std::tr1::make_tuple(22050, 96000, kResamplingRMSError, -62.42),
    427         std::tr1::make_tuple(32000, 96000, kResamplingRMSError, -63.95),
    428         std::tr1::make_tuple(44100, 96000, kResamplingRMSError, -62.63),
    429         std::tr1::make_tuple(48000, 96000, kResamplingRMSError, -73.52),
    430         std::tr1::make_tuple(96000, 96000, kResamplingRMSError, -73.52),
    431         std::tr1::make_tuple(192000, 96000, kResamplingRMSError, -28.41),
    432 
    433         // To 192kHz
    434         std::tr1::make_tuple(8000, 192000, kResamplingRMSError, -63.10),
    435         std::tr1::make_tuple(11025, 192000, kResamplingRMSError, -62.61),
    436         std::tr1::make_tuple(16000, 192000, kResamplingRMSError, -63.14),
    437         std::tr1::make_tuple(22050, 192000, kResamplingRMSError, -62.42),
    438         std::tr1::make_tuple(32000, 192000, kResamplingRMSError, -63.38),
    439         std::tr1::make_tuple(44100, 192000, kResamplingRMSError, -62.63),
    440         std::tr1::make_tuple(48000, 192000, kResamplingRMSError, -73.44),
    441         std::tr1::make_tuple(96000, 192000, kResamplingRMSError, -73.52),
    442         std::tr1::make_tuple(192000, 192000, kResamplingRMSError, -73.52)));
    443 
    444 }  // namespace media
    445