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 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 |