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 // MSVC++ requires this to be set before any other includes to get M_PI.
15 #define _USE_MATH_DEFINES
16
17 #include "common_audio/resampler/sinc_resampler.h"
18
19 #include <math.h>
20
21 #include <algorithm>
22 #include <memory>
23 #include <tuple>
24
25 #include "common_audio/resampler/sinusoidal_linear_chirp_source.h"
26 #include "rtc_base/system/arch.h"
27 #include "rtc_base/time_utils.h"
28 #include "system_wrappers/include/cpu_features_wrapper.h"
29 #include "test/gmock.h"
30 #include "test/gtest.h"
31
32 using ::testing::_;
33
34 namespace webrtc {
35
36 static const double kSampleRateRatio = 192000.0 / 44100.0;
37 static const double kKernelInterpolationFactor = 0.5;
38
39 // Helper class to ensure ChunkedResample() functions properly.
40 class MockSource : public SincResamplerCallback {
41 public:
42 MOCK_METHOD(void, Run, (size_t frames, float* destination), (override));
43 };
44
ACTION(ClearBuffer)45 ACTION(ClearBuffer) {
46 memset(arg1, 0, arg0 * sizeof(float));
47 }
48
ACTION(FillBuffer)49 ACTION(FillBuffer) {
50 // Value chosen arbitrarily such that SincResampler resamples it to something
51 // easily representable on all platforms; e.g., using kSampleRateRatio this
52 // becomes 1.81219.
53 memset(arg1, 64, arg0 * sizeof(float));
54 }
55
56 // Test requesting multiples of ChunkSize() frames results in the proper number
57 // of callbacks.
TEST(SincResamplerTest,ChunkedResample)58 TEST(SincResamplerTest, ChunkedResample) {
59 MockSource mock_source;
60
61 // Choose a high ratio of input to output samples which will result in quick
62 // exhaustion of SincResampler's internal buffers.
63 SincResampler resampler(kSampleRateRatio, SincResampler::kDefaultRequestSize,
64 &mock_source);
65
66 static const int kChunks = 2;
67 size_t max_chunk_size = resampler.ChunkSize() * kChunks;
68 std::unique_ptr<float[]> resampled_destination(new float[max_chunk_size]);
69
70 // Verify requesting ChunkSize() frames causes a single callback.
71 EXPECT_CALL(mock_source, Run(_, _)).Times(1).WillOnce(ClearBuffer());
72 resampler.Resample(resampler.ChunkSize(), resampled_destination.get());
73
74 // Verify requesting kChunks * ChunkSize() frames causes kChunks callbacks.
75 ::testing::Mock::VerifyAndClear(&mock_source);
76 EXPECT_CALL(mock_source, Run(_, _))
77 .Times(kChunks)
78 .WillRepeatedly(ClearBuffer());
79 resampler.Resample(max_chunk_size, resampled_destination.get());
80 }
81
82 // Test flush resets the internal state properly.
TEST(SincResamplerTest,Flush)83 TEST(SincResamplerTest, Flush) {
84 MockSource mock_source;
85 SincResampler resampler(kSampleRateRatio, SincResampler::kDefaultRequestSize,
86 &mock_source);
87 std::unique_ptr<float[]> resampled_destination(
88 new float[resampler.ChunkSize()]);
89
90 // Fill the resampler with junk data.
91 EXPECT_CALL(mock_source, Run(_, _)).Times(1).WillOnce(FillBuffer());
92 resampler.Resample(resampler.ChunkSize() / 2, resampled_destination.get());
93 ASSERT_NE(resampled_destination[0], 0);
94
95 // Flush and request more data, which should all be zeros now.
96 resampler.Flush();
97 ::testing::Mock::VerifyAndClear(&mock_source);
98 EXPECT_CALL(mock_source, Run(_, _)).Times(1).WillOnce(ClearBuffer());
99 resampler.Resample(resampler.ChunkSize() / 2, resampled_destination.get());
100 for (size_t i = 0; i < resampler.ChunkSize() / 2; ++i)
101 ASSERT_FLOAT_EQ(resampled_destination[i], 0);
102 }
103
104 // Test flush resets the internal state properly.
TEST(SincResamplerTest,DISABLED_SetRatioBench)105 TEST(SincResamplerTest, DISABLED_SetRatioBench) {
106 MockSource mock_source;
107 SincResampler resampler(kSampleRateRatio, SincResampler::kDefaultRequestSize,
108 &mock_source);
109
110 int64_t start = rtc::TimeNanos();
111 for (int i = 1; i < 10000; ++i)
112 resampler.SetRatio(1.0 / i);
113 double total_time_c_us =
114 (rtc::TimeNanos() - start) / rtc::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.
TEST(SincResamplerTest,Convolve)121 TEST(SincResamplerTest, Convolve) {
122 #if defined(WEBRTC_ARCH_X86_FAMILY)
123 ASSERT_TRUE(GetCPUInfo(kSSE2));
124 #elif defined(WEBRTC_ARCH_ARM_V7)
125 ASSERT_TRUE(GetCPUFeaturesARM() & kCPUFeatureNEON);
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 = resampler.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 = resampler.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.
TEST(SincResamplerTest,ConvolveBenchmark)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 = rtc::TimeNanos();
174 for (int i = 0; i < kConvolveIterations; ++i) {
175 resampler.Convolve_C(
176 resampler.kernel_storage_.get(), resampler.kernel_storage_.get(),
177 resampler.kernel_storage_.get(), kKernelInterpolationFactor);
178 }
179 double total_time_c_us =
180 (rtc::TimeNanos() - start) / rtc::kNumNanosecsPerMicrosec;
181 printf("Convolve_C took %.2fms.\n", total_time_c_us / 1000);
182
183 #if defined(WEBRTC_ARCH_X86_FAMILY)
184 ASSERT_TRUE(GetCPUInfo(kSSE2));
185 #elif defined(WEBRTC_ARCH_ARM_V7)
186 ASSERT_TRUE(GetCPUFeaturesARM() & kCPUFeatureNEON);
187 #endif
188
189 // Benchmark with unaligned input pointer.
190 start = rtc::TimeNanos();
191 for (int j = 0; j < kConvolveIterations; ++j) {
192 resampler.convolve_proc_(
193 resampler.kernel_storage_.get() + 1, resampler.kernel_storage_.get(),
194 resampler.kernel_storage_.get(), kKernelInterpolationFactor);
195 }
196 double total_time_optimized_unaligned_us =
197 (rtc::TimeNanos() - start) / rtc::kNumNanosecsPerMicrosec;
198 printf(
199 "convolve_proc_(unaligned) took %.2fms; which is %.2fx "
200 "faster than Convolve_C.\n",
201 total_time_optimized_unaligned_us / 1000,
202 total_time_c_us / total_time_optimized_unaligned_us);
203
204 // Benchmark with aligned input pointer.
205 start = rtc::TimeNanos();
206 for (int j = 0; j < kConvolveIterations; ++j) {
207 resampler.convolve_proc_(
208 resampler.kernel_storage_.get(), resampler.kernel_storage_.get(),
209 resampler.kernel_storage_.get(), kKernelInterpolationFactor);
210 }
211 double total_time_optimized_aligned_us =
212 (rtc::TimeNanos() - start) / rtc::kNumNanosecsPerMicrosec;
213 printf(
214 "convolve_proc_ (aligned) took %.2fms; which is %.2fx "
215 "faster than Convolve_C and %.2fx faster than "
216 "convolve_proc_ (unaligned).\n",
217 total_time_optimized_aligned_us / 1000,
218 total_time_c_us / total_time_optimized_aligned_us,
219 total_time_optimized_unaligned_us / total_time_optimized_aligned_us);
220 }
221
222 typedef std::tuple<int, int, double, double> SincResamplerTestData;
223 class SincResamplerTest
224 : public ::testing::TestWithParam<SincResamplerTestData> {
225 public:
SincResamplerTest()226 SincResamplerTest()
227 : input_rate_(std::get<0>(GetParam())),
228 output_rate_(std::get<1>(GetParam())),
229 rms_error_(std::get<2>(GetParam())),
230 low_freq_error_(std::get<3>(GetParam())) {}
231
~SincResamplerTest()232 virtual ~SincResamplerTest() {}
233
234 protected:
235 int input_rate_;
236 int output_rate_;
237 double rms_error_;
238 double low_freq_error_;
239 };
240
241 // Tests resampling using a given input and output sample rate.
TEST_P(SincResamplerTest,Resample)242 TEST_P(SincResamplerTest, Resample) {
243 // Make comparisons using one second of data.
244 static const double kTestDurationSecs = 1;
245 const size_t input_samples =
246 static_cast<size_t>(kTestDurationSecs * input_rate_);
247 const size_t output_samples =
248 static_cast<size_t>(kTestDurationSecs * output_rate_);
249
250 // Nyquist frequency for the input sampling rate.
251 const double input_nyquist_freq = 0.5 * input_rate_;
252
253 // Source for data to be resampled.
254 SinusoidalLinearChirpSource resampler_source(input_rate_, input_samples,
255 input_nyquist_freq, 0);
256
257 const double io_ratio = input_rate_ / static_cast<double>(output_rate_);
258 SincResampler resampler(io_ratio, SincResampler::kDefaultRequestSize,
259 &resampler_source);
260
261 // Force an update to the sample rate ratio to ensure dynamic sample rate
262 // changes are working correctly.
263 std::unique_ptr<float[]> kernel(new float[SincResampler::kKernelStorageSize]);
264 memcpy(kernel.get(), resampler.get_kernel_for_testing(),
265 SincResampler::kKernelStorageSize);
266 resampler.SetRatio(M_PI);
267 ASSERT_NE(0, memcmp(kernel.get(), resampler.get_kernel_for_testing(),
268 SincResampler::kKernelStorageSize));
269 resampler.SetRatio(io_ratio);
270 ASSERT_EQ(0, memcmp(kernel.get(), resampler.get_kernel_for_testing(),
271 SincResampler::kKernelStorageSize));
272
273 // TODO(dalecurtis): If we switch to AVX/SSE optimization, we'll need to
274 // allocate these on 32-byte boundaries and ensure they're sized % 32 bytes.
275 std::unique_ptr<float[]> resampled_destination(new float[output_samples]);
276 std::unique_ptr<float[]> pure_destination(new float[output_samples]);
277
278 // Generate resampled signal.
279 resampler.Resample(output_samples, resampled_destination.get());
280
281 // Generate pure signal.
282 SinusoidalLinearChirpSource pure_source(output_rate_, output_samples,
283 input_nyquist_freq, 0);
284 pure_source.Run(output_samples, pure_destination.get());
285
286 // Range of the Nyquist frequency (0.5 * min(input rate, output_rate)) which
287 // we refer to as low and high.
288 static const double kLowFrequencyNyquistRange = 0.7;
289 static const double kHighFrequencyNyquistRange = 0.9;
290
291 // Calculate Root-Mean-Square-Error and maximum error for the resampling.
292 double sum_of_squares = 0;
293 double low_freq_max_error = 0;
294 double high_freq_max_error = 0;
295 int minimum_rate = std::min(input_rate_, output_rate_);
296 double low_frequency_range = kLowFrequencyNyquistRange * 0.5 * minimum_rate;
297 double high_frequency_range = kHighFrequencyNyquistRange * 0.5 * minimum_rate;
298 for (size_t i = 0; i < output_samples; ++i) {
299 double error = fabs(resampled_destination[i] - pure_destination[i]);
300
301 if (pure_source.Frequency(i) < low_frequency_range) {
302 if (error > low_freq_max_error)
303 low_freq_max_error = error;
304 } else if (pure_source.Frequency(i) < high_frequency_range) {
305 if (error > high_freq_max_error)
306 high_freq_max_error = error;
307 }
308 // TODO(dalecurtis): Sanity check frequencies > kHighFrequencyNyquistRange.
309
310 sum_of_squares += error * error;
311 }
312
313 double rms_error = sqrt(sum_of_squares / output_samples);
314
315 // Convert each error to dbFS.
316 #define DBFS(x) 20 * log10(x)
317 rms_error = DBFS(rms_error);
318 low_freq_max_error = DBFS(low_freq_max_error);
319 high_freq_max_error = DBFS(high_freq_max_error);
320
321 EXPECT_LE(rms_error, rms_error_);
322 EXPECT_LE(low_freq_max_error, low_freq_error_);
323
324 // All conversions currently have a high frequency error around -6 dbFS.
325 static const double kHighFrequencyMaxError = -6.02;
326 EXPECT_LE(high_freq_max_error, kHighFrequencyMaxError);
327 }
328
329 // Almost all conversions have an RMS error of around -14 dbFS.
330 static const double kResamplingRMSError = -14.58;
331
332 // Thresholds chosen arbitrarily based on what each resampling reported during
333 // testing. All thresholds are in dbFS, http://en.wikipedia.org/wiki/DBFS.
334 INSTANTIATE_TEST_SUITE_P(
335 SincResamplerTest,
336 SincResamplerTest,
337 ::testing::Values(
338 // To 22.05kHz
339 std::make_tuple(8000, 22050, kResamplingRMSError, -62.73),
340 std::make_tuple(11025, 22050, kResamplingRMSError, -72.19),
341 std::make_tuple(16000, 22050, kResamplingRMSError, -62.54),
342 std::make_tuple(22050, 22050, kResamplingRMSError, -73.53),
343 std::make_tuple(32000, 22050, kResamplingRMSError, -46.45),
344 std::make_tuple(44100, 22050, kResamplingRMSError, -28.49),
345 std::make_tuple(48000, 22050, -15.01, -25.56),
346 std::make_tuple(96000, 22050, -18.49, -13.42),
347 std::make_tuple(192000, 22050, -20.50, -9.23),
348
349 // To 44.1kHz
350 std::make_tuple(8000, 44100, kResamplingRMSError, -62.73),
351 std::make_tuple(11025, 44100, kResamplingRMSError, -72.19),
352 std::make_tuple(16000, 44100, kResamplingRMSError, -62.54),
353 std::make_tuple(22050, 44100, kResamplingRMSError, -73.53),
354 std::make_tuple(32000, 44100, kResamplingRMSError, -63.32),
355 std::make_tuple(44100, 44100, kResamplingRMSError, -73.52),
356 std::make_tuple(48000, 44100, -15.01, -64.04),
357 std::make_tuple(96000, 44100, -18.49, -25.51),
358 std::make_tuple(192000, 44100, -20.50, -13.31),
359
360 // To 48kHz
361 std::make_tuple(8000, 48000, kResamplingRMSError, -63.43),
362 std::make_tuple(11025, 48000, kResamplingRMSError, -62.61),
363 std::make_tuple(16000, 48000, kResamplingRMSError, -63.95),
364 std::make_tuple(22050, 48000, kResamplingRMSError, -62.42),
365 std::make_tuple(32000, 48000, kResamplingRMSError, -64.04),
366 std::make_tuple(44100, 48000, kResamplingRMSError, -62.63),
367 std::make_tuple(48000, 48000, kResamplingRMSError, -73.52),
368 std::make_tuple(96000, 48000, -18.40, -28.44),
369 std::make_tuple(192000, 48000, -20.43, -14.11),
370
371 // To 96kHz
372 std::make_tuple(8000, 96000, kResamplingRMSError, -63.19),
373 std::make_tuple(11025, 96000, kResamplingRMSError, -62.61),
374 std::make_tuple(16000, 96000, kResamplingRMSError, -63.39),
375 std::make_tuple(22050, 96000, kResamplingRMSError, -62.42),
376 std::make_tuple(32000, 96000, kResamplingRMSError, -63.95),
377 std::make_tuple(44100, 96000, kResamplingRMSError, -62.63),
378 std::make_tuple(48000, 96000, kResamplingRMSError, -73.52),
379 std::make_tuple(96000, 96000, kResamplingRMSError, -73.52),
380 std::make_tuple(192000, 96000, kResamplingRMSError, -28.41),
381
382 // To 192kHz
383 std::make_tuple(8000, 192000, kResamplingRMSError, -63.10),
384 std::make_tuple(11025, 192000, kResamplingRMSError, -62.61),
385 std::make_tuple(16000, 192000, kResamplingRMSError, -63.14),
386 std::make_tuple(22050, 192000, kResamplingRMSError, -62.42),
387 std::make_tuple(32000, 192000, kResamplingRMSError, -63.38),
388 std::make_tuple(44100, 192000, kResamplingRMSError, -62.63),
389 std::make_tuple(48000, 192000, kResamplingRMSError, -73.44),
390 std::make_tuple(96000, 192000, kResamplingRMSError, -73.52),
391 std::make_tuple(192000, 192000, kResamplingRMSError, -73.52)));
392
393 } // namespace webrtc
394