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