summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authordalecurtis@chromium.org <dalecurtis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-11 21:58:08 +0000
committerdalecurtis@chromium.org <dalecurtis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-11 21:58:08 +0000
commita5e97e2b8623e66712abca17945f940c0f2c6591 (patch)
tree9c04e04c599afcd9427fc7d6bf0dbc24b100a884 /media
parent47b11873a6a9ac3a59c3f22a5410317048266fff (diff)
downloadchromium_src-a5e97e2b8623e66712abca17945f940c0f2c6591.zip
chromium_src-a5e97e2b8623e66712abca17945f940c0f2c6591.tar.gz
chromium_src-a5e97e2b8623e66712abca17945f940c0f2c6591.tar.bz2
Add SincResampler ported from WebKit.
This is a partial port of WebAudio's SincResampler from WebKit formatted and culled for use by Chrome Media. We can't directly use the one in WebKit as it's layed under a ton of abstraction and is tightly coupled with WebKit objects. Test generates a swept sine wave and calculates the RMS error for common sample rates (via UMA stats). MultiChannelResampler and AudioRenderMixer changes to support resampling will come in later CLs. The 1000 ft view is that MultiChannelResampler will implement SincResampler:: AudioSourceProvider and AudioRendererMixer will implement a new MultiChannelResampler::MultiChannelAudioSourceProvider interface. When resampling is necessary AudioRenderMixer will feed itself into a MultiChannelResampler instance which will poll data as necessary and feed it channel by channel into a set of SincResamplers (one for each channel). We want to resample post-mixing since resampling is a much more expensive operation. Original for reference: http://git.chromium.org/gitweb/?p=external/Webkit.git&a=blob&f=Source/WebCore/platform/audio/SincResampler.cpp Visual plot of 44100 to 48000 for reference; red line is the resampled signal and the green line is the reference signal. Ideally only the pure signal (green) should be seen. Any bit of the resampled signal showing (red) is where the resampling algorithm is incorrect: http://i.imgur.com/1vsaI.png BUG=133637 TEST=New unittests. Review URL: https://chromiumcodereview.appspot.com/10702050 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@146219 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r--media/base/sinc_resampler.cc227
-rw-r--r--media/base/sinc_resampler.h71
-rw-r--r--media/base/sinc_resampler_unittest.cc253
-rw-r--r--media/media.gyp3
4 files changed, 554 insertions, 0 deletions
diff --git a/media/base/sinc_resampler.cc b/media/base/sinc_resampler.cc
new file mode 100644
index 0000000..88e6204
--- /dev/null
+++ b/media/base/sinc_resampler.cc
@@ -0,0 +1,227 @@
+// 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.
+//
+// Input buffer layout, dividing the total buffer into regions (r0_ - r5_):
+//
+// |----------------|-----------------------------------------|----------------|
+//
+// kBlockSize + kKernelSize / 2
+// <--------------------------------------------------------->
+// r0_
+//
+// kKernelSize / 2 kKernelSize / 2 kKernelSize / 2 kKernelSize / 2
+// <---------------> <---------------> <---------------> <--------------->
+// r1_ r2_ r3_ r4_
+//
+// kBlockSize
+// <--------------------------------------->
+// r5_
+//
+// The algorithm:
+//
+// 1) Consume input frames into r0_ (r1_ is zero-initialized).
+// 2) Position kernel centered at start of r0_ (r2_) and generate output frames
+// until kernel is centered at start of r4_ or we've finished generating all
+// the output frames.
+// 3) Copy r3_ to r1_ and r4_ to r2_.
+// 4) Consume input frames into r5_ (zero-pad if we run out of input).
+// 5) Goto (2) until all of input is consumed.
+//
+// Note: we're glossing over how the sub-sample handling works with
+// |virtual_source_idx_|, etc.
+
+// MSVC++ requires this to be set before any other includes to get M_PI.
+#define _USE_MATH_DEFINES
+
+#include "media/base/sinc_resampler.h"
+
+#include <cmath>
+
+#include "base/logging.h"
+
+namespace media {
+
+enum {
+ // The kernel size can be adjusted for quality (higher is better) at the
+ // expense of performance. Must be an even number.
+ // TODO(dalecurtis): Test performance to see if we can jack this up to 64+.
+ kKernelSize = 32,
+
+ // The number of destination frames generated per processing pass. Affects
+ // how often and for how much SincResampler calls back for input. Must be
+ // greater than kKernelSize.
+ kBlockSize = 512,
+
+ // The kernel offset count is used for interpolation and is the number of
+ // sub-sample kernel shifts. Can be adjusted for quality (higher is better)
+ // at the expense of allocating more memory.
+ kKernelOffsetCount = 32,
+ kKernelStorageSize = kKernelSize * (kKernelOffsetCount + 1),
+
+ // The size (in samples) of the internal buffer used by the resampler.
+ kBufferSize = kBlockSize + kKernelSize
+};
+
+SincResampler::SincResampler(double io_sample_rate_ratio, const ReadCB& read_cb)
+ : io_sample_rate_ratio_(io_sample_rate_ratio),
+ virtual_source_idx_(0),
+ buffer_primed_(false),
+ read_cb_(read_cb),
+ // TODO(dalecurtis): When we switch to AVX/SSE optimization, we'll need to
+ // allocate with 32-byte alignment and ensure they're sized % 32 bytes.
+ kernel_storage_(new float[kKernelStorageSize]),
+ input_buffer_(new float[kBufferSize]),
+ // Setup various region pointers in the buffer (see diagram above).
+ r0_(input_buffer_.get() + kKernelSize / 2),
+ r1_(input_buffer_.get()),
+ r2_(r0_),
+ r3_(r0_ + kBlockSize - kKernelSize / 2),
+ r4_(r0_ + kBlockSize),
+ r5_(r0_ + kKernelSize / 2) {
+ DCHECK_EQ(kKernelSize % 2, 0) << "kKernelSize must be even!";
+ DCHECK_GT(kBlockSize, kKernelSize)
+ << "kBlockSize must be greater than kKernelSize!";
+ // Basic sanity checks to ensure buffer regions are laid out correctly:
+ // r0_ and r2_ should always be the same position.
+ DCHECK_EQ(r0_, r2_);
+ // r1_ at the beginning of the buffer.
+ DCHECK_EQ(r1_, input_buffer_.get());
+ // r1_ left of r2_, r2_ left of r5_ and r1_, r2_ size correct.
+ DCHECK_EQ(r2_ - r1_, r5_ - r2_);
+ // r3_ left of r4_, r5_ left of r0_ and r3_ size correct.
+ DCHECK_EQ(r4_ - r3_, r5_ - r0_);
+ // r3_, r4_ size correct and r4_ at the end of the buffer.
+ DCHECK_EQ(r4_ + (r4_ - r3_), r1_ + kBufferSize);
+ // r5_ size correct and at the end of the buffer.
+ DCHECK_EQ(r5_ + kBlockSize, r1_ + kBufferSize);
+
+ memset(kernel_storage_.get(), 0,
+ sizeof(*kernel_storage_.get()) * kKernelStorageSize);
+ memset(input_buffer_.get(), 0, sizeof(*input_buffer_.get()) * kBufferSize);
+
+ InitializeKernel();
+}
+
+SincResampler::~SincResampler() {}
+
+void SincResampler::InitializeKernel() {
+ // Blackman window parameters.
+ static const double kAlpha = 0.16;
+ static const double kA0 = 0.5 * (1.0 - kAlpha);
+ static const double kA1 = 0.5;
+ static const double kA2 = 0.5 * kAlpha;
+
+ // |sinc_scale_factor| is basically the normalized cutoff frequency of the
+ // low-pass filter.
+ double sinc_scale_factor =
+ io_sample_rate_ratio_ > 1.0 ? 1.0 / io_sample_rate_ratio_ : 1.0;
+
+ // The sinc function is an idealized brick-wall filter, but since we're
+ // windowing it the transition from pass to stop does not happen right away.
+ // So we should adjust the low pass filter cutoff slightly downward to avoid
+ // some aliasing at the very high-end.
+ // TODO(crogers): this value is empirical and to be more exact should vary
+ // depending on kKernelSize.
+ sinc_scale_factor *= 0.9;
+
+ // Generates a set of windowed sinc() kernels.
+ // We generate a range of sub-sample offsets from 0.0 to 1.0.
+ for (int offset_idx = 0; offset_idx <= kKernelOffsetCount; ++offset_idx) {
+ double subsample_offset =
+ static_cast<double>(offset_idx) / kKernelOffsetCount;
+
+ for (int i = 0; i < kKernelSize; ++i) {
+ // Compute the sinc with offset.
+ double s =
+ sinc_scale_factor * M_PI * (i - kKernelSize / 2 - subsample_offset);
+ double sinc = (!s ? 1.0 : sin(s) / s) * sinc_scale_factor;
+
+ // Compute Blackman window, matching the offset of the sinc().
+ double x = (i - subsample_offset) / kKernelSize;
+ double window = kA0 - kA1 * cos(2.0 * M_PI * x) + kA2
+ * cos(4.0 * M_PI * x);
+
+ // Window the sinc() function and store at the correct offset.
+ kernel_storage_[i + offset_idx * kKernelSize] = sinc * window;
+ }
+ }
+}
+
+void SincResampler::Resample(float* destination, int frames) {
+ int remaining_frames = frames;
+
+ // Step (1) -- Prime the input buffer at the start of the input stream.
+ if (!buffer_primed_) {
+ read_cb_.Run(r0_, kBlockSize + kKernelSize / 2);
+ buffer_primed_ = true;
+ }
+
+ // Step (2) -- Resample!
+ while (remaining_frames) {
+ while (virtual_source_idx_ < kBlockSize) {
+ // |virtual_source_idx_| lies in between two kernel offsets so figure out
+ // what they are.
+ int source_idx = static_cast<int>(virtual_source_idx_);
+ double subsample_remainder = virtual_source_idx_ - source_idx;
+
+ double virtual_offset_idx = subsample_remainder * kKernelOffsetCount;
+ int offset_idx = static_cast<int>(virtual_offset_idx);
+
+ float* k1 = kernel_storage_.get() + offset_idx * kKernelSize;
+ float* k2 = k1 + kKernelSize;
+
+ // Initialize input pointer based on quantized |virtual_source_idx_|.
+ float* input_ptr = r1_ + source_idx;
+
+ // We'll compute "convolutions" for the two kernels which straddle
+ // |virtual_source_idx_|.
+ float sum1 = 0;
+ float sum2 = 0;
+
+ // Figure out how much to weight each kernel's "convolution".
+ double kernel_interpolation_factor = virtual_offset_idx - offset_idx;
+
+ // Generate a single output sample.
+ int n = kKernelSize;
+ float input;
+ // TODO(dalecurtis): For initial commit, I've ripped out all the SSE
+ // optimizations, these definitely need to go back in before release.
+ while (n--) {
+ input = *input_ptr++;
+ sum1 += input * *k1++;
+ sum2 += input * *k2++;
+ }
+
+ // Linearly interpolate the two "convolutions".
+ double result = (1.0 - kernel_interpolation_factor) * sum1
+ + kernel_interpolation_factor * sum2;
+
+ *destination++ = result;
+
+ // Advance the virtual index.
+ virtual_source_idx_ += io_sample_rate_ratio_;
+
+ if (!--remaining_frames)
+ return;
+ }
+
+ // Wrap back around to the start.
+ virtual_source_idx_ -= kBlockSize;
+
+ // Step (3) Copy r3_ to r1_ and r4_ to r2_.
+ // This wraps the last input frames back to the start of the buffer.
+ memcpy(r1_, r3_, sizeof(*input_buffer_.get()) * (kKernelSize / 2));
+ memcpy(r2_, r4_, sizeof(*input_buffer_.get()) * (kKernelSize / 2));
+
+ // Step (4)
+ // Refresh the buffer with more input.
+ read_cb_.Run(r5_, kBlockSize);
+ }
+}
+
+int SincResampler::ChunkSize() {
+ return kBlockSize / io_sample_rate_ratio_;
+}
+
+} // namespace media
diff --git a/media/base/sinc_resampler.h b/media/base/sinc_resampler.h
new file mode 100644
index 0000000..58f5c2d
--- /dev/null
+++ b/media/base/sinc_resampler.h
@@ -0,0 +1,71 @@
+// 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.
+
+#ifndef MEDIA_BASE_SINC_RESAMPLER_H_
+#define MEDIA_BASE_SINC_RESAMPLER_H_
+
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+// SincResampler is a high-quality single-channel sample-rate converter.
+class MEDIA_EXPORT SincResampler {
+ public:
+ // Callback type for providing more data into the resampler. Expects |frames|
+ // of data to be rendered into |destination|; zero padded if not enough frames
+ // are available to satisfy the request.
+ typedef base::Callback<void(float* destination, int frames)> ReadCB;
+
+ // Constructs a SincResampler with the specified |read_cb|, which is used to
+ // acquire audio data for resampling. |io_sample_rate_ratio| is the ratio of
+ // input / output sample rates.
+ SincResampler(double io_sample_rate_ratio, const ReadCB& read_cb);
+ virtual ~SincResampler();
+
+ // Resample |frames| of data from |read_cb_| into |destination|.
+ void Resample(float* destination, int frames);
+
+ // The maximum size in frames that guarantees Resample() will only make a
+ // single call to |read_cb_| for more data.
+ int ChunkSize();
+
+ private:
+ void InitializeKernel();
+
+ // The ratio of input / output sample rates.
+ double io_sample_rate_ratio_;
+
+ // An index on the source input buffer with sub-sample precision. It must be
+ // double precision to avoid drift.
+ double virtual_source_idx_;
+
+ // The buffer is primed once at the very beginning of processing.
+ bool buffer_primed_;
+
+ // Source of data for resampling.
+ ReadCB read_cb_;
+
+ // Contains kKernelOffsetCount kernels back-to-back, each of size kKernelSize.
+ // The kernel offsets are sub-sample shifts of a windowed sinc shifted from
+ // 0.0 to 1.0 sample.
+ scoped_array<float> kernel_storage_;
+
+ // Data from the source is copied into this buffer for each processing pass.
+ scoped_array<float> input_buffer_;
+
+ // Pointers to the various regions inside |input_buffer_|. See the diagram at
+ // the top of the .cc file for more information.
+ float* const r0_;
+ float* const r1_;
+ float* const r2_;
+ float* const r3_;
+ float* const r4_;
+ float* const r5_;
+};
+
+} // namespace media
+
+#endif // MEDIA_BASE_SINC_RESAMPLER_H_
diff --git a/media/base/sinc_resampler_unittest.cc b/media/base/sinc_resampler_unittest.cc
new file mode 100644
index 0000000..9b3cd38
--- /dev/null
+++ b/media/base/sinc_resampler_unittest.cc
@@ -0,0 +1,253 @@
+// 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"
+
+using testing::_;
+
+namespace media {
+
+// Helper class to ensure ChunkedResample() functions properly.
+class MockSource {
+ public:
+ MOCK_METHOD2(ProvideInput, void(float* destination, int frames));
+};
+
+// Test requesting multiples of ChunkSize() frames results in the proper number
+// of callbacks.
+TEST(SincResamplerTest, ChunkedResample) {
+ MockSource mock_source;
+
+ // 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.0 / 44100.0;
+ SincResampler resampler(
+ kSampleRateRatio,
+ base::Bind(&MockSource::ProvideInput, base::Unretained(&mock_source)));
+
+ static const int kChunks = 2;
+ int max_chunk_size = resampler.ChunkSize() * kChunks;
+ scoped_array<float> resampled_destination(new float[max_chunk_size]);
+
+ // Verify requesting ChunkSize() frames causes a single callback.
+ EXPECT_CALL(mock_source, ProvideInput(_, _)).Times(1);
+ resampler.Resample(resampled_destination.get(), resampler.ChunkSize());
+
+ // Verify requesting kChunks * ChunkSize() frames causes kChunks callbacks.
+ testing::Mock::VerifyAndClear(&mock_source);
+ EXPECT_CALL(mock_source, ProvideInput(_, _)).Times(kChunks);
+ resampler.Resample(resampled_destination.get(), max_chunk_size);
+}
+
+// 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) / duration;
+ }
+
+ 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_ / 2) * 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_;
+};
+
+// 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);
+
+ // Range of the Nyquist frequency (0.5 * min(input rate, output_rate)) which
+ // we refer to as low and high.
+ static const double kLowFrequencyNyquistRange = 0.7;
+ static const double kHighFrequencyNyquistRange = 0.9;
+
+ // 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;
+ int minimum_rate = std::min(input_rate_, output_rate_);
+ double low_frequency_range = kLowFrequencyNyquistRange * 0.5 * minimum_rate;
+ double high_frequency_range = kHighFrequencyNyquistRange * 0.5 * minimum_rate;
+ for (int i = 0; i < output_samples; ++i) {
+ double error = fabs(resampled_destination[i] - pure_destination[i]);
+
+ if (pure_source.Frequency(i) < low_frequency_range) {
+ if (error > low_freq_max_error)
+ low_freq_max_error = error;
+ } else if (pure_source.Frequency(i) < high_frequency_range) {
+ if (error > high_freq_max_error)
+ high_freq_max_error = error;
+ }
+ // TODO(dalecurtis): Sanity check frequencies > kHighFrequencyNyquistRange.
+
+ sum_of_squares += error * error;
+ }
+
+ double rms_error = sqrt(sum_of_squares / output_samples);
+
+ // Convert each error to dbFS.
+ #define DBFS(x) 20 * log10(x)
+ rms_error = DBFS(rms_error);
+ low_freq_max_error = DBFS(low_freq_max_error);
+ high_freq_max_error = DBFS(high_freq_max_error);
+
+ EXPECT_LE(rms_error, rms_error_);
+ EXPECT_LE(low_freq_max_error, low_freq_error_);
+
+ // All conversions currently have a high frequency error around -6 dbFS.
+ static const double kHighFrequencyMaxError = -6.02;
+ EXPECT_LE(high_freq_max_error, kHighFrequencyMaxError);
+}
+
+// Almost all conversions have an RMS error of around -14 dbFS.
+static const double kResamplingRMSError = -14.58;
+
+// Thresholds chosen arbitrarily based on what each resampling reported during
+// testing. All thresholds are in dbFS, http://en.wikipedia.org/wiki/DBFS.
+INSTANTIATE_TEST_CASE_P(
+ SincResamplerTest, SincResamplerTestCase, testing::Values(
+ // To 44.1kHz
+ std::tr1::make_tuple(8000, 44100, kResamplingRMSError, -62.73),
+ std::tr1::make_tuple(11025, 44100, kResamplingRMSError, -72.19),
+ std::tr1::make_tuple(16000, 44100, kResamplingRMSError, -62.54),
+ std::tr1::make_tuple(22050, 44100, kResamplingRMSError, -73.53),
+ std::tr1::make_tuple(32000, 44100, kResamplingRMSError, -63.32),
+ std::tr1::make_tuple(44100, 44100, kResamplingRMSError, -73.53),
+ std::tr1::make_tuple(48000, 44100, -15.01, -64.04),
+ std::tr1::make_tuple(96000, 44100, -18.49, -25.51),
+ std::tr1::make_tuple(192000, 44100, -20.50, -13.31),
+
+ // To 48kHz
+ std::tr1::make_tuple(8000, 48000, kResamplingRMSError, -63.43),
+ std::tr1::make_tuple(11025, 48000, kResamplingRMSError, -62.61),
+ std::tr1::make_tuple(16000, 48000, kResamplingRMSError, -63.96),
+ std::tr1::make_tuple(22050, 48000, kResamplingRMSError, -62.42),
+ std::tr1::make_tuple(32000, 48000, kResamplingRMSError, -64.04),
+ std::tr1::make_tuple(44100, 48000, kResamplingRMSError, -62.63),
+ std::tr1::make_tuple(48000, 48000, kResamplingRMSError, -73.52),
+ std::tr1::make_tuple(96000, 48000, -18.40, -28.44),
+ std::tr1::make_tuple(192000, 48000, -20.43, -14.11),
+
+ // To 96kHz
+ std::tr1::make_tuple(8000, 96000, kResamplingRMSError, -63.19),
+ std::tr1::make_tuple(11025, 96000, kResamplingRMSError, -62.61),
+ std::tr1::make_tuple(16000, 96000, kResamplingRMSError, -63.39),
+ std::tr1::make_tuple(22050, 96000, kResamplingRMSError, -62.42),
+ std::tr1::make_tuple(32000, 96000, kResamplingRMSError, -63.95),
+ std::tr1::make_tuple(44100, 96000, kResamplingRMSError, -62.63),
+ std::tr1::make_tuple(48000, 96000, kResamplingRMSError, -73.52),
+ std::tr1::make_tuple(96000, 96000, kResamplingRMSError, -73.52),
+ std::tr1::make_tuple(192000, 96000, kResamplingRMSError, -28.41),
+
+ // To 192kHz
+ std::tr1::make_tuple(8000, 192000, kResamplingRMSError, -63.10),
+ std::tr1::make_tuple(11025, 192000, kResamplingRMSError, -62.61),
+ std::tr1::make_tuple(16000, 192000, kResamplingRMSError, -63.14),
+ std::tr1::make_tuple(22050, 192000, kResamplingRMSError, -62.42),
+ std::tr1::make_tuple(32000, 192000, kResamplingRMSError, -63.38),
+ std::tr1::make_tuple(44100, 192000, kResamplingRMSError, -62.63),
+ std::tr1::make_tuple(48000, 192000, kResamplingRMSError, -73.44),
+ std::tr1::make_tuple(96000, 192000, kResamplingRMSError, -73.52),
+ std::tr1::make_tuple(192000, 192000, kResamplingRMSError, -73.52)));
+
+} // namespace media
diff --git a/media/media.gyp b/media/media.gyp
index c73c314..085d0f8 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -189,6 +189,8 @@
'base/ranges.h',
'base/seekable_buffer.cc',
'base/seekable_buffer.h',
+ 'base/sinc_resampler.cc',
+ 'base/sinc_resampler.h',
'base/state_matrix.cc',
'base/state_matrix.h',
'base/stream_parser.cc',
@@ -686,6 +688,7 @@
'base/ranges_unittest.cc',
'base/run_all_unittests.cc',
'base/seekable_buffer_unittest.cc',
+ 'base/sinc_resampler_unittest.cc',
'base/state_matrix_unittest.cc',
'base/test_data_util.cc',
'base/test_data_util.h',