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