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