sinc_resampler_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/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/string_number_conversions.h"
16#include "base/strings/stringize_macros.h"
17#include "base/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
23using testing::_;
24
25namespace media {
26
27static const double kSampleRateRatio = 192000.0 / 44100.0;
28static const double kKernelInterpolationFactor = 0.5;
29
30// Command line switch for runtime adjustment of ConvolveBenchmark iterations.
31static const char kConvolveIterations[] = "convolve-iterations";
32
33// Helper class to ensure ChunkedResample() functions properly.
34class MockSource {
35 public:
36  MOCK_METHOD2(ProvideInput, void(float* destination, int frames));
37};
38
39ACTION(ClearBuffer) {
40  memset(arg0, 0, arg1 * sizeof(float));
41}
42
43ACTION(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(arg0, 64, arg1 * sizeof(float));
48}
49
50// Test requesting multiples of ChunkSize() frames results in the proper number
51// of callbacks.
52TEST(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,
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(resampled_destination.get(), resampler.ChunkSize());
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(resampled_destination.get(), max_chunk_size);
75}
76
77// Test flush resets the internal state properly.
78TEST(SincResamplerTest, Flush) {
79  MockSource mock_source;
80  SincResampler resampler(
81      kSampleRateRatio,
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(resampled_destination.get(), resampler.ChunkSize() / 2);
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(resampled_destination.get(), resampler.ChunkSize() / 2);
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.
102TEST(SincResamplerTest, DISABLED_SetRatioBench) {
103  MockSource mock_source;
104  SincResampler resampler(
105      kSampleRateRatio,
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)
128TEST(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,
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.
167TEST(SincResamplerTest, ConvolveBenchmark) {
168  // Initialize a dummy resampler.
169  MockSource mock_source;
170  SincResampler resampler(
171      kSampleRateRatio,
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.
235class SinusoidalLinearChirpSource {
236 public:
237  SinusoidalLinearChirpSource(int sample_rate, int samples,
238                              double max_frequency)
239      : sample_rate_(sample_rate),
240        total_samples_(samples),
241        max_frequency_(max_frequency),
242        current_index_(0) {
243    // Chirp rate.
244    double duration = static_cast<double>(total_samples_) / sample_rate_;
245    k_ = (max_frequency_ - kMinFrequency) / duration;
246  }
247
248  virtual ~SinusoidalLinearChirpSource() {}
249
250  void ProvideInput(float* destination, int frames) {
251    for (int i = 0; i < frames; ++i, ++current_index_) {
252      // Filter out frequencies higher than Nyquist.
253      if (Frequency(current_index_) > 0.5 * sample_rate_) {
254        destination[i] = 0;
255      } else {
256        // Calculate time in seconds.
257        double t = static_cast<double>(current_index_) / sample_rate_;
258
259        // Sinusoidal linear chirp.
260        destination[i] = sin(2 * M_PI * (kMinFrequency * t + (k_ / 2) * t * t));
261      }
262    }
263  }
264
265  double Frequency(int position) {
266    return kMinFrequency + position * (max_frequency_ - kMinFrequency)
267        / total_samples_;
268  }
269
270 private:
271  enum {
272    kMinFrequency = 5
273  };
274
275  double sample_rate_;
276  int total_samples_;
277  double max_frequency_;
278  double k_;
279  int current_index_;
280
281  DISALLOW_COPY_AND_ASSIGN(SinusoidalLinearChirpSource);
282};
283
284typedef std::tr1::tuple<int, int, double, double> SincResamplerTestData;
285class SincResamplerTest
286    : public testing::TestWithParam<SincResamplerTestData> {
287 public:
288  SincResamplerTest()
289      : input_rate_(std::tr1::get<0>(GetParam())),
290        output_rate_(std::tr1::get<1>(GetParam())),
291        rms_error_(std::tr1::get<2>(GetParam())),
292        low_freq_error_(std::tr1::get<3>(GetParam())) {
293  }
294
295  virtual ~SincResamplerTest() {}
296
297 protected:
298  int input_rate_;
299  int output_rate_;
300  double rms_error_;
301  double low_freq_error_;
302};
303
304// Tests resampling using a given input and output sample rate.
305TEST_P(SincResamplerTest, Resample) {
306  // Make comparisons using one second of data.
307  static const double kTestDurationSecs = 1;
308  int input_samples = kTestDurationSecs * input_rate_;
309  int output_samples = kTestDurationSecs * output_rate_;
310
311  // Nyquist frequency for the input sampling rate.
312  double input_nyquist_freq = 0.5 * input_rate_;
313
314  // Source for data to be resampled.
315  SinusoidalLinearChirpSource resampler_source(
316      input_rate_, input_samples, input_nyquist_freq);
317
318  const double io_ratio = input_rate_ / static_cast<double>(output_rate_);
319  SincResampler resampler(
320      io_ratio,
321      base::Bind(&SinusoidalLinearChirpSource::ProvideInput,
322                 base::Unretained(&resampler_source)));
323
324  // Force an update to the sample rate ratio to ensure dyanmic sample rate
325  // changes are working correctly.
326  scoped_ptr<float[]> kernel(new float[SincResampler::kKernelStorageSize]);
327  memcpy(kernel.get(), resampler.get_kernel_for_testing(),
328         SincResampler::kKernelStorageSize);
329  resampler.SetRatio(M_PI);
330  ASSERT_NE(0, memcmp(kernel.get(), resampler.get_kernel_for_testing(),
331                      SincResampler::kKernelStorageSize));
332  resampler.SetRatio(io_ratio);
333  ASSERT_EQ(0, memcmp(kernel.get(), resampler.get_kernel_for_testing(),
334                      SincResampler::kKernelStorageSize));
335
336  // TODO(dalecurtis): If we switch to AVX/SSE optimization, we'll need to
337  // allocate these on 32-byte boundaries and ensure they're sized % 32 bytes.
338  scoped_ptr<float[]> resampled_destination(new float[output_samples]);
339  scoped_ptr<float[]> pure_destination(new float[output_samples]);
340
341  // Generate resampled signal.
342  resampler.Resample(resampled_destination.get(), output_samples);
343
344  // Generate pure signal.
345  SinusoidalLinearChirpSource pure_source(
346      output_rate_, output_samples, input_nyquist_freq);
347  pure_source.ProvideInput(pure_destination.get(), output_samples);
348
349  // Range of the Nyquist frequency (0.5 * min(input rate, output_rate)) which
350  // we refer to as low and high.
351  static const double kLowFrequencyNyquistRange = 0.7;
352  static const double kHighFrequencyNyquistRange = 0.9;
353
354  // Calculate Root-Mean-Square-Error and maximum error for the resampling.
355  double sum_of_squares = 0;
356  double low_freq_max_error = 0;
357  double high_freq_max_error = 0;
358  int minimum_rate = std::min(input_rate_, output_rate_);
359  double low_frequency_range = kLowFrequencyNyquistRange * 0.5 * minimum_rate;
360  double high_frequency_range = kHighFrequencyNyquistRange * 0.5 * minimum_rate;
361  for (int i = 0; i < output_samples; ++i) {
362    double error = fabs(resampled_destination[i] - pure_destination[i]);
363
364    if (pure_source.Frequency(i) < low_frequency_range) {
365      if (error > low_freq_max_error)
366        low_freq_max_error = error;
367    } else if (pure_source.Frequency(i) < high_frequency_range) {
368      if (error > high_freq_max_error)
369        high_freq_max_error = error;
370    }
371    // TODO(dalecurtis): Sanity check frequencies > kHighFrequencyNyquistRange.
372
373    sum_of_squares += error * error;
374  }
375
376  double rms_error = sqrt(sum_of_squares / output_samples);
377
378  // Convert each error to dbFS.
379  #define DBFS(x) 20 * log10(x)
380  rms_error = DBFS(rms_error);
381  low_freq_max_error = DBFS(low_freq_max_error);
382  high_freq_max_error = DBFS(high_freq_max_error);
383
384  EXPECT_LE(rms_error, rms_error_);
385  EXPECT_LE(low_freq_max_error, low_freq_error_);
386
387  // All conversions currently have a high frequency error around -6 dbFS.
388  static const double kHighFrequencyMaxError = -6.02;
389  EXPECT_LE(high_freq_max_error, kHighFrequencyMaxError);
390}
391
392// Almost all conversions have an RMS error of around -14 dbFS.
393static const double kResamplingRMSError = -14.58;
394
395// Thresholds chosen arbitrarily based on what each resampling reported during
396// testing.  All thresholds are in dbFS, http://en.wikipedia.org/wiki/DBFS.
397INSTANTIATE_TEST_CASE_P(
398    SincResamplerTest, SincResamplerTest, testing::Values(
399        // To 44.1kHz
400        std::tr1::make_tuple(8000, 44100, kResamplingRMSError, -62.73),
401        std::tr1::make_tuple(11025, 44100, kResamplingRMSError, -72.19),
402        std::tr1::make_tuple(16000, 44100, kResamplingRMSError, -62.54),
403        std::tr1::make_tuple(22050, 44100, kResamplingRMSError, -73.53),
404        std::tr1::make_tuple(32000, 44100, kResamplingRMSError, -63.32),
405        std::tr1::make_tuple(44100, 44100, kResamplingRMSError, -73.53),
406        std::tr1::make_tuple(48000, 44100, -15.01, -64.04),
407        std::tr1::make_tuple(96000, 44100, -18.49, -25.51),
408        std::tr1::make_tuple(192000, 44100, -20.50, -13.31),
409
410        // To 48kHz
411        std::tr1::make_tuple(8000, 48000, kResamplingRMSError, -63.43),
412        std::tr1::make_tuple(11025, 48000, kResamplingRMSError, -62.61),
413        std::tr1::make_tuple(16000, 48000, kResamplingRMSError, -63.96),
414        std::tr1::make_tuple(22050, 48000, kResamplingRMSError, -62.42),
415        std::tr1::make_tuple(32000, 48000, kResamplingRMSError, -64.04),
416        std::tr1::make_tuple(44100, 48000, kResamplingRMSError, -62.63),
417        std::tr1::make_tuple(48000, 48000, kResamplingRMSError, -73.52),
418        std::tr1::make_tuple(96000, 48000, -18.40, -28.44),
419        std::tr1::make_tuple(192000, 48000, -20.43, -14.11),
420
421        // To 96kHz
422        std::tr1::make_tuple(8000, 96000, kResamplingRMSError, -63.19),
423        std::tr1::make_tuple(11025, 96000, kResamplingRMSError, -62.61),
424        std::tr1::make_tuple(16000, 96000, kResamplingRMSError, -63.39),
425        std::tr1::make_tuple(22050, 96000, kResamplingRMSError, -62.42),
426        std::tr1::make_tuple(32000, 96000, kResamplingRMSError, -63.95),
427        std::tr1::make_tuple(44100, 96000, kResamplingRMSError, -62.63),
428        std::tr1::make_tuple(48000, 96000, kResamplingRMSError, -73.52),
429        std::tr1::make_tuple(96000, 96000, kResamplingRMSError, -73.52),
430        std::tr1::make_tuple(192000, 96000, kResamplingRMSError, -28.41),
431
432        // To 192kHz
433        std::tr1::make_tuple(8000, 192000, kResamplingRMSError, -63.10),
434        std::tr1::make_tuple(11025, 192000, kResamplingRMSError, -62.61),
435        std::tr1::make_tuple(16000, 192000, kResamplingRMSError, -63.14),
436        std::tr1::make_tuple(22050, 192000, kResamplingRMSError, -62.42),
437        std::tr1::make_tuple(32000, 192000, kResamplingRMSError, -63.38),
438        std::tr1::make_tuple(44100, 192000, kResamplingRMSError, -62.63),
439        std::tr1::make_tuple(48000, 192000, kResamplingRMSError, -73.44),
440        std::tr1::make_tuple(96000, 192000, kResamplingRMSError, -73.52),
441        std::tr1::make_tuple(192000, 192000, kResamplingRMSError, -73.52)));
442
443}  // namespace media
444