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 namespace media { | |
19 | |
20 // Range of the Nyquist frequency (0.5 * min(input rate, output_rate)) which | |
Ami GONE FROM CHROMIUM
2012/07/10 03:23:00
All these consts could move south to above where t
DaleCurtis
2012/07/10 21:38:55
Done.
| |
21 // we refer to as low and high. | |
22 static const double kLowFrequencyNyquistRange = 0.7; | |
23 static const double kHighFrequencyNyquistRange = 0.9; | |
24 | |
25 // All conversions currently have a high frequency error of ~0.5. | |
26 static const double kHighFrequencyMaxError = 0.5; | |
27 | |
28 // Almost all conversions have an RMS error of ~0.19. | |
29 static const double kRMSError = 0.19; | |
Ami GONE FROM CHROMIUM
2012/07/10 03:23:00
This is a terrible variable name ;)
DaleCurtis
2012/07/10 21:38:55
Done.
| |
30 | |
31 // Helper class to ensure ChunkedResample() functions properly. | |
32 class MockSource { | |
33 public: | |
34 MOCK_METHOD2(ProvideInput, void(float* destination, int frames)); | |
35 }; | |
36 | |
37 // Fake audio source for testing the resampler. Generates a sinusoidal linear | |
38 // chirp (http://en.wikipedia.org/wiki/Chirp) which can be tuned to stress the | |
39 // resampler for the specific sample rate conversion being used. | |
40 class SinusoidalLinearChirpSource { | |
41 public: | |
42 SinusoidalLinearChirpSource(int sample_rate, int samples, | |
43 double max_frequency) | |
44 : sample_rate_(sample_rate), | |
45 total_samples_(samples), | |
46 max_frequency_(max_frequency), | |
47 current_index_(0) { | |
48 // Chirp rate. | |
49 double duration = static_cast<double>(total_samples_) / sample_rate_; | |
50 k_ = (max_frequency_ - kMinFrequency) / (2 * duration); | |
Chris Rogers
2012/07/10 17:37:11
I see you've tried to optimize the 0.5 factor into
DaleCurtis
2012/07/10 21:38:55
Done.
| |
51 } | |
52 | |
53 virtual ~SinusoidalLinearChirpSource() {} | |
54 | |
55 void ProvideInput(float* destination, int frames) { | |
56 for (int i = 0; i < frames; ++i, ++current_index_) { | |
57 // Filter out frequencies higher than Nyquist. | |
58 if (Frequency(current_index_) > 0.5 * sample_rate_) { | |
59 destination[i] = 0; | |
60 } else { | |
61 // Calculate time in seconds. | |
62 double t = static_cast<double>(current_index_) / sample_rate_; | |
63 | |
64 // Sinusoidal linear chirp. | |
65 destination[i] = sin(2 * M_PI * (kMinFrequency * t + k_ * t * t)); | |
66 } | |
67 } | |
68 } | |
69 | |
70 double Frequency(int position) { | |
71 return kMinFrequency + position * (max_frequency_ - kMinFrequency) | |
72 / total_samples_; | |
73 } | |
74 | |
75 private: | |
76 enum { | |
77 kMinFrequency = 5 | |
78 }; | |
79 | |
80 double sample_rate_; | |
81 int total_samples_; | |
82 double max_frequency_; | |
83 double k_; | |
84 int current_index_; | |
85 | |
86 DISALLOW_COPY_AND_ASSIGN(SinusoidalLinearChirpSource); | |
87 }; | |
88 | |
89 typedef std::tr1::tuple<int, int, double, double> SincResamplerTestData; | |
90 class SincResamplerTestCase | |
91 : public testing::TestWithParam<SincResamplerTestData> { | |
92 public: | |
93 SincResamplerTestCase() | |
94 : input_rate_(std::tr1::get<0>(GetParam())), | |
95 output_rate_(std::tr1::get<1>(GetParam())), | |
96 rms_error_(std::tr1::get<2>(GetParam())), | |
97 low_freq_error_(std::tr1::get<3>(GetParam())) { | |
98 } | |
99 | |
100 virtual ~SincResamplerTestCase() {} | |
101 | |
102 protected: | |
103 int input_rate_; | |
104 int output_rate_; | |
105 double rms_error_; | |
106 double low_freq_error_; | |
107 }; | |
108 | |
109 // Test requesting ChunkSize() frames only results in a single callback call. | |
Ami GONE FROM CHROMIUM
2012/07/10 03:23:00
Do you want to also assert that ChunkSize()+1 resu
DaleCurtis
2012/07/10 21:38:55
Done.
| |
110 TEST(SincResamplerTest, ChunkedResample) { | |
Ami GONE FROM CHROMIUM
2012/07/10 03:23:00
I'd put this above SRTC to clarify it's got nothin
DaleCurtis
2012/07/10 21:38:55
Done.
| |
111 MockSource mock_source; | |
112 EXPECT_CALL(mock_source, ProvideInput(testing::_, testing::_)).Times(1); | |
113 | |
114 // Choose a high ratio of input to output samples which will result in quick | |
115 // exhaustion of SincResampler's internal buffers. | |
116 static const double kSampleRateRatio = 192000.0f / 48000.0f; | |
Ami GONE FROM CHROMIUM
2012/07/10 03:23:00
It's strange that the RHS uses 'f's though the res
DaleCurtis
2012/07/10 21:38:55
Done.
| |
117 SincResampler resampler( | |
118 kSampleRateRatio, | |
119 base::Bind(&MockSource::ProvideInput, base::Unretained(&mock_source))); | |
120 | |
121 scoped_array<float> resampled_destination(new float[resampler.ChunkSize()]); | |
122 resampler.Resample(resampled_destination.get(), resampler.ChunkSize()); | |
123 } | |
124 | |
125 // Tests resampling using a given input and output sample rate. | |
126 TEST_P(SincResamplerTestCase, Resample) { | |
127 // Make comparisons using one second of data. | |
128 static const double kTestDurationSecs = 1; | |
129 int input_samples = kTestDurationSecs * input_rate_; | |
130 int output_samples = kTestDurationSecs * output_rate_; | |
131 | |
132 // Nyquist frequency for the input sampling rate. | |
133 double input_nyquist_freq = 0.5 * input_rate_; | |
134 | |
135 // Source for data to be resampled. | |
136 SinusoidalLinearChirpSource resampler_source( | |
137 input_rate_, input_samples, input_nyquist_freq); | |
138 | |
139 SincResampler resampler( | |
140 input_rate_ / static_cast<double>(output_rate_), | |
141 base::Bind(&SinusoidalLinearChirpSource::ProvideInput, | |
142 base::Unretained(&resampler_source))); | |
143 | |
144 // TODO(dalecurtis): If we switch to AVX/SSE optimization, we'll need to | |
145 // allocate these on 32-byte boundaries and ensure they're sized % 32 bytes. | |
146 scoped_array<float> resampled_destination(new float[output_samples]); | |
147 scoped_array<float> pure_destination(new float[output_samples]); | |
148 | |
149 // Generate resampled signal. | |
150 resampler.Resample(resampled_destination.get(), output_samples); | |
151 | |
152 // Generate pure signal. | |
153 SinusoidalLinearChirpSource pure_source( | |
154 output_rate_, output_samples, input_nyquist_freq); | |
155 pure_source.ProvideInput(pure_destination.get(), output_samples); | |
156 | |
157 | |
Chris Rogers
2012/07/10 17:37:11
nit: extra blank line
DaleCurtis
2012/07/10 21:38:55
Done.
| |
158 // Calculate Root-Mean-Square-Error and maximum error for the resampling. | |
159 double sum_of_squares = 0; | |
160 double low_freq_max_error = 0; | |
161 double high_freq_max_error = 0; | |
162 for (int i = 0; i < output_samples; ++i) { | |
163 double error = fabs(resampled_destination[i] - pure_destination[i]); | |
164 | |
165 if (pure_source.Frequency(i) < kLowFrequencyNyquistRange * 0.5 | |
166 * std::min(input_rate_, output_rate_)) { | |
167 if (error > low_freq_max_error) | |
168 low_freq_max_error = error; | |
169 } else if (pure_source.Frequency(i) < kHighFrequencyNyquistRange * 0.5 | |
170 * std::min(input_rate_, output_rate_)) { | |
171 if (error > high_freq_max_error) | |
172 high_freq_max_error = error; | |
173 } | |
Chris Rogers
2012/07/10 17:37:11
add TODO to sanity-check frequencies > kHighFreque
DaleCurtis
2012/07/10 21:38:55
Done. I'm still not sure I understand what to do t
| |
174 | |
175 sum_of_squares += error * error; | |
176 } | |
177 | |
178 double rms_error = sqrt(sum_of_squares / output_samples); | |
179 | |
180 EXPECT_LT(rms_error, rms_error_); | |
181 EXPECT_LT(low_freq_max_error, low_freq_error_); | |
182 EXPECT_LT(high_freq_max_error, kHighFrequencyMaxError); | |
183 } | |
184 | |
185 // TODO(dalecurtis): Thresholds should be made tighter once we switch to a | |
Ami GONE FROM CHROMIUM
2012/07/10 03:23:00
You used to have a comment to the effect that thes
Chris Rogers
2012/07/10 17:37:11
I agree. Just to clarify, the errors we're seeing
Chris Rogers
2012/07/10 17:37:11
Please add TODO that we could test the high freque
DaleCurtis
2012/07/10 21:38:55
Done.
DaleCurtis
2012/07/10 21:38:55
This is covered in the TODO above?
| |
186 // higher kernel size. | |
187 INSTANTIATE_TEST_CASE_P( | |
188 SincResamplerTest, SincResamplerTestCase, testing::Values( | |
189 // To 44.1kHz | |
190 std::tr1::make_tuple(8000, 44100, kRMSError, 0.00074), | |
Chris Rogers
2012/07/10 17:37:11
My preference would have been to have these aliasi
DaleCurtis
2012/07/10 21:38:55
Done.
| |
191 std::tr1::make_tuple(11025, 44100, kRMSError, 0.00025), | |
192 std::tr1::make_tuple(16000, 44100, kRMSError, 0.00075), | |
193 std::tr1::make_tuple(22050, 44100, kRMSError, 0.00022), | |
194 std::tr1::make_tuple(32000, 44100, kRMSError, 0.00069), | |
195 std::tr1::make_tuple(44100, 44100, kRMSError, 0.00022), | |
196 std::tr1::make_tuple(48000, 44100, 0.18, 0.00063), | |
197 std::tr1::make_tuple(96000, 44100, 0.12, 0.053), | |
198 std::tr1::make_tuple(192000, 44100, 0.095, 0.22), | |
199 | |
200 // To 48kHz | |
201 std::tr1::make_tuple(8000, 48000, kRMSError, 0.00068), | |
202 std::tr1::make_tuple(11025, 48000, kRMSError, 0.00075), | |
203 std::tr1::make_tuple(16000, 48000, kRMSError, 0.00064), | |
204 std::tr1::make_tuple(22050, 48000, kRMSError, 0.00076), | |
205 std::tr1::make_tuple(32000, 48000, kRMSError, 0.00063), | |
206 std::tr1::make_tuple(44100, 48000, kRMSError, 0.00074), | |
207 std::tr1::make_tuple(48000, 48000, kRMSError, 0.00022), | |
208 std::tr1::make_tuple(96000, 48000, 0.13, 0.038), | |
209 std::tr1::make_tuple(192000, 48000, 0.096, 0.20), | |
210 | |
211 // To 96kHz | |
212 std::tr1::make_tuple(8000, 96000, kRMSError, 0.00070), | |
213 std::tr1::make_tuple(11025, 96000, kRMSError, 0.00075), | |
214 std::tr1::make_tuple(16000, 96000, kRMSError, 0.00068), | |
215 std::tr1::make_tuple(22050, 96000, kRMSError, 0.00076), | |
216 std::tr1::make_tuple(32000, 96000, kRMSError, 0.00064), | |
217 std::tr1::make_tuple(44100, 96000, kRMSError, 0.00074), | |
218 std::tr1::make_tuple(48000, 96000, kRMSError, 0.00022), | |
219 std::tr1::make_tuple(96000, 96000, kRMSError, 0.00022), | |
220 std::tr1::make_tuple(192000, 96000, kRMSError, 0.038), | |
221 | |
222 // To 192kHz | |
223 std::tr1::make_tuple(8000, 192000, kRMSError, 0.00070), | |
224 std::tr1::make_tuple(11025, 192000, kRMSError, 0.00075), | |
225 std::tr1::make_tuple(16000, 192000, kRMSError, 0.00070), | |
226 std::tr1::make_tuple(22050, 192000, kRMSError, 0.00076), | |
227 std::tr1::make_tuple(32000, 192000, kRMSError, 0.00068), | |
228 std::tr1::make_tuple(44100, 192000, kRMSError, 0.00074), | |
229 std::tr1::make_tuple(48000, 192000, kRMSError, 0.00022), | |
230 std::tr1::make_tuple(96000, 192000, kRMSError, 0.00022), | |
231 std::tr1::make_tuple(192000, 192000, kRMSError, 0.00022))); | |
232 | |
233 } // namespace media | |
OLD | NEW |