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/base/scoped_ptr.h" 22 #include "webrtc/common_audio/resampler/sinc_resampler.h" 23 #include "webrtc/common_audio/resampler/sinusoidal_linear_chirp_source.h" 24 #include "webrtc/system_wrappers/include/cpu_features_wrapper.h" 25 #include "webrtc/system_wrappers/include/stringize_macros.h" 26 #include "webrtc/system_wrappers/include/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(size_t 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 size_t max_chunk_size = resampler.ChunkSize() * kChunks; 65 rtc::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 rtc::scoped_ptr<float[]> resampled_destination( 85 new float[resampler.ChunkSize()]); 86 87 // Fill the resampler with junk data. 88 EXPECT_CALL(mock_source, Run(_, _)) 89 .Times(1).WillOnce(FillBuffer()); 90 resampler.Resample(resampler.ChunkSize() / 2, resampled_destination.get()); 91 ASSERT_NE(resampled_destination[0], 0); 92 93 // Flush and request more data, which should all be zeros now. 94 resampler.Flush(); 95 testing::Mock::VerifyAndClear(&mock_source); 96 EXPECT_CALL(mock_source, Run(_, _)) 97 .Times(1).WillOnce(ClearBuffer()); 98 resampler.Resample(resampler.ChunkSize() / 2, resampled_destination.get()); 99 for (size_t i = 0; i < resampler.ChunkSize() / 2; ++i) 100 ASSERT_FLOAT_EQ(resampled_destination[i], 0); 101 } 102 103 // Test flush resets the internal state properly. 104 TEST(SincResamplerTest, DISABLED_SetRatioBench) { 105 MockSource mock_source; 106 SincResampler resampler(kSampleRateRatio, SincResampler::kDefaultRequestSize, 107 &mock_source); 108 109 TickTime start = TickTime::Now(); 110 for (int i = 1; i < 10000; ++i) 111 resampler.SetRatio(1.0 / i); 112 double total_time_c_us = (TickTime::Now() - start).Microseconds(); 113 printf("SetRatio() took %.2fms.\n", total_time_c_us / 1000); 114 } 115 116 117 // Define platform independent function name for Convolve* tests. 118 #if defined(WEBRTC_ARCH_X86_FAMILY) 119 #define CONVOLVE_FUNC Convolve_SSE 120 #elif defined(WEBRTC_ARCH_ARM_V7) 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) 128 TEST(SincResamplerTest, Convolve) { 129 #if defined(WEBRTC_ARCH_X86_FAMILY) 130 ASSERT_TRUE(WebRtc_GetCPUInfo(kSSE2)); 131 #elif defined(WEBRTC_ARCH_ARM_V7) 132 ASSERT_TRUE(WebRtc_GetCPUFeaturesARM() & kCPUFeatureNEON); 133 #endif 134 135 // Initialize a dummy resampler. 136 MockSource mock_source; 137 SincResampler resampler(kSampleRateRatio, SincResampler::kDefaultRequestSize, 138 &mock_source); 139 140 // The optimized Convolve methods are slightly more precise than Convolve_C(), 141 // so comparison must be done using an epsilon. 142 static const double kEpsilon = 0.00000005; 143 144 // Use a kernel from SincResampler as input and kernel data, this has the 145 // benefit of already being properly sized and aligned for Convolve_SSE(). 146 double result = resampler.Convolve_C( 147 resampler.kernel_storage_.get(), resampler.kernel_storage_.get(), 148 resampler.kernel_storage_.get(), kKernelInterpolationFactor); 149 double result2 = resampler.CONVOLVE_FUNC( 150 resampler.kernel_storage_.get(), resampler.kernel_storage_.get(), 151 resampler.kernel_storage_.get(), kKernelInterpolationFactor); 152 EXPECT_NEAR(result2, result, kEpsilon); 153 154 // Test Convolve() w/ unaligned input pointer. 155 result = resampler.Convolve_C( 156 resampler.kernel_storage_.get() + 1, resampler.kernel_storage_.get(), 157 resampler.kernel_storage_.get(), kKernelInterpolationFactor); 158 result2 = resampler.CONVOLVE_FUNC( 159 resampler.kernel_storage_.get() + 1, resampler.kernel_storage_.get(), 160 resampler.kernel_storage_.get(), kKernelInterpolationFactor); 161 EXPECT_NEAR(result2, result, kEpsilon); 162 } 163 #endif 164 165 // Benchmark for the various Convolve() methods. Make sure to build with 166 // branding=Chrome so that RTC_DCHECKs are compiled out when benchmarking. 167 // Original benchmarks were run with --convolve-iterations=50000000. 168 TEST(SincResamplerTest, ConvolveBenchmark) { 169 // Initialize a dummy resampler. 170 MockSource mock_source; 171 SincResampler resampler(kSampleRateRatio, SincResampler::kDefaultRequestSize, 172 &mock_source); 173 174 // Retrieve benchmark iterations from command line. 175 // TODO(ajm): Reintroduce this as a command line option. 176 const int kConvolveIterations = 1000000; 177 178 printf("Benchmarking %d iterations:\n", kConvolveIterations); 179 180 // Benchmark Convolve_C(). 181 TickTime start = TickTime::Now(); 182 for (int i = 0; i < kConvolveIterations; ++i) { 183 resampler.Convolve_C( 184 resampler.kernel_storage_.get(), resampler.kernel_storage_.get(), 185 resampler.kernel_storage_.get(), kKernelInterpolationFactor); 186 } 187 double total_time_c_us = (TickTime::Now() - start).Microseconds(); 188 printf("Convolve_C took %.2fms.\n", total_time_c_us / 1000); 189 190 #if defined(CONVOLVE_FUNC) 191 #if defined(WEBRTC_ARCH_X86_FAMILY) 192 ASSERT_TRUE(WebRtc_GetCPUInfo(kSSE2)); 193 #elif defined(WEBRTC_ARCH_ARM_V7) 194 ASSERT_TRUE(WebRtc_GetCPUFeaturesARM() & kCPUFeatureNEON); 195 #endif 196 197 // Benchmark with unaligned input pointer. 198 start = TickTime::Now(); 199 for (int j = 0; j < kConvolveIterations; ++j) { 200 resampler.CONVOLVE_FUNC( 201 resampler.kernel_storage_.get() + 1, resampler.kernel_storage_.get(), 202 resampler.kernel_storage_.get(), kKernelInterpolationFactor); 203 } 204 double total_time_optimized_unaligned_us = 205 (TickTime::Now() - start).Microseconds(); 206 printf(STRINGIZE(CONVOLVE_FUNC) "(unaligned) took %.2fms; which is %.2fx " 207 "faster than Convolve_C.\n", total_time_optimized_unaligned_us / 1000, 208 total_time_c_us / total_time_optimized_unaligned_us); 209 210 // Benchmark with aligned input pointer. 211 start = TickTime::Now(); 212 for (int j = 0; j < kConvolveIterations; ++j) { 213 resampler.CONVOLVE_FUNC( 214 resampler.kernel_storage_.get(), resampler.kernel_storage_.get(), 215 resampler.kernel_storage_.get(), kKernelInterpolationFactor); 216 } 217 double total_time_optimized_aligned_us = 218 (TickTime::Now() - start).Microseconds(); 219 printf(STRINGIZE(CONVOLVE_FUNC) " (aligned) took %.2fms; which is %.2fx " 220 "faster than Convolve_C and %.2fx faster than " 221 STRINGIZE(CONVOLVE_FUNC) " (unaligned).\n", 222 total_time_optimized_aligned_us / 1000, 223 total_time_c_us / total_time_optimized_aligned_us, 224 total_time_optimized_unaligned_us / total_time_optimized_aligned_us); 225 #endif 226 } 227 228 #undef CONVOLVE_FUNC 229 230 typedef std::tr1::tuple<int, int, double, double> SincResamplerTestData; 231 class SincResamplerTest 232 : public testing::TestWithParam<SincResamplerTestData> { 233 public: 234 SincResamplerTest() 235 : input_rate_(std::tr1::get<0>(GetParam())), 236 output_rate_(std::tr1::get<1>(GetParam())), 237 rms_error_(std::tr1::get<2>(GetParam())), 238 low_freq_error_(std::tr1::get<3>(GetParam())) { 239 } 240 241 virtual ~SincResamplerTest() {} 242 243 protected: 244 int input_rate_; 245 int output_rate_; 246 double rms_error_; 247 double low_freq_error_; 248 }; 249 250 // Tests resampling using a given input and output sample rate. 251 TEST_P(SincResamplerTest, Resample) { 252 // Make comparisons using one second of data. 253 static const double kTestDurationSecs = 1; 254 const size_t input_samples = 255 static_cast<size_t>(kTestDurationSecs * input_rate_); 256 const size_t output_samples = 257 static_cast<size_t>(kTestDurationSecs * output_rate_); 258 259 // Nyquist frequency for the input sampling rate. 260 const double input_nyquist_freq = 0.5 * input_rate_; 261 262 // Source for data to be resampled. 263 SinusoidalLinearChirpSource resampler_source( 264 input_rate_, input_samples, input_nyquist_freq, 0); 265 266 const double io_ratio = input_rate_ / static_cast<double>(output_rate_); 267 SincResampler resampler(io_ratio, SincResampler::kDefaultRequestSize, 268 &resampler_source); 269 270 // Force an update to the sample rate ratio to ensure dyanmic sample rate 271 // changes are working correctly. 272 rtc::scoped_ptr<float[]> kernel(new float[SincResampler::kKernelStorageSize]); 273 memcpy(kernel.get(), resampler.get_kernel_for_testing(), 274 SincResampler::kKernelStorageSize); 275 resampler.SetRatio(M_PI); 276 ASSERT_NE(0, memcmp(kernel.get(), resampler.get_kernel_for_testing(), 277 SincResampler::kKernelStorageSize)); 278 resampler.SetRatio(io_ratio); 279 ASSERT_EQ(0, memcmp(kernel.get(), resampler.get_kernel_for_testing(), 280 SincResampler::kKernelStorageSize)); 281 282 // TODO(dalecurtis): If we switch to AVX/SSE optimization, we'll need to 283 // allocate these on 32-byte boundaries and ensure they're sized % 32 bytes. 284 rtc::scoped_ptr<float[]> resampled_destination(new float[output_samples]); 285 rtc::scoped_ptr<float[]> pure_destination(new float[output_samples]); 286 287 // Generate resampled signal. 288 resampler.Resample(output_samples, resampled_destination.get()); 289 290 // Generate pure signal. 291 SinusoidalLinearChirpSource pure_source( 292 output_rate_, output_samples, input_nyquist_freq, 0); 293 pure_source.Run(output_samples, pure_destination.get()); 294 295 // Range of the Nyquist frequency (0.5 * min(input rate, output_rate)) which 296 // we refer to as low and high. 297 static const double kLowFrequencyNyquistRange = 0.7; 298 static const double kHighFrequencyNyquistRange = 0.9; 299 300 // Calculate Root-Mean-Square-Error and maximum error for the resampling. 301 double sum_of_squares = 0; 302 double low_freq_max_error = 0; 303 double high_freq_max_error = 0; 304 int minimum_rate = std::min(input_rate_, output_rate_); 305 double low_frequency_range = kLowFrequencyNyquistRange * 0.5 * minimum_rate; 306 double high_frequency_range = kHighFrequencyNyquistRange * 0.5 * minimum_rate; 307 for (size_t i = 0; i < output_samples; ++i) { 308 double error = fabs(resampled_destination[i] - pure_destination[i]); 309 310 if (pure_source.Frequency(i) < low_frequency_range) { 311 if (error > low_freq_max_error) 312 low_freq_max_error = error; 313 } else if (pure_source.Frequency(i) < high_frequency_range) { 314 if (error > high_freq_max_error) 315 high_freq_max_error = error; 316 } 317 // TODO(dalecurtis): Sanity check frequencies > kHighFrequencyNyquistRange. 318 319 sum_of_squares += error * error; 320 } 321 322 double rms_error = sqrt(sum_of_squares / output_samples); 323 324 // Convert each error to dbFS. 325 #define DBFS(x) 20 * log10(x) 326 rms_error = DBFS(rms_error); 327 low_freq_max_error = DBFS(low_freq_max_error); 328 high_freq_max_error = DBFS(high_freq_max_error); 329 330 EXPECT_LE(rms_error, rms_error_); 331 EXPECT_LE(low_freq_max_error, low_freq_error_); 332 333 // All conversions currently have a high frequency error around -6 dbFS. 334 static const double kHighFrequencyMaxError = -6.02; 335 EXPECT_LE(high_freq_max_error, kHighFrequencyMaxError); 336 } 337 338 // Almost all conversions have an RMS error of around -14 dbFS. 339 static const double kResamplingRMSError = -14.58; 340 341 // Thresholds chosen arbitrarily based on what each resampling reported during 342 // testing. All thresholds are in dbFS, http://en.wikipedia.org/wiki/DBFS. 343 INSTANTIATE_TEST_CASE_P( 344 SincResamplerTest, SincResamplerTest, testing::Values( 345 // To 44.1kHz 346 std::tr1::make_tuple(8000, 44100, kResamplingRMSError, -62.73), 347 std::tr1::make_tuple(11025, 44100, kResamplingRMSError, -72.19), 348 std::tr1::make_tuple(16000, 44100, kResamplingRMSError, -62.54), 349 std::tr1::make_tuple(22050, 44100, kResamplingRMSError, -73.53), 350 std::tr1::make_tuple(32000, 44100, kResamplingRMSError, -63.32), 351 std::tr1::make_tuple(44100, 44100, kResamplingRMSError, -73.53), 352 std::tr1::make_tuple(48000, 44100, -15.01, -64.04), 353 std::tr1::make_tuple(96000, 44100, -18.49, -25.51), 354 std::tr1::make_tuple(192000, 44100, -20.50, -13.31), 355 356 // To 48kHz 357 std::tr1::make_tuple(8000, 48000, kResamplingRMSError, -63.43), 358 std::tr1::make_tuple(11025, 48000, kResamplingRMSError, -62.61), 359 std::tr1::make_tuple(16000, 48000, kResamplingRMSError, -63.96), 360 std::tr1::make_tuple(22050, 48000, kResamplingRMSError, -62.42), 361 std::tr1::make_tuple(32000, 48000, kResamplingRMSError, -64.04), 362 std::tr1::make_tuple(44100, 48000, kResamplingRMSError, -62.63), 363 std::tr1::make_tuple(48000, 48000, kResamplingRMSError, -73.52), 364 std::tr1::make_tuple(96000, 48000, -18.40, -28.44), 365 std::tr1::make_tuple(192000, 48000, -20.43, -14.11), 366 367 // To 96kHz 368 std::tr1::make_tuple(8000, 96000, kResamplingRMSError, -63.19), 369 std::tr1::make_tuple(11025, 96000, kResamplingRMSError, -62.61), 370 std::tr1::make_tuple(16000, 96000, kResamplingRMSError, -63.39), 371 std::tr1::make_tuple(22050, 96000, kResamplingRMSError, -62.42), 372 std::tr1::make_tuple(32000, 96000, kResamplingRMSError, -63.95), 373 std::tr1::make_tuple(44100, 96000, kResamplingRMSError, -62.63), 374 std::tr1::make_tuple(48000, 96000, kResamplingRMSError, -73.52), 375 std::tr1::make_tuple(96000, 96000, kResamplingRMSError, -73.52), 376 std::tr1::make_tuple(192000, 96000, kResamplingRMSError, -28.41), 377 378 // To 192kHz 379 std::tr1::make_tuple(8000, 192000, kResamplingRMSError, -63.10), 380 std::tr1::make_tuple(11025, 192000, kResamplingRMSError, -62.61), 381 std::tr1::make_tuple(16000, 192000, kResamplingRMSError, -63.14), 382 std::tr1::make_tuple(22050, 192000, kResamplingRMSError, -62.42), 383 std::tr1::make_tuple(32000, 192000, kResamplingRMSError, -63.38), 384 std::tr1::make_tuple(44100, 192000, kResamplingRMSError, -62.63), 385 std::tr1::make_tuple(48000, 192000, kResamplingRMSError, -73.44), 386 std::tr1::make_tuple(96000, 192000, kResamplingRMSError, -73.52), 387 std::tr1::make_tuple(192000, 192000, kResamplingRMSError, -73.52))); 388 389 } // namespace webrtc 390