OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 // MSVC++ requires this to be set before any other includes to get M_PI. |
| 6 #define _USE_MATH_DEFINES |
| 7 |
| 8 #include <cmath> |
| 9 |
| 10 #include "base/bind.h" |
| 11 #include "base/bind_helpers.h" |
| 12 #include "base/logging.h" |
| 13 #include "base/memory/scoped_ptr.h" |
| 14 #include "base/stringprintf.h" |
| 15 #include "media/base/sinc_resampler.h" |
| 16 #include "testing/gmock/include/gmock/gmock.h" |
| 17 #include "testing/gtest/include/gtest/gtest.h" |
| 18 |
| 19 using testing::_; |
| 20 |
| 21 namespace media { |
| 22 |
| 23 // Helper class to ensure ChunkedResample() functions properly. |
| 24 class MockSource { |
| 25 public: |
| 26 MOCK_METHOD2(ProvideInput, void(float* destination, int frames)); |
| 27 }; |
| 28 |
| 29 // Test requesting multiples of ChunkSize() frames results in the proper number |
| 30 // of callbacks. |
| 31 TEST(SincResamplerTest, ChunkedResample) { |
| 32 MockSource mock_source; |
| 33 |
| 34 // Choose a high ratio of input to output samples which will result in quick |
| 35 // exhaustion of SincResampler's internal buffers. |
| 36 static const double kSampleRateRatio = 192000.0 / 44100.0; |
| 37 SincResampler resampler( |
| 38 kSampleRateRatio, |
| 39 base::Bind(&MockSource::ProvideInput, base::Unretained(&mock_source))); |
| 40 |
| 41 static const int kChunks = 2; |
| 42 int max_chunk_size = resampler.ChunkSize() * kChunks; |
| 43 scoped_array<float> resampled_destination(new float[max_chunk_size]); |
| 44 |
| 45 // Verify requesting ChunkSize() frames causes a single callback. |
| 46 EXPECT_CALL(mock_source, ProvideInput(_, _)).Times(1); |
| 47 resampler.Resample(resampled_destination.get(), resampler.ChunkSize()); |
| 48 |
| 49 // Verify requesting kChunks * ChunkSize() frames causes kChunks callbacks. |
| 50 testing::Mock::VerifyAndClear(&mock_source); |
| 51 EXPECT_CALL(mock_source, ProvideInput(_, _)).Times(kChunks); |
| 52 resampler.Resample(resampled_destination.get(), max_chunk_size); |
| 53 } |
| 54 |
| 55 // Fake audio source for testing the resampler. Generates a sinusoidal linear |
| 56 // chirp (http://en.wikipedia.org/wiki/Chirp) which can be tuned to stress the |
| 57 // resampler for the specific sample rate conversion being used. |
| 58 class SinusoidalLinearChirpSource { |
| 59 public: |
| 60 SinusoidalLinearChirpSource(int sample_rate, int samples, |
| 61 double max_frequency) |
| 62 : sample_rate_(sample_rate), |
| 63 total_samples_(samples), |
| 64 max_frequency_(max_frequency), |
| 65 current_index_(0) { |
| 66 // Chirp rate. |
| 67 double duration = static_cast<double>(total_samples_) / sample_rate_; |
| 68 k_ = (max_frequency_ - kMinFrequency) / duration; |
| 69 } |
| 70 |
| 71 virtual ~SinusoidalLinearChirpSource() {} |
| 72 |
| 73 void ProvideInput(float* destination, int frames) { |
| 74 for (int i = 0; i < frames; ++i, ++current_index_) { |
| 75 // Filter out frequencies higher than Nyquist. |
| 76 if (Frequency(current_index_) > 0.5 * sample_rate_) { |
| 77 destination[i] = 0; |
| 78 } else { |
| 79 // Calculate time in seconds. |
| 80 double t = static_cast<double>(current_index_) / sample_rate_; |
| 81 |
| 82 // Sinusoidal linear chirp. |
| 83 destination[i] = sin(2 * M_PI * (kMinFrequency * t + (k_ / 2) * t * t)); |
| 84 } |
| 85 } |
| 86 } |
| 87 |
| 88 double Frequency(int position) { |
| 89 return kMinFrequency + position * (max_frequency_ - kMinFrequency) |
| 90 / total_samples_; |
| 91 } |
| 92 |
| 93 private: |
| 94 enum { |
| 95 kMinFrequency = 5 |
| 96 }; |
| 97 |
| 98 double sample_rate_; |
| 99 int total_samples_; |
| 100 double max_frequency_; |
| 101 double k_; |
| 102 int current_index_; |
| 103 |
| 104 DISALLOW_COPY_AND_ASSIGN(SinusoidalLinearChirpSource); |
| 105 }; |
| 106 |
| 107 typedef std::tr1::tuple<int, int, double, double> SincResamplerTestData; |
| 108 class SincResamplerTestCase |
| 109 : public testing::TestWithParam<SincResamplerTestData> { |
| 110 public: |
| 111 SincResamplerTestCase() |
| 112 : input_rate_(std::tr1::get<0>(GetParam())), |
| 113 output_rate_(std::tr1::get<1>(GetParam())), |
| 114 rms_error_(std::tr1::get<2>(GetParam())), |
| 115 low_freq_error_(std::tr1::get<3>(GetParam())) { |
| 116 } |
| 117 |
| 118 virtual ~SincResamplerTestCase() {} |
| 119 |
| 120 protected: |
| 121 int input_rate_; |
| 122 int output_rate_; |
| 123 double rms_error_; |
| 124 double low_freq_error_; |
| 125 }; |
| 126 |
| 127 // Tests resampling using a given input and output sample rate. |
| 128 TEST_P(SincResamplerTestCase, Resample) { |
| 129 // Make comparisons using one second of data. |
| 130 static const double kTestDurationSecs = 1; |
| 131 int input_samples = kTestDurationSecs * input_rate_; |
| 132 int output_samples = kTestDurationSecs * output_rate_; |
| 133 |
| 134 // Nyquist frequency for the input sampling rate. |
| 135 double input_nyquist_freq = 0.5 * input_rate_; |
| 136 |
| 137 // Source for data to be resampled. |
| 138 SinusoidalLinearChirpSource resampler_source( |
| 139 input_rate_, input_samples, input_nyquist_freq); |
| 140 |
| 141 SincResampler resampler( |
| 142 input_rate_ / static_cast<double>(output_rate_), |
| 143 base::Bind(&SinusoidalLinearChirpSource::ProvideInput, |
| 144 base::Unretained(&resampler_source))); |
| 145 |
| 146 // TODO(dalecurtis): If we switch to AVX/SSE optimization, we'll need to |
| 147 // allocate these on 32-byte boundaries and ensure they're sized % 32 bytes. |
| 148 scoped_array<float> resampled_destination(new float[output_samples]); |
| 149 scoped_array<float> pure_destination(new float[output_samples]); |
| 150 |
| 151 // Generate resampled signal. |
| 152 resampler.Resample(resampled_destination.get(), output_samples); |
| 153 |
| 154 // Generate pure signal. |
| 155 SinusoidalLinearChirpSource pure_source( |
| 156 output_rate_, output_samples, input_nyquist_freq); |
| 157 pure_source.ProvideInput(pure_destination.get(), output_samples); |
| 158 |
| 159 // Range of the Nyquist frequency (0.5 * min(input rate, output_rate)) which |
| 160 // we refer to as low and high. |
| 161 static const double kLowFrequencyNyquistRange = 0.7; |
| 162 static const double kHighFrequencyNyquistRange = 0.9; |
| 163 |
| 164 // Calculate Root-Mean-Square-Error and maximum error for the resampling. |
| 165 double sum_of_squares = 0; |
| 166 double low_freq_max_error = 0; |
| 167 double high_freq_max_error = 0; |
| 168 int minimum_rate = std::min(input_rate_, output_rate_); |
| 169 double low_frequency_range = kLowFrequencyNyquistRange * 0.5 * minimum_rate; |
| 170 double high_frequency_range = kHighFrequencyNyquistRange * 0.5 * minimum_rate; |
| 171 for (int i = 0; i < output_samples; ++i) { |
| 172 double error = fabs(resampled_destination[i] - pure_destination[i]); |
| 173 |
| 174 if (pure_source.Frequency(i) < low_frequency_range) { |
| 175 if (error > low_freq_max_error) |
| 176 low_freq_max_error = error; |
| 177 } else if (pure_source.Frequency(i) < high_frequency_range) { |
| 178 if (error > high_freq_max_error) |
| 179 high_freq_max_error = error; |
| 180 } |
| 181 // TODO(dalecurtis): Sanity check frequencies > kHighFrequencyNyquistRange. |
| 182 |
| 183 sum_of_squares += error * error; |
| 184 } |
| 185 |
| 186 double rms_error = sqrt(sum_of_squares / output_samples); |
| 187 |
| 188 // Convert each error to dbFS. |
| 189 #define DBFS(x) 20 * log10(x) |
| 190 rms_error = DBFS(rms_error); |
| 191 low_freq_max_error = DBFS(low_freq_max_error); |
| 192 high_freq_max_error = DBFS(high_freq_max_error); |
| 193 |
| 194 EXPECT_LE(rms_error, rms_error_); |
| 195 EXPECT_LE(low_freq_max_error, low_freq_error_); |
| 196 |
| 197 // All conversions currently have a high frequency error around -6 dbFS. |
| 198 static const double kHighFrequencyMaxError = -6.02; |
| 199 EXPECT_LE(high_freq_max_error, kHighFrequencyMaxError); |
| 200 } |
| 201 |
| 202 // Almost all conversions have an RMS error of around -14 dbFS. |
| 203 static const double kResamplingRMSError = -14.58; |
| 204 |
| 205 // Thresholds chosen arbitrarily based on what each resampling reported during |
| 206 // testing. All thresholds are in dbFS, http://en.wikipedia.org/wiki/DBFS. |
| 207 INSTANTIATE_TEST_CASE_P( |
| 208 SincResamplerTest, SincResamplerTestCase, testing::Values( |
| 209 // To 44.1kHz |
| 210 std::tr1::make_tuple(8000, 44100, kResamplingRMSError, -62.73), |
| 211 std::tr1::make_tuple(11025, 44100, kResamplingRMSError, -72.19), |
| 212 std::tr1::make_tuple(16000, 44100, kResamplingRMSError, -62.54), |
| 213 std::tr1::make_tuple(22050, 44100, kResamplingRMSError, -73.53), |
| 214 std::tr1::make_tuple(32000, 44100, kResamplingRMSError, -63.32), |
| 215 std::tr1::make_tuple(44100, 44100, kResamplingRMSError, -73.53), |
| 216 std::tr1::make_tuple(48000, 44100, -15.01, -64.04), |
| 217 std::tr1::make_tuple(96000, 44100, -18.49, -25.51), |
| 218 std::tr1::make_tuple(192000, 44100, -20.50, -13.31), |
| 219 |
| 220 // To 48kHz |
| 221 std::tr1::make_tuple(8000, 48000, kResamplingRMSError, -63.43), |
| 222 std::tr1::make_tuple(11025, 48000, kResamplingRMSError, -62.61), |
| 223 std::tr1::make_tuple(16000, 48000, kResamplingRMSError, -63.96), |
| 224 std::tr1::make_tuple(22050, 48000, kResamplingRMSError, -62.42), |
| 225 std::tr1::make_tuple(32000, 48000, kResamplingRMSError, -64.04), |
| 226 std::tr1::make_tuple(44100, 48000, kResamplingRMSError, -62.63), |
| 227 std::tr1::make_tuple(48000, 48000, kResamplingRMSError, -73.52), |
| 228 std::tr1::make_tuple(96000, 48000, -18.40, -28.44), |
| 229 std::tr1::make_tuple(192000, 48000, -20.43, -14.11), |
| 230 |
| 231 // To 96kHz |
| 232 std::tr1::make_tuple(8000, 96000, kResamplingRMSError, -63.19), |
| 233 std::tr1::make_tuple(11025, 96000, kResamplingRMSError, -62.61), |
| 234 std::tr1::make_tuple(16000, 96000, kResamplingRMSError, -63.39), |
| 235 std::tr1::make_tuple(22050, 96000, kResamplingRMSError, -62.42), |
| 236 std::tr1::make_tuple(32000, 96000, kResamplingRMSError, -63.95), |
| 237 std::tr1::make_tuple(44100, 96000, kResamplingRMSError, -62.63), |
| 238 std::tr1::make_tuple(48000, 96000, kResamplingRMSError, -73.52), |
| 239 std::tr1::make_tuple(96000, 96000, kResamplingRMSError, -73.52), |
| 240 std::tr1::make_tuple(192000, 96000, kResamplingRMSError, -28.41), |
| 241 |
| 242 // To 192kHz |
| 243 std::tr1::make_tuple(8000, 192000, kResamplingRMSError, -63.10), |
| 244 std::tr1::make_tuple(11025, 192000, kResamplingRMSError, -62.61), |
| 245 std::tr1::make_tuple(16000, 192000, kResamplingRMSError, -63.14), |
| 246 std::tr1::make_tuple(22050, 192000, kResamplingRMSError, -62.42), |
| 247 std::tr1::make_tuple(32000, 192000, kResamplingRMSError, -63.38), |
| 248 std::tr1::make_tuple(44100, 192000, kResamplingRMSError, -62.63), |
| 249 std::tr1::make_tuple(48000, 192000, kResamplingRMSError, -73.44), |
| 250 std::tr1::make_tuple(96000, 192000, kResamplingRMSError, -73.52), |
| 251 std::tr1::make_tuple(192000, 192000, kResamplingRMSError, -73.52))); |
| 252 |
| 253 } // namespace media |
OLD | NEW |