Index: media/base/sinc_resampler_unittest.cc |
diff --git a/media/base/sinc_resampler_unittest.cc b/media/base/sinc_resampler_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..34c7fc379a2cf1a240d4c17b2a068461b661ccb7 |
--- /dev/null |
+++ b/media/base/sinc_resampler_unittest.cc |
@@ -0,0 +1,233 @@ |
+// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+// MSVC++ requires this to be set before any other includes to get M_PI. |
+#define _USE_MATH_DEFINES |
+ |
+#include <cmath> |
+ |
+#include "base/bind.h" |
+#include "base/bind_helpers.h" |
+#include "base/logging.h" |
+#include "base/memory/scoped_ptr.h" |
+#include "base/stringprintf.h" |
+#include "media/base/sinc_resampler.h" |
+#include "testing/gmock/include/gmock/gmock.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+namespace media { |
+ |
+// 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.
|
+// we refer to as low and high. |
+static const double kLowFrequencyNyquistRange = 0.7; |
+static const double kHighFrequencyNyquistRange = 0.9; |
+ |
+// All conversions currently have a high frequency error of ~0.5. |
+static const double kHighFrequencyMaxError = 0.5; |
+ |
+// Almost all conversions have an RMS error of ~0.19. |
+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.
|
+ |
+// Helper class to ensure ChunkedResample() functions properly. |
+class MockSource { |
+ public: |
+ MOCK_METHOD2(ProvideInput, void(float* destination, int frames)); |
+}; |
+ |
+// Fake audio source for testing the resampler. Generates a sinusoidal linear |
+// chirp (http://en.wikipedia.org/wiki/Chirp) which can be tuned to stress the |
+// resampler for the specific sample rate conversion being used. |
+class SinusoidalLinearChirpSource { |
+ public: |
+ SinusoidalLinearChirpSource(int sample_rate, int samples, |
+ double max_frequency) |
+ : sample_rate_(sample_rate), |
+ total_samples_(samples), |
+ max_frequency_(max_frequency), |
+ current_index_(0) { |
+ // Chirp rate. |
+ double duration = static_cast<double>(total_samples_) / sample_rate_; |
+ 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.
|
+ } |
+ |
+ virtual ~SinusoidalLinearChirpSource() {} |
+ |
+ void ProvideInput(float* destination, int frames) { |
+ for (int i = 0; i < frames; ++i, ++current_index_) { |
+ // Filter out frequencies higher than Nyquist. |
+ if (Frequency(current_index_) > 0.5 * sample_rate_) { |
+ destination[i] = 0; |
+ } else { |
+ // Calculate time in seconds. |
+ double t = static_cast<double>(current_index_) / sample_rate_; |
+ |
+ // Sinusoidal linear chirp. |
+ destination[i] = sin(2 * M_PI * (kMinFrequency * t + k_ * t * t)); |
+ } |
+ } |
+ } |
+ |
+ double Frequency(int position) { |
+ return kMinFrequency + position * (max_frequency_ - kMinFrequency) |
+ / total_samples_; |
+ } |
+ |
+ private: |
+ enum { |
+ kMinFrequency = 5 |
+ }; |
+ |
+ double sample_rate_; |
+ int total_samples_; |
+ double max_frequency_; |
+ double k_; |
+ int current_index_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(SinusoidalLinearChirpSource); |
+}; |
+ |
+typedef std::tr1::tuple<int, int, double, double> SincResamplerTestData; |
+class SincResamplerTestCase |
+ : public testing::TestWithParam<SincResamplerTestData> { |
+ public: |
+ SincResamplerTestCase() |
+ : input_rate_(std::tr1::get<0>(GetParam())), |
+ output_rate_(std::tr1::get<1>(GetParam())), |
+ rms_error_(std::tr1::get<2>(GetParam())), |
+ low_freq_error_(std::tr1::get<3>(GetParam())) { |
+ } |
+ |
+ virtual ~SincResamplerTestCase() {} |
+ |
+ protected: |
+ int input_rate_; |
+ int output_rate_; |
+ double rms_error_; |
+ double low_freq_error_; |
+}; |
+ |
+// 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.
|
+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.
|
+ MockSource mock_source; |
+ EXPECT_CALL(mock_source, ProvideInput(testing::_, testing::_)).Times(1); |
+ |
+ // Choose a high ratio of input to output samples which will result in quick |
+ // exhaustion of SincResampler's internal buffers. |
+ 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.
|
+ SincResampler resampler( |
+ kSampleRateRatio, |
+ base::Bind(&MockSource::ProvideInput, base::Unretained(&mock_source))); |
+ |
+ scoped_array<float> resampled_destination(new float[resampler.ChunkSize()]); |
+ resampler.Resample(resampled_destination.get(), resampler.ChunkSize()); |
+} |
+ |
+// Tests resampling using a given input and output sample rate. |
+TEST_P(SincResamplerTestCase, Resample) { |
+ // Make comparisons using one second of data. |
+ static const double kTestDurationSecs = 1; |
+ int input_samples = kTestDurationSecs * input_rate_; |
+ int output_samples = kTestDurationSecs * output_rate_; |
+ |
+ // Nyquist frequency for the input sampling rate. |
+ double input_nyquist_freq = 0.5 * input_rate_; |
+ |
+ // Source for data to be resampled. |
+ SinusoidalLinearChirpSource resampler_source( |
+ input_rate_, input_samples, input_nyquist_freq); |
+ |
+ SincResampler resampler( |
+ input_rate_ / static_cast<double>(output_rate_), |
+ base::Bind(&SinusoidalLinearChirpSource::ProvideInput, |
+ base::Unretained(&resampler_source))); |
+ |
+ // TODO(dalecurtis): If we switch to AVX/SSE optimization, we'll need to |
+ // allocate these on 32-byte boundaries and ensure they're sized % 32 bytes. |
+ scoped_array<float> resampled_destination(new float[output_samples]); |
+ scoped_array<float> pure_destination(new float[output_samples]); |
+ |
+ // Generate resampled signal. |
+ resampler.Resample(resampled_destination.get(), output_samples); |
+ |
+ // Generate pure signal. |
+ SinusoidalLinearChirpSource pure_source( |
+ output_rate_, output_samples, input_nyquist_freq); |
+ pure_source.ProvideInput(pure_destination.get(), output_samples); |
+ |
+ |
Chris Rogers
2012/07/10 17:37:11
nit: extra blank line
DaleCurtis
2012/07/10 21:38:55
Done.
|
+ // Calculate Root-Mean-Square-Error and maximum error for the resampling. |
+ double sum_of_squares = 0; |
+ double low_freq_max_error = 0; |
+ double high_freq_max_error = 0; |
+ for (int i = 0; i < output_samples; ++i) { |
+ double error = fabs(resampled_destination[i] - pure_destination[i]); |
+ |
+ if (pure_source.Frequency(i) < kLowFrequencyNyquistRange * 0.5 |
+ * std::min(input_rate_, output_rate_)) { |
+ if (error > low_freq_max_error) |
+ low_freq_max_error = error; |
+ } else if (pure_source.Frequency(i) < kHighFrequencyNyquistRange * 0.5 |
+ * std::min(input_rate_, output_rate_)) { |
+ if (error > high_freq_max_error) |
+ high_freq_max_error = error; |
+ } |
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
|
+ |
+ sum_of_squares += error * error; |
+ } |
+ |
+ double rms_error = sqrt(sum_of_squares / output_samples); |
+ |
+ EXPECT_LT(rms_error, rms_error_); |
+ EXPECT_LT(low_freq_max_error, low_freq_error_); |
+ EXPECT_LT(high_freq_max_error, kHighFrequencyMaxError); |
+} |
+ |
+// 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?
|
+// higher kernel size. |
+INSTANTIATE_TEST_CASE_P( |
+ SincResamplerTest, SincResamplerTestCase, testing::Values( |
+ // To 44.1kHz |
+ 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.
|
+ std::tr1::make_tuple(11025, 44100, kRMSError, 0.00025), |
+ std::tr1::make_tuple(16000, 44100, kRMSError, 0.00075), |
+ std::tr1::make_tuple(22050, 44100, kRMSError, 0.00022), |
+ std::tr1::make_tuple(32000, 44100, kRMSError, 0.00069), |
+ std::tr1::make_tuple(44100, 44100, kRMSError, 0.00022), |
+ std::tr1::make_tuple(48000, 44100, 0.18, 0.00063), |
+ std::tr1::make_tuple(96000, 44100, 0.12, 0.053), |
+ std::tr1::make_tuple(192000, 44100, 0.095, 0.22), |
+ |
+ // To 48kHz |
+ std::tr1::make_tuple(8000, 48000, kRMSError, 0.00068), |
+ std::tr1::make_tuple(11025, 48000, kRMSError, 0.00075), |
+ std::tr1::make_tuple(16000, 48000, kRMSError, 0.00064), |
+ std::tr1::make_tuple(22050, 48000, kRMSError, 0.00076), |
+ std::tr1::make_tuple(32000, 48000, kRMSError, 0.00063), |
+ std::tr1::make_tuple(44100, 48000, kRMSError, 0.00074), |
+ std::tr1::make_tuple(48000, 48000, kRMSError, 0.00022), |
+ std::tr1::make_tuple(96000, 48000, 0.13, 0.038), |
+ std::tr1::make_tuple(192000, 48000, 0.096, 0.20), |
+ |
+ // To 96kHz |
+ std::tr1::make_tuple(8000, 96000, kRMSError, 0.00070), |
+ std::tr1::make_tuple(11025, 96000, kRMSError, 0.00075), |
+ std::tr1::make_tuple(16000, 96000, kRMSError, 0.00068), |
+ std::tr1::make_tuple(22050, 96000, kRMSError, 0.00076), |
+ std::tr1::make_tuple(32000, 96000, kRMSError, 0.00064), |
+ std::tr1::make_tuple(44100, 96000, kRMSError, 0.00074), |
+ std::tr1::make_tuple(48000, 96000, kRMSError, 0.00022), |
+ std::tr1::make_tuple(96000, 96000, kRMSError, 0.00022), |
+ std::tr1::make_tuple(192000, 96000, kRMSError, 0.038), |
+ |
+ // To 192kHz |
+ std::tr1::make_tuple(8000, 192000, kRMSError, 0.00070), |
+ std::tr1::make_tuple(11025, 192000, kRMSError, 0.00075), |
+ std::tr1::make_tuple(16000, 192000, kRMSError, 0.00070), |
+ std::tr1::make_tuple(22050, 192000, kRMSError, 0.00076), |
+ std::tr1::make_tuple(32000, 192000, kRMSError, 0.00068), |
+ std::tr1::make_tuple(44100, 192000, kRMSError, 0.00074), |
+ std::tr1::make_tuple(48000, 192000, kRMSError, 0.00022), |
+ std::tr1::make_tuple(96000, 192000, kRMSError, 0.00022), |
+ std::tr1::make_tuple(192000, 192000, kRMSError, 0.00022))); |
+ |
+} // namespace media |