tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

sinc_resampler_unittest.cc (16411B)


      1 /*
      2 *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
      3 *
      4 *  Use of this source code is governed by a BSD-style license
      5 *  that can be found in the LICENSE file in the root of the source
      6 *  tree. An additional intellectual property rights grant can be found
      7 *  in the file PATENTS.  All contributing project authors may
      8 *  be found in the AUTHORS file in the root of the source tree.
      9 */
     10 
     11 // Modified from the Chromium original:
     12 // src/media/base/sinc_resampler_unittest.cc
     13 
     14 #include "common_audio/resampler/sinc_resampler.h"
     15 
     16 #include <algorithm>
     17 #include <cmath>
     18 #include <cstddef>
     19 #include <cstdint>
     20 #include <cstdio>
     21 #include <cstring>
     22 #include <memory>
     23 #include <numbers>
     24 #include <tuple>
     25 
     26 #include "common_audio/resampler/sinusoidal_linear_chirp_source.h"
     27 #include "rtc_base/cpu_info.h"
     28 #include "rtc_base/system/arch.h"
     29 #include "rtc_base/time_utils.h"
     30 #include "test/gmock.h"
     31 #include "test/gtest.h"
     32 
     33 using ::testing::_;
     34 
     35 namespace webrtc {
     36 
     37 static const double kSampleRateRatio = 192000.0 / 44100.0;
     38 static const double kKernelInterpolationFactor = 0.5;
     39 
     40 // Helper class to ensure ChunkedResample() functions properly.
     41 class MockSource : public SincResamplerCallback {
     42 public:
     43  MOCK_METHOD(void, Run, (size_t frames, float* destination), (override));
     44 };
     45 
     46 ACTION(ClearBuffer) {
     47  memset(arg1, 0, arg0 * sizeof(float));
     48 }
     49 
     50 ACTION(FillBuffer) {
     51  // Value chosen arbitrarily such that SincResampler resamples it to something
     52  // easily representable on all platforms; e.g., using kSampleRateRatio this
     53  // becomes 1.81219.
     54  memset(arg1, 64, arg0 * sizeof(float));
     55 }
     56 
     57 // Test requesting multiples of ChunkSize() frames results in the proper number
     58 // of callbacks.
     59 TEST(SincResamplerTest, ChunkedResample) {
     60  MockSource mock_source;
     61 
     62  // Choose a high ratio of input to output samples which will result in quick
     63  // exhaustion of SincResampler's internal buffers.
     64  SincResampler resampler(kSampleRateRatio, SincResampler::kDefaultRequestSize,
     65                          &mock_source);
     66 
     67  static const int kChunks = 2;
     68  size_t max_chunk_size = resampler.ChunkSize() * kChunks;
     69  std::unique_ptr<float[]> resampled_destination(new float[max_chunk_size]);
     70 
     71  // Verify requesting ChunkSize() frames causes a single callback.
     72  EXPECT_CALL(mock_source, Run(_, _)).Times(1).WillOnce(ClearBuffer());
     73  resampler.Resample(resampler.ChunkSize(), resampled_destination.get());
     74 
     75  // Verify requesting kChunks * ChunkSize() frames causes kChunks callbacks.
     76  ::testing::Mock::VerifyAndClear(&mock_source);
     77  EXPECT_CALL(mock_source, Run(_, _))
     78      .Times(kChunks)
     79      .WillRepeatedly(ClearBuffer());
     80  resampler.Resample(max_chunk_size, resampled_destination.get());
     81 }
     82 
     83 // Test flush resets the internal state properly.
     84 TEST(SincResamplerTest, Flush) {
     85  MockSource mock_source;
     86  SincResampler resampler(kSampleRateRatio, SincResampler::kDefaultRequestSize,
     87                          &mock_source);
     88  std::unique_ptr<float[]> resampled_destination(
     89      new float[resampler.ChunkSize()]);
     90 
     91  // Fill the resampler with junk data.
     92  EXPECT_CALL(mock_source, Run(_, _)).Times(1).WillOnce(FillBuffer());
     93  resampler.Resample(resampler.ChunkSize() / 2, resampled_destination.get());
     94  ASSERT_NE(resampled_destination[0], 0);
     95 
     96  // Flush and request more data, which should all be zeros now.
     97  resampler.Flush();
     98  ::testing::Mock::VerifyAndClear(&mock_source);
     99  EXPECT_CALL(mock_source, Run(_, _)).Times(1).WillOnce(ClearBuffer());
    100  resampler.Resample(resampler.ChunkSize() / 2, resampled_destination.get());
    101  for (size_t i = 0; i < resampler.ChunkSize() / 2; ++i)
    102    ASSERT_FLOAT_EQ(resampled_destination[i], 0);
    103 }
    104 
    105 // Test flush resets the internal state properly.
    106 TEST(SincResamplerTest, DISABLED_SetRatioBench) {
    107  MockSource mock_source;
    108  SincResampler resampler(kSampleRateRatio, SincResampler::kDefaultRequestSize,
    109                          &mock_source);
    110 
    111  int64_t start = TimeNanos();
    112  for (int i = 1; i < 10000; ++i)
    113    resampler.SetRatio(1.0 / i);
    114  double total_time_c_us = (TimeNanos() - start) / kNumNanosecsPerMicrosec;
    115  printf("SetRatio() took %.2fms.\n", total_time_c_us / 1000);
    116 }
    117 
    118 // Ensure various optimized Convolve() methods return the same value.  Only run
    119 // this test if other optimized methods exist, otherwise the default Convolve()
    120 // will be tested by the parameterized SincResampler tests below.
    121 TEST(SincResamplerTest, Convolve) {
    122 #if defined(WEBRTC_ARCH_X86_FAMILY)
    123  ASSERT_TRUE(cpu_info::Supports(cpu_info::ISA::kSSE2));
    124 #elif defined(WEBRTC_ARCH_ARM_V7)
    125  ASSERT_TRUE(cpu_info::Supports(cpu_info::ISA::kNeon));
    126 #endif
    127 
    128  // Initialize a dummy resampler.
    129  MockSource mock_source;
    130  SincResampler resampler(kSampleRateRatio, SincResampler::kDefaultRequestSize,
    131                          &mock_source);
    132 
    133  // The optimized Convolve methods are slightly more precise than Convolve_C(),
    134  // so comparison must be done using an epsilon.
    135  static const double kEpsilon = 0.00000005;
    136 
    137  // Use a kernel from SincResampler as input and kernel data, this has the
    138  // benefit of already being properly sized and aligned for Convolve_SSE().
    139  double result = SincResampler::Convolve_C(
    140      resampler.kernel_storage_.get(), resampler.kernel_storage_.get(),
    141      resampler.kernel_storage_.get(), kKernelInterpolationFactor);
    142  double result2 = resampler.convolve_proc_(
    143      resampler.kernel_storage_.get(), resampler.kernel_storage_.get(),
    144      resampler.kernel_storage_.get(), kKernelInterpolationFactor);
    145  EXPECT_NEAR(result2, result, kEpsilon);
    146 
    147  // Test Convolve() w/ unaligned input pointer.
    148  result = SincResampler::Convolve_C(
    149      resampler.kernel_storage_.get() + 1, resampler.kernel_storage_.get(),
    150      resampler.kernel_storage_.get(), kKernelInterpolationFactor);
    151  result2 = resampler.convolve_proc_(
    152      resampler.kernel_storage_.get() + 1, resampler.kernel_storage_.get(),
    153      resampler.kernel_storage_.get(), kKernelInterpolationFactor);
    154  EXPECT_NEAR(result2, result, kEpsilon);
    155 }
    156 
    157 // Benchmark for the various Convolve() methods.  Make sure to build with
    158 // branding=Chrome so that RTC_DCHECKs are compiled out when benchmarking.
    159 // Original benchmarks were run with --convolve-iterations=50000000.
    160 TEST(SincResamplerTest, ConvolveBenchmark) {
    161  // Initialize a dummy resampler.
    162  MockSource mock_source;
    163  SincResampler resampler(kSampleRateRatio, SincResampler::kDefaultRequestSize,
    164                          &mock_source);
    165 
    166  // Retrieve benchmark iterations from command line.
    167  // TODO(ajm): Reintroduce this as a command line option.
    168  const int kConvolveIterations = 1000000;
    169 
    170  printf("Benchmarking %d iterations:\n", kConvolveIterations);
    171 
    172  // Benchmark Convolve_C().
    173  int64_t start = TimeNanos();
    174  for (int i = 0; i < kConvolveIterations; ++i) {
    175    SincResampler::Convolve_C(
    176        resampler.kernel_storage_.get(), resampler.kernel_storage_.get(),
    177        resampler.kernel_storage_.get(), kKernelInterpolationFactor);
    178  }
    179  double total_time_c_us = (TimeNanos() - start) / kNumNanosecsPerMicrosec;
    180  printf("Convolve_C took %.2fms.\n", total_time_c_us / 1000);
    181 
    182 #if defined(WEBRTC_ARCH_X86_FAMILY)
    183  ASSERT_TRUE(cpu_info::Supports(cpu_info::ISA::kSSE2));
    184 #elif defined(WEBRTC_ARCH_ARM_V7)
    185  ASSERT_TRUE(cpu_info::Supports(cpu_info::ISA::kNeon));
    186 #endif
    187 
    188  // Benchmark with unaligned input pointer.
    189  start = TimeNanos();
    190  for (int j = 0; j < kConvolveIterations; ++j) {
    191    resampler.convolve_proc_(
    192        resampler.kernel_storage_.get() + 1, resampler.kernel_storage_.get(),
    193        resampler.kernel_storage_.get(), kKernelInterpolationFactor);
    194  }
    195  double total_time_optimized_unaligned_us =
    196      (TimeNanos() - start) / kNumNanosecsPerMicrosec;
    197  printf(
    198      "convolve_proc_(unaligned) took %.2fms; which is %.2fx "
    199      "faster than Convolve_C.\n",
    200      total_time_optimized_unaligned_us / 1000,
    201      total_time_c_us / total_time_optimized_unaligned_us);
    202 
    203  // Benchmark with aligned input pointer.
    204  start = TimeNanos();
    205  for (int j = 0; j < kConvolveIterations; ++j) {
    206    resampler.convolve_proc_(
    207        resampler.kernel_storage_.get(), resampler.kernel_storage_.get(),
    208        resampler.kernel_storage_.get(), kKernelInterpolationFactor);
    209  }
    210  double total_time_optimized_aligned_us =
    211      (TimeNanos() - start) / kNumNanosecsPerMicrosec;
    212  printf(
    213      "convolve_proc_ (aligned) took %.2fms; which is %.2fx "
    214      "faster than Convolve_C and %.2fx faster than "
    215      "convolve_proc_ (unaligned).\n",
    216      total_time_optimized_aligned_us / 1000,
    217      total_time_c_us / total_time_optimized_aligned_us,
    218      total_time_optimized_unaligned_us / total_time_optimized_aligned_us);
    219 }
    220 
    221 typedef std::tuple<int, int, double, double> SincResamplerTestData;
    222 class SincResamplerTest
    223    : public ::testing::TestWithParam<SincResamplerTestData> {
    224 public:
    225  SincResamplerTest()
    226      : input_rate_(std::get<0>(GetParam())),
    227        output_rate_(std::get<1>(GetParam())),
    228        rms_error_(std::get<2>(GetParam())),
    229        low_freq_error_(std::get<3>(GetParam())) {}
    230 
    231  ~SincResamplerTest() override {}
    232 
    233 protected:
    234  int input_rate_;
    235  int output_rate_;
    236  double rms_error_;
    237  double low_freq_error_;
    238 };
    239 
    240 // Tests resampling using a given input and output sample rate.
    241 TEST_P(SincResamplerTest, Resample) {
    242  // Make comparisons using one second of data.
    243  static const double kTestDurationSecs = 1;
    244  const size_t input_samples =
    245      static_cast<size_t>(kTestDurationSecs * input_rate_);
    246  const size_t output_samples =
    247      static_cast<size_t>(kTestDurationSecs * output_rate_);
    248 
    249  // Nyquist frequency for the input sampling rate.
    250  const double input_nyquist_freq = 0.5 * input_rate_;
    251 
    252  // Source for data to be resampled.
    253  SinusoidalLinearChirpSource resampler_source(input_rate_, input_samples,
    254                                               input_nyquist_freq, 0);
    255 
    256  const double io_ratio = input_rate_ / static_cast<double>(output_rate_);
    257  SincResampler resampler(io_ratio, SincResampler::kDefaultRequestSize,
    258                          &resampler_source);
    259 
    260  // Force an update to the sample rate ratio to ensure dynamic sample rate
    261  // changes are working correctly.
    262  std::unique_ptr<float[]> kernel(new float[SincResampler::kKernelStorageSize]);
    263  memcpy(kernel.get(), resampler.get_kernel_for_testing(),
    264         SincResampler::kKernelStorageSize);
    265  resampler.SetRatio(std::numbers::pi_v<float>);
    266  ASSERT_NE(0, memcmp(kernel.get(), resampler.get_kernel_for_testing(),
    267                      SincResampler::kKernelStorageSize));
    268  resampler.SetRatio(io_ratio);
    269  ASSERT_EQ(0, memcmp(kernel.get(), resampler.get_kernel_for_testing(),
    270                      SincResampler::kKernelStorageSize));
    271 
    272  // TODO(dalecurtis): If we switch to AVX/SSE optimization, we'll need to
    273  // allocate these on 32-byte boundaries and ensure they're sized % 32 bytes.
    274  std::unique_ptr<float[]> resampled_destination(new float[output_samples]);
    275  std::unique_ptr<float[]> pure_destination(new float[output_samples]);
    276 
    277  // Generate resampled signal.
    278  resampler.Resample(output_samples, resampled_destination.get());
    279 
    280  // Generate pure signal.
    281  SinusoidalLinearChirpSource pure_source(output_rate_, output_samples,
    282                                          input_nyquist_freq, 0);
    283  pure_source.Run(output_samples, pure_destination.get());
    284 
    285  // Range of the Nyquist frequency (0.5 * min(input rate, output_rate)) which
    286  // we refer to as low and high.
    287  static const double kLowFrequencyNyquistRange = 0.7;
    288  static const double kHighFrequencyNyquistRange = 0.9;
    289 
    290  // Calculate Root-Mean-Square-Error and maximum error for the resampling.
    291  double sum_of_squares = 0;
    292  double low_freq_max_error = 0;
    293  double high_freq_max_error = 0;
    294  int minimum_rate = std::min(input_rate_, output_rate_);
    295  double low_frequency_range = kLowFrequencyNyquistRange * 0.5 * minimum_rate;
    296  double high_frequency_range = kHighFrequencyNyquistRange * 0.5 * minimum_rate;
    297  for (size_t i = 0; i < output_samples; ++i) {
    298    double error = fabs(resampled_destination[i] - pure_destination[i]);
    299 
    300    if (pure_source.Frequency(i) < low_frequency_range) {
    301      if (error > low_freq_max_error)
    302        low_freq_max_error = error;
    303    } else if (pure_source.Frequency(i) < high_frequency_range) {
    304      if (error > high_freq_max_error)
    305        high_freq_max_error = error;
    306    }
    307    // TODO(dalecurtis): Sanity check frequencies > kHighFrequencyNyquistRange.
    308 
    309    sum_of_squares += error * error;
    310  }
    311 
    312  double rms_error = sqrt(sum_of_squares / output_samples);
    313 
    314 // Convert each error to dbFS.
    315 #define DBFS(x) 20 * log10(x)
    316  rms_error = DBFS(rms_error);
    317  low_freq_max_error = DBFS(low_freq_max_error);
    318  high_freq_max_error = DBFS(high_freq_max_error);
    319 
    320  EXPECT_LE(rms_error, rms_error_);
    321  EXPECT_LE(low_freq_max_error, low_freq_error_);
    322 
    323  // All conversions currently have a high frequency error around -6 dbFS.
    324  static const double kHighFrequencyMaxError = -6.02;
    325  EXPECT_LE(high_freq_max_error, kHighFrequencyMaxError);
    326 }
    327 
    328 // Almost all conversions have an RMS error of around -14 dbFS.
    329 static const double kResamplingRMSError = -14.58;
    330 
    331 // Thresholds chosen arbitrarily based on what each resampling reported during
    332 // testing.  All thresholds are in dbFS, http://en.wikipedia.org/wiki/DBFS.
    333 INSTANTIATE_TEST_SUITE_P(
    334    SincResamplerTest,
    335    SincResamplerTest,
    336    ::testing::Values(
    337        // To 22.05kHz
    338        std::make_tuple(8000, 22050, kResamplingRMSError, -62.73),
    339        std::make_tuple(11025, 22050, kResamplingRMSError, -72.19),
    340        std::make_tuple(16000, 22050, kResamplingRMSError, -62.54),
    341        std::make_tuple(22050, 22050, kResamplingRMSError, -73.53),
    342        std::make_tuple(32000, 22050, kResamplingRMSError, -46.45),
    343        std::make_tuple(44100, 22050, kResamplingRMSError, -28.49),
    344        std::make_tuple(48000, 22050, -15.01, -25.56),
    345        std::make_tuple(96000, 22050, -18.49, -13.42),
    346        std::make_tuple(192000, 22050, -20.50, -9.23),
    347 
    348        // To 44.1kHz
    349        std::make_tuple(8000, 44100, kResamplingRMSError, -62.73),
    350        std::make_tuple(11025, 44100, kResamplingRMSError, -72.19),
    351        std::make_tuple(16000, 44100, kResamplingRMSError, -62.54),
    352        std::make_tuple(22050, 44100, kResamplingRMSError, -73.53),
    353        std::make_tuple(32000, 44100, kResamplingRMSError, -63.32),
    354        std::make_tuple(44100, 44100, kResamplingRMSError, -73.52),
    355        std::make_tuple(48000, 44100, -15.01, -64.04),
    356        std::make_tuple(96000, 44100, -18.49, -25.51),
    357        std::make_tuple(192000, 44100, -20.50, -13.31),
    358 
    359        // To 48kHz
    360        std::make_tuple(8000, 48000, kResamplingRMSError, -63.43),
    361        std::make_tuple(11025, 48000, kResamplingRMSError, -62.61),
    362        std::make_tuple(16000, 48000, kResamplingRMSError, -63.95),
    363        std::make_tuple(22050, 48000, kResamplingRMSError, -62.42),
    364        std::make_tuple(32000, 48000, kResamplingRMSError, -64.04),
    365        std::make_tuple(44100, 48000, kResamplingRMSError, -62.63),
    366        std::make_tuple(48000, 48000, kResamplingRMSError, -73.52),
    367        std::make_tuple(96000, 48000, -18.40, -28.44),
    368        std::make_tuple(192000, 48000, -20.43, -14.11),
    369 
    370        // To 96kHz
    371        std::make_tuple(8000, 96000, kResamplingRMSError, -63.19),
    372        std::make_tuple(11025, 96000, kResamplingRMSError, -62.61),
    373        std::make_tuple(16000, 96000, kResamplingRMSError, -63.39),
    374        std::make_tuple(22050, 96000, kResamplingRMSError, -62.42),
    375        std::make_tuple(32000, 96000, kResamplingRMSError, -63.95),
    376        std::make_tuple(44100, 96000, kResamplingRMSError, -62.63),
    377        std::make_tuple(48000, 96000, kResamplingRMSError, -73.52),
    378        std::make_tuple(96000, 96000, kResamplingRMSError, -73.52),
    379        std::make_tuple(192000, 96000, kResamplingRMSError, -28.41),
    380 
    381        // To 192kHz
    382        std::make_tuple(8000, 192000, kResamplingRMSError, -63.10),
    383        std::make_tuple(11025, 192000, kResamplingRMSError, -62.61),
    384        std::make_tuple(16000, 192000, kResamplingRMSError, -63.14),
    385        std::make_tuple(22050, 192000, kResamplingRMSError, -62.42),
    386        std::make_tuple(32000, 192000, kResamplingRMSError, -63.38),
    387        std::make_tuple(44100, 192000, kResamplingRMSError, -62.63),
    388        std::make_tuple(48000, 192000, kResamplingRMSError, -73.44),
    389        std::make_tuple(96000, 192000, kResamplingRMSError, -73.52),
    390        std::make_tuple(192000, 192000, kResamplingRMSError, -73.52)));
    391 
    392 }  // namespace webrtc