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 |