summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/common_audio/resampler/push_sinc_resampler_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/common_audio/resampler/push_sinc_resampler_unittest.cc')
-rw-r--r--third_party/libwebrtc/common_audio/resampler/push_sinc_resampler_unittest.cc367
1 files changed, 367 insertions, 0 deletions
diff --git a/third_party/libwebrtc/common_audio/resampler/push_sinc_resampler_unittest.cc b/third_party/libwebrtc/common_audio/resampler/push_sinc_resampler_unittest.cc
new file mode 100644
index 0000000000..8f82199d1d
--- /dev/null
+++ b/third_party/libwebrtc/common_audio/resampler/push_sinc_resampler_unittest.cc
@@ -0,0 +1,367 @@
+/*
+ * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "common_audio/resampler/push_sinc_resampler.h"
+
+#include <algorithm>
+#include <cmath>
+#include <cstring>
+#include <memory>
+
+#include "common_audio/include/audio_util.h"
+#include "common_audio/resampler/sinusoidal_linear_chirp_source.h"
+#include "rtc_base/time_utils.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+namespace {
+
+// Almost all conversions have an RMS error of around -14 dbFS.
+const double kResamplingRMSError = -14.42;
+
+// Used to convert errors to dbFS.
+template <typename T>
+T DBFS(T x) {
+ return 20 * std::log10(x);
+}
+
+} // namespace
+
+class PushSincResamplerTest : public ::testing::TestWithParam<
+ ::testing::tuple<int, int, double, double>> {
+ public:
+ PushSincResamplerTest()
+ : input_rate_(::testing::get<0>(GetParam())),
+ output_rate_(::testing::get<1>(GetParam())),
+ rms_error_(::testing::get<2>(GetParam())),
+ low_freq_error_(::testing::get<3>(GetParam())) {}
+
+ ~PushSincResamplerTest() override {}
+
+ protected:
+ void ResampleBenchmarkTest(bool int_format);
+ void ResampleTest(bool int_format);
+
+ int input_rate_;
+ int output_rate_;
+ double rms_error_;
+ double low_freq_error_;
+};
+
+class ZeroSource : public SincResamplerCallback {
+ public:
+ void Run(size_t frames, float* destination) override {
+ std::memset(destination, 0, sizeof(float) * frames);
+ }
+};
+
+void PushSincResamplerTest::ResampleBenchmarkTest(bool int_format) {
+ const size_t input_samples = static_cast<size_t>(input_rate_ / 100);
+ const size_t output_samples = static_cast<size_t>(output_rate_ / 100);
+ const int kResampleIterations = 500000;
+
+ // Source for data to be resampled.
+ ZeroSource resampler_source;
+
+ std::unique_ptr<float[]> resampled_destination(new float[output_samples]);
+ std::unique_ptr<float[]> source(new float[input_samples]);
+ std::unique_ptr<int16_t[]> source_int(new int16_t[input_samples]);
+ std::unique_ptr<int16_t[]> destination_int(new int16_t[output_samples]);
+
+ resampler_source.Run(input_samples, source.get());
+ for (size_t i = 0; i < input_samples; ++i) {
+ source_int[i] = static_cast<int16_t>(floor(32767 * source[i] + 0.5));
+ }
+
+ printf("Benchmarking %d iterations of %d Hz -> %d Hz:\n", kResampleIterations,
+ input_rate_, output_rate_);
+ const double io_ratio = input_rate_ / static_cast<double>(output_rate_);
+ SincResampler sinc_resampler(io_ratio, SincResampler::kDefaultRequestSize,
+ &resampler_source);
+ int64_t start = rtc::TimeNanos();
+ for (int i = 0; i < kResampleIterations; ++i) {
+ sinc_resampler.Resample(output_samples, resampled_destination.get());
+ }
+ double total_time_sinc_us =
+ (rtc::TimeNanos() - start) / rtc::kNumNanosecsPerMicrosec;
+ printf("SincResampler took %.2f us per frame.\n",
+ total_time_sinc_us / kResampleIterations);
+
+ PushSincResampler resampler(input_samples, output_samples);
+ start = rtc::TimeNanos();
+ if (int_format) {
+ for (int i = 0; i < kResampleIterations; ++i) {
+ EXPECT_EQ(output_samples,
+ resampler.Resample(source_int.get(), input_samples,
+ destination_int.get(), output_samples));
+ }
+ } else {
+ for (int i = 0; i < kResampleIterations; ++i) {
+ EXPECT_EQ(output_samples, resampler.Resample(source.get(), input_samples,
+ resampled_destination.get(),
+ output_samples));
+ }
+ }
+ double total_time_us =
+ (rtc::TimeNanos() - start) / rtc::kNumNanosecsPerMicrosec;
+ printf(
+ "PushSincResampler took %.2f us per frame; which is a %.1f%% overhead "
+ "on SincResampler.\n\n",
+ total_time_us / kResampleIterations,
+ (total_time_us - total_time_sinc_us) / total_time_sinc_us * 100);
+}
+
+// Disabled because it takes too long to run routinely. Use for performance
+// benchmarking when needed.
+TEST_P(PushSincResamplerTest, DISABLED_BenchmarkInt) {
+ ResampleBenchmarkTest(true);
+}
+
+TEST_P(PushSincResamplerTest, DISABLED_BenchmarkFloat) {
+ ResampleBenchmarkTest(false);
+}
+
+// Tests resampling using a given input and output sample rate.
+void PushSincResamplerTest::ResampleTest(bool int_format) {
+ // Make comparisons using one second of data.
+ static const double kTestDurationSecs = 1;
+ // 10 ms blocks.
+ const size_t kNumBlocks = static_cast<size_t>(kTestDurationSecs * 100);
+ const size_t input_block_size = static_cast<size_t>(input_rate_ / 100);
+ const size_t output_block_size = static_cast<size_t>(output_rate_ / 100);
+ const size_t input_samples =
+ static_cast<size_t>(kTestDurationSecs * input_rate_);
+ const size_t output_samples =
+ static_cast<size_t>(kTestDurationSecs * output_rate_);
+
+ // Nyquist frequency for the input sampling rate.
+ const double input_nyquist_freq = 0.5 * input_rate_;
+
+ // Source for data to be resampled.
+ SinusoidalLinearChirpSource resampler_source(input_rate_, input_samples,
+ input_nyquist_freq, 0);
+
+ PushSincResampler resampler(input_block_size, output_block_size);
+
+ // TODO(dalecurtis): If we switch to AVX/SSE optimization, we'll need to
+ // allocate these on 32-byte boundaries and ensure they're sized % 32 bytes.
+ std::unique_ptr<float[]> resampled_destination(new float[output_samples]);
+ std::unique_ptr<float[]> pure_destination(new float[output_samples]);
+ std::unique_ptr<float[]> source(new float[input_samples]);
+ std::unique_ptr<int16_t[]> source_int(new int16_t[input_block_size]);
+ std::unique_ptr<int16_t[]> destination_int(new int16_t[output_block_size]);
+
+ // The sinc resampler has an implicit delay of approximately half the kernel
+ // size at the input sample rate. By moving to a push model, this delay
+ // becomes explicit and is managed by zero-stuffing in PushSincResampler. We
+ // deal with it in the test by delaying the "pure" source to match. It must be
+ // checked before the first call to Resample(), because ChunkSize() will
+ // change afterwards.
+ const size_t output_delay_samples =
+ output_block_size - resampler.get_resampler_for_testing()->ChunkSize();
+
+ // Generate resampled signal.
+ // With the PushSincResampler, we produce the signal block-by-10ms-block
+ // rather than in a single pass, to exercise how it will be used in WebRTC.
+ resampler_source.Run(input_samples, source.get());
+ if (int_format) {
+ for (size_t i = 0; i < kNumBlocks; ++i) {
+ FloatToS16(&source[i * input_block_size], input_block_size,
+ source_int.get());
+ EXPECT_EQ(output_block_size,
+ resampler.Resample(source_int.get(), input_block_size,
+ destination_int.get(), output_block_size));
+ S16ToFloat(destination_int.get(), output_block_size,
+ &resampled_destination[i * output_block_size]);
+ }
+ } else {
+ for (size_t i = 0; i < kNumBlocks; ++i) {
+ EXPECT_EQ(
+ output_block_size,
+ resampler.Resample(&source[i * input_block_size], input_block_size,
+ &resampled_destination[i * output_block_size],
+ output_block_size));
+ }
+ }
+
+ // Generate pure signal.
+ SinusoidalLinearChirpSource pure_source(
+ output_rate_, output_samples, input_nyquist_freq, output_delay_samples);
+ pure_source.Run(output_samples, pure_destination.get());
+
+ // Range of the Nyquist frequency (0.5 * min(input rate, output_rate)) which
+ // we refer to as low and high.
+ static const double kLowFrequencyNyquistRange = 0.7;
+ static const double kHighFrequencyNyquistRange = 0.9;
+
+ // Calculate Root-Mean-Square-Error and maximum error for the resampling.
+ double sum_of_squares = 0;
+ double low_freq_max_error = 0;
+ double high_freq_max_error = 0;
+ int minimum_rate = std::min(input_rate_, output_rate_);
+ double low_frequency_range = kLowFrequencyNyquistRange * 0.5 * minimum_rate;
+ double high_frequency_range = kHighFrequencyNyquistRange * 0.5 * minimum_rate;
+
+ for (size_t i = 0; i < output_samples; ++i) {
+ double error = fabs(resampled_destination[i] - pure_destination[i]);
+
+ if (pure_source.Frequency(i) < low_frequency_range) {
+ if (error > low_freq_max_error)
+ low_freq_max_error = error;
+ } else if (pure_source.Frequency(i) < high_frequency_range) {
+ if (error > high_freq_max_error)
+ high_freq_max_error = error;
+ }
+ // TODO(dalecurtis): Sanity check frequencies > kHighFrequencyNyquistRange.
+
+ sum_of_squares += error * error;
+ }
+
+ double rms_error = sqrt(sum_of_squares / output_samples);
+
+ rms_error = DBFS(rms_error);
+ // In order to keep the thresholds in this test identical to SincResamplerTest
+ // we must account for the quantization error introduced by truncating from
+ // float to int. This happens twice (once at input and once at output) and we
+ // allow for the maximum possible error (1 / 32767) for each step.
+ //
+ // The quantization error is insignificant in the RMS calculation so does not
+ // need to be accounted for there.
+ low_freq_max_error = DBFS(low_freq_max_error - 2.0 / 32767);
+ high_freq_max_error = DBFS(high_freq_max_error - 2.0 / 32767);
+
+ EXPECT_LE(rms_error, rms_error_);
+ EXPECT_LE(low_freq_max_error, low_freq_error_);
+
+ // All conversions currently have a high frequency error around -6 dbFS.
+ static const double kHighFrequencyMaxError = -6.01;
+ EXPECT_LE(high_freq_max_error, kHighFrequencyMaxError);
+}
+
+TEST_P(PushSincResamplerTest, ResampleInt) {
+ ResampleTest(true);
+}
+
+TEST_P(PushSincResamplerTest, ResampleFloat) {
+ ResampleTest(false);
+}
+
+// Thresholds chosen arbitrarily based on what each resampling reported during
+// testing. All thresholds are in dbFS, http://en.wikipedia.org/wiki/DBFS.
+INSTANTIATE_TEST_SUITE_P(
+ PushSincResamplerTest,
+ PushSincResamplerTest,
+ ::testing::Values(
+ // First run through the rates tested in SincResamplerTest. The
+ // thresholds are identical.
+ //
+ // We don't directly test rates which fail to provide an integer number
+ // of samples in a 10 ms block (22050 and 11025 Hz), they are replaced
+ // by nearby rates in order to simplify testing.
+ //
+ // The PushSincResampler is in practice sample rate agnostic and derives
+ // resampling ratios from the block size, which for WebRTC purposes are
+ // blocks of floor(sample_rate/100) samples. So the 22050 Hz case is
+ // treated identically to the 22000 Hz case. Direct tests of 22050 Hz
+ // have to account for the simulated clock drift induced by the
+ // resampler inferring an incorrect sample rate ratio, without testing
+ // anything new within the resampler itself.
+
+ // To 22kHz
+ std::make_tuple(8000, 22000, kResamplingRMSError, -62.73),
+ std::make_tuple(11000, 22000, kResamplingRMSError, -74.17),
+ std::make_tuple(16000, 22000, kResamplingRMSError, -62.54),
+ std::make_tuple(22000, 22000, kResamplingRMSError, -73.53),
+ std::make_tuple(32000, 22000, kResamplingRMSError, -46.45),
+ std::make_tuple(44100, 22000, kResamplingRMSError, -28.34),
+ std::make_tuple(48000, 22000, -15.01, -25.56),
+ std::make_tuple(96000, 22000, -18.49, -13.30),
+ std::make_tuple(192000, 22000, -20.50, -9.20),
+
+ // To 44.1kHz
+ ::testing::make_tuple(8000, 44100, kResamplingRMSError, -62.73),
+ ::testing::make_tuple(11000, 44100, kResamplingRMSError, -63.57),
+ ::testing::make_tuple(16000, 44100, kResamplingRMSError, -62.54),
+ ::testing::make_tuple(22000, 44100, kResamplingRMSError, -62.73),
+ ::testing::make_tuple(32000, 44100, kResamplingRMSError, -63.32),
+ ::testing::make_tuple(44100, 44100, kResamplingRMSError, -73.53),
+ ::testing::make_tuple(48000, 44100, -15.01, -64.04),
+ ::testing::make_tuple(96000, 44100, -18.49, -25.51),
+ ::testing::make_tuple(192000, 44100, -20.50, -13.31),
+
+ // To 48kHz
+ ::testing::make_tuple(8000, 48000, kResamplingRMSError, -63.43),
+ ::testing::make_tuple(11000, 48000, kResamplingRMSError, -63.96),
+ ::testing::make_tuple(16000, 48000, kResamplingRMSError, -63.96),
+ ::testing::make_tuple(22000, 48000, kResamplingRMSError, -63.80),
+ ::testing::make_tuple(32000, 48000, kResamplingRMSError, -64.04),
+ ::testing::make_tuple(44100, 48000, kResamplingRMSError, -62.63),
+ ::testing::make_tuple(48000, 48000, kResamplingRMSError, -73.52),
+ ::testing::make_tuple(96000, 48000, -18.40, -28.44),
+ ::testing::make_tuple(192000, 48000, -20.43, -14.11),
+
+ // To 96kHz
+ ::testing::make_tuple(8000, 96000, kResamplingRMSError, -63.19),
+ ::testing::make_tuple(11000, 96000, kResamplingRMSError, -63.89),
+ ::testing::make_tuple(16000, 96000, kResamplingRMSError, -63.39),
+ ::testing::make_tuple(22000, 96000, kResamplingRMSError, -63.39),
+ ::testing::make_tuple(32000, 96000, kResamplingRMSError, -63.95),
+ ::testing::make_tuple(44100, 96000, kResamplingRMSError, -62.63),
+ ::testing::make_tuple(48000, 96000, kResamplingRMSError, -73.52),
+ ::testing::make_tuple(96000, 96000, kResamplingRMSError, -73.52),
+ ::testing::make_tuple(192000, 96000, kResamplingRMSError, -28.41),
+
+ // To 192kHz
+ ::testing::make_tuple(8000, 192000, kResamplingRMSError, -63.10),
+ ::testing::make_tuple(11000, 192000, kResamplingRMSError, -63.17),
+ ::testing::make_tuple(16000, 192000, kResamplingRMSError, -63.14),
+ ::testing::make_tuple(22000, 192000, kResamplingRMSError, -63.14),
+ ::testing::make_tuple(32000, 192000, kResamplingRMSError, -63.38),
+ ::testing::make_tuple(44100, 192000, kResamplingRMSError, -62.63),
+ ::testing::make_tuple(48000, 192000, kResamplingRMSError, -73.44),
+ ::testing::make_tuple(96000, 192000, kResamplingRMSError, -73.52),
+ ::testing::make_tuple(192000, 192000, kResamplingRMSError, -73.52),
+
+ // Next run through some additional cases interesting for WebRTC.
+ // We skip some extreme downsampled cases (192 -> {8, 16}, 96 -> 8)
+ // because they violate `kHighFrequencyMaxError`, which is not
+ // unexpected. It's very unlikely that we'll see these conversions in
+ // practice anyway.
+
+ // To 8 kHz
+ ::testing::make_tuple(8000, 8000, kResamplingRMSError, -75.50),
+ ::testing::make_tuple(16000, 8000, -18.56, -28.79),
+ ::testing::make_tuple(32000, 8000, -20.36, -14.13),
+ ::testing::make_tuple(44100, 8000, -21.00, -11.39),
+ ::testing::make_tuple(48000, 8000, -20.96, -11.04),
+
+ // To 16 kHz
+ ::testing::make_tuple(8000, 16000, kResamplingRMSError, -70.30),
+ ::testing::make_tuple(11000, 16000, kResamplingRMSError, -72.31),
+ ::testing::make_tuple(16000, 16000, kResamplingRMSError, -75.51),
+ ::testing::make_tuple(22000, 16000, kResamplingRMSError, -52.08),
+ ::testing::make_tuple(32000, 16000, -18.48, -28.59),
+ ::testing::make_tuple(44100, 16000, -19.30, -19.67),
+ ::testing::make_tuple(48000, 16000, -19.81, -18.11),
+ ::testing::make_tuple(96000, 16000, -20.95, -10.9596),
+
+ // To 32 kHz
+ ::testing::make_tuple(8000, 32000, kResamplingRMSError, -70.30),
+ ::testing::make_tuple(11000, 32000, kResamplingRMSError, -71.34),
+ ::testing::make_tuple(16000, 32000, kResamplingRMSError, -75.51),
+ ::testing::make_tuple(22000, 32000, kResamplingRMSError, -72.05),
+ ::testing::make_tuple(32000, 32000, kResamplingRMSError, -75.51),
+ ::testing::make_tuple(44100, 32000, -16.44, -51.0349),
+ ::testing::make_tuple(48000, 32000, -16.90, -43.9967),
+ ::testing::make_tuple(96000, 32000, -19.61, -18.04),
+ ::testing::make_tuple(192000, 32000, -21.02, -10.94)));
+
+} // namespace webrtc