andrew@webrtc.org | dea537b | 2013-04-26 14:56:51 +0000 | [diff] [blame] | 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 | #include <cmath> |
| 12 | |
| 13 | #include "testing/gmock/include/gmock/gmock.h" |
| 14 | #include "testing/gtest/include/gtest/gtest.h" |
| 15 | #include "webrtc/common_audio/resampler/push_sinc_resampler.h" |
| 16 | #include "webrtc/common_audio/resampler/sinusoidal_linear_chirp_source.h" |
| 17 | #include "webrtc/system_wrappers/interface/scoped_ptr.h" |
| 18 | #include "webrtc/typedefs.h" |
| 19 | |
| 20 | namespace webrtc { |
| 21 | |
| 22 | typedef std::tr1::tuple<int, int, double, double> PushSincResamplerTestData; |
| 23 | class PushSincResamplerTest |
| 24 | : public testing::TestWithParam<PushSincResamplerTestData> { |
| 25 | public: |
| 26 | PushSincResamplerTest() |
| 27 | : input_rate_(std::tr1::get<0>(GetParam())), |
| 28 | output_rate_(std::tr1::get<1>(GetParam())), |
| 29 | rms_error_(std::tr1::get<2>(GetParam())), |
| 30 | low_freq_error_(std::tr1::get<3>(GetParam())) { |
| 31 | } |
| 32 | |
| 33 | virtual ~PushSincResamplerTest() {} |
| 34 | |
| 35 | protected: |
| 36 | int input_rate_; |
| 37 | int output_rate_; |
| 38 | double rms_error_; |
| 39 | double low_freq_error_; |
| 40 | }; |
| 41 | |
| 42 | // Tests resampling using a given input and output sample rate. |
| 43 | TEST_P(PushSincResamplerTest, Resample) { |
| 44 | // Make comparisons using one second of data. |
| 45 | static const double kTestDurationSecs = 1; |
| 46 | // 10 ms blocks. |
| 47 | const int kNumBlocks = kTestDurationSecs * 100; |
| 48 | const int input_block_size = input_rate_ / 100; |
| 49 | const int output_block_size = output_rate_ / 100; |
| 50 | const int input_samples = kTestDurationSecs * input_rate_; |
| 51 | const int output_samples = kTestDurationSecs * output_rate_; |
| 52 | |
| 53 | // Nyquist frequency for the input sampling rate. |
| 54 | const double input_nyquist_freq = 0.5 * input_rate_; |
| 55 | |
| 56 | // Source for data to be resampled. |
| 57 | SinusoidalLinearChirpSource resampler_source( |
| 58 | input_rate_, input_samples, input_nyquist_freq, 0); |
| 59 | |
| 60 | PushSincResampler resampler(input_block_size, output_block_size); |
| 61 | |
| 62 | // TODO(dalecurtis): If we switch to AVX/SSE optimization, we'll need to |
| 63 | // allocate these on 32-byte boundaries and ensure they're sized % 32 bytes. |
| 64 | scoped_array<float> resampled_destination(new float[output_samples]); |
| 65 | scoped_array<float> pure_destination(new float[output_samples]); |
| 66 | scoped_array<float> source(new float[input_samples]); |
| 67 | scoped_array<int16_t> source_int(new int16_t[input_block_size]); |
| 68 | scoped_array<int16_t> destination_int(new int16_t[output_block_size]); |
| 69 | |
| 70 | // Generate resampled signal. |
| 71 | // With the PushSincResampler, we produce the signal block-by-10ms-block |
| 72 | // rather than in a single pass, to exercise how it will be used in WebRTC. |
| 73 | resampler_source.Run(source.get(), input_samples); |
| 74 | for (int i = 0; i < kNumBlocks; ++i) { |
| 75 | for (int j = 0; j < input_block_size; ++j) { |
| 76 | source_int[j] = static_cast<int16_t>(std::floor(32767 * |
| 77 | source[i * input_block_size + j] + 0.5)); |
| 78 | } |
| 79 | EXPECT_EQ(output_block_size, |
| 80 | resampler.Resample(source_int.get(), input_block_size, |
| 81 | destination_int.get(), output_block_size)); |
| 82 | for (int j = 0; j < output_block_size; ++j) { |
| 83 | resampled_destination[i * output_block_size + j] = |
| 84 | static_cast<float>(destination_int[j]) / 32767; |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | // Generate pure signal. |
| 89 | // The sinc resampler has an implicit delay of half the kernel size (32) at |
| 90 | // the input sample rate. By moving to a push model, this delay becomes |
| 91 | // explicit and is managed by zero-stuffing in PushSincResampler. This delay |
| 92 | // can be a fractional sample amount, so we deal with it in the test by |
| 93 | // delaying the "pure" source to match. |
| 94 | static const int kInputKernelDelaySamples = 16; |
| 95 | double output_delay_samples = static_cast<double>(output_rate_) |
| 96 | / input_rate_ * kInputKernelDelaySamples; |
| 97 | SinusoidalLinearChirpSource pure_source( |
| 98 | output_rate_, output_samples, input_nyquist_freq, output_delay_samples); |
| 99 | pure_source.Run(pure_destination.get(), output_samples); |
| 100 | |
| 101 | // Range of the Nyquist frequency (0.5 * min(input rate, output_rate)) which |
| 102 | // we refer to as low and high. |
| 103 | static const double kLowFrequencyNyquistRange = 0.7; |
| 104 | static const double kHighFrequencyNyquistRange = 0.9; |
| 105 | |
| 106 | // Calculate Root-Mean-Square-Error and maximum error for the resampling. |
| 107 | double sum_of_squares = 0; |
| 108 | double low_freq_max_error = 0; |
| 109 | double high_freq_max_error = 0; |
| 110 | int minimum_rate = std::min(input_rate_, output_rate_); |
| 111 | double low_frequency_range = kLowFrequencyNyquistRange * 0.5 * minimum_rate; |
| 112 | double high_frequency_range = kHighFrequencyNyquistRange * 0.5 * minimum_rate; |
| 113 | |
| 114 | for (int i = 0; i < output_samples; ++i) { |
| 115 | double error = fabs(resampled_destination[i] - pure_destination[i]); |
| 116 | |
| 117 | if (pure_source.Frequency(i) < low_frequency_range) { |
| 118 | if (error > low_freq_max_error) |
| 119 | low_freq_max_error = error; |
| 120 | } else if (pure_source.Frequency(i) < high_frequency_range) { |
| 121 | if (error > high_freq_max_error) |
| 122 | high_freq_max_error = error; |
| 123 | } |
| 124 | // TODO(dalecurtis): Sanity check frequencies > kHighFrequencyNyquistRange. |
| 125 | |
| 126 | sum_of_squares += error * error; |
| 127 | } |
| 128 | |
| 129 | double rms_error = sqrt(sum_of_squares / output_samples); |
| 130 | |
| 131 | // Convert each error to dbFS. |
| 132 | #define DBFS(x) 20 * log10(x) |
| 133 | rms_error = DBFS(rms_error); |
| 134 | // In order to keep the thresholds in this test identical to SincResamplerTest |
| 135 | // we must account for the quantization error introduced by truncating from |
| 136 | // float to int. This happens twice (once at input and once at output) and we |
| 137 | // allow for the maximum possible error (1 / 32767) for each step. |
| 138 | // |
| 139 | // The quantization error is insignificant in the RMS calculation so does not |
| 140 | // need to be accounted for there. |
| 141 | low_freq_max_error = DBFS(low_freq_max_error - 2.0 / 32767); |
| 142 | high_freq_max_error = DBFS(high_freq_max_error - 2.0 / 32767); |
| 143 | |
| 144 | EXPECT_LE(rms_error, rms_error_); |
| 145 | EXPECT_LE(low_freq_max_error, low_freq_error_); |
| 146 | |
| 147 | // All conversions currently have a high frequency error around -6 dbFS. |
| 148 | static const double kHighFrequencyMaxError = -6.02; |
| 149 | EXPECT_LE(high_freq_max_error, kHighFrequencyMaxError); |
| 150 | } |
| 151 | |
| 152 | // Almost all conversions have an RMS error of around -14 dbFS. |
| 153 | static const double kResamplingRMSError = -14.42; |
| 154 | |
| 155 | // Thresholds chosen arbitrarily based on what each resampling reported during |
| 156 | // testing. All thresholds are in dbFS, http://en.wikipedia.org/wiki/DBFS. |
| 157 | INSTANTIATE_TEST_CASE_P( |
| 158 | PushSincResamplerTest, PushSincResamplerTest, testing::Values( |
| 159 | // First run through the rates tested in SincResamplerTest. The |
| 160 | // thresholds are identical. |
| 161 | // |
| 162 | // We don't test rates which fail to provide an integer number of |
| 163 | // samples in a 10 ms block (22050 and 11025 Hz). WebRTC doesn't support |
| 164 | // these rates in any case (for the same reason). |
| 165 | |
| 166 | // To 44.1kHz |
| 167 | std::tr1::make_tuple(8000, 44100, kResamplingRMSError, -62.73), |
| 168 | std::tr1::make_tuple(16000, 44100, kResamplingRMSError, -62.54), |
| 169 | std::tr1::make_tuple(32000, 44100, kResamplingRMSError, -63.32), |
| 170 | std::tr1::make_tuple(44100, 44100, kResamplingRMSError, -73.53), |
| 171 | std::tr1::make_tuple(48000, 44100, -15.01, -64.04), |
| 172 | std::tr1::make_tuple(96000, 44100, -18.49, -25.51), |
| 173 | std::tr1::make_tuple(192000, 44100, -20.50, -13.31), |
| 174 | |
| 175 | // To 48kHz |
| 176 | std::tr1::make_tuple(8000, 48000, kResamplingRMSError, -63.43), |
| 177 | std::tr1::make_tuple(16000, 48000, kResamplingRMSError, -63.96), |
| 178 | std::tr1::make_tuple(32000, 48000, kResamplingRMSError, -64.04), |
| 179 | std::tr1::make_tuple(44100, 48000, kResamplingRMSError, -62.63), |
| 180 | std::tr1::make_tuple(48000, 48000, kResamplingRMSError, -73.52), |
| 181 | std::tr1::make_tuple(96000, 48000, -18.40, -28.44), |
| 182 | std::tr1::make_tuple(192000, 48000, -20.43, -14.11), |
| 183 | |
| 184 | // To 96kHz |
| 185 | std::tr1::make_tuple(8000, 96000, kResamplingRMSError, -63.19), |
| 186 | std::tr1::make_tuple(16000, 96000, kResamplingRMSError, -63.39), |
| 187 | std::tr1::make_tuple(32000, 96000, kResamplingRMSError, -63.95), |
| 188 | std::tr1::make_tuple(44100, 96000, kResamplingRMSError, -62.63), |
| 189 | std::tr1::make_tuple(48000, 96000, kResamplingRMSError, -73.52), |
| 190 | std::tr1::make_tuple(96000, 96000, kResamplingRMSError, -73.52), |
| 191 | std::tr1::make_tuple(192000, 96000, kResamplingRMSError, -28.41), |
| 192 | |
| 193 | // To 192kHz |
| 194 | std::tr1::make_tuple(8000, 192000, kResamplingRMSError, -63.10), |
| 195 | std::tr1::make_tuple(16000, 192000, kResamplingRMSError, -63.14), |
| 196 | std::tr1::make_tuple(32000, 192000, kResamplingRMSError, -63.38), |
| 197 | std::tr1::make_tuple(44100, 192000, kResamplingRMSError, -62.63), |
| 198 | std::tr1::make_tuple(48000, 192000, kResamplingRMSError, -73.44), |
| 199 | std::tr1::make_tuple(96000, 192000, kResamplingRMSError, -73.52), |
| 200 | std::tr1::make_tuple(192000, 192000, kResamplingRMSError, -73.52), |
| 201 | |
| 202 | // Next run through some additional cases interesting for WebRTC. |
| 203 | // We skip some extreme downsampled cases (192 -> {8, 16}, 96 -> 8) |
| 204 | // because they violate |kHighFrequencyMaxError|, which is not |
| 205 | // unexpected. It's very unlikely that we'll see these conversions in |
| 206 | // practice anyway. |
| 207 | |
| 208 | // To 8 kHz |
| 209 | std::tr1::make_tuple(8000, 8000, kResamplingRMSError, -75.51), |
| 210 | std::tr1::make_tuple(16000, 8000, -18.56, -28.79), |
| 211 | std::tr1::make_tuple(32000, 8000, -20.36, -14.13), |
| 212 | std::tr1::make_tuple(44100, 8000, -21.00, -11.39), |
| 213 | std::tr1::make_tuple(48000, 8000, -20.96, -11.04), |
| 214 | |
| 215 | // To 16 kHz |
| 216 | std::tr1::make_tuple(8000, 16000, kResamplingRMSError, -70.30), |
| 217 | std::tr1::make_tuple(16000, 16000, kResamplingRMSError, -75.51), |
| 218 | std::tr1::make_tuple(32000, 16000, -18.48, -28.59), |
| 219 | std::tr1::make_tuple(44100, 16000, -19.59, -19.77), |
| 220 | std::tr1::make_tuple(48000, 16000, -20.01, -18.11), |
| 221 | std::tr1::make_tuple(96000, 16000, -20.95, -10.99), |
| 222 | |
| 223 | // To 32 kHz |
| 224 | std::tr1::make_tuple(8000, 32000, kResamplingRMSError, -70.30), |
| 225 | std::tr1::make_tuple(16000, 32000, kResamplingRMSError, -75.51), |
| 226 | std::tr1::make_tuple(32000, 32000, kResamplingRMSError, -75.56), |
| 227 | std::tr1::make_tuple(44100, 32000, -16.52, -51.10), |
| 228 | std::tr1::make_tuple(48000, 32000, -16.90, -44.17), |
| 229 | std::tr1::make_tuple(96000, 32000, -19.80, -18.05), |
| 230 | std::tr1::make_tuple(192000, 32000, -21.02, -10.94))); |
| 231 | |
| 232 | } // namespace webrtc |