Add a float interface to PushSincResampler.

Provides a push interface to SincResampler without the int16->float
overhead. This is required to support resampling in the new
AudioProcessing float path.

BUG=2894
TESTED=unit tests
R=turaj@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/9529004

git-svn-id: http://webrtc.googlecode.com/svn/trunk@5673 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
andrew@webrtc.org
2014-03-10 18:51:42 +00:00
parent bc206eadb8
commit d32797f853
3 changed files with 109 additions and 46 deletions

View File

@ -9,22 +9,22 @@
*/
#include "webrtc/common_audio/include/audio_util.h"
#include "webrtc/common_audio/resampler/push_sinc_resampler.h"
#include <string.h>
#include "webrtc/common_audio/resampler/push_sinc_resampler.h"
namespace webrtc {
PushSincResampler::PushSincResampler(int source_frames,
int destination_frames)
PushSincResampler::PushSincResampler(int source_frames, int destination_frames)
: resampler_(new SincResampler(source_frames * 1.0 / destination_frames,
source_frames, this)),
float_buffer_(new float[destination_frames]),
source_frames,
this)),
source_ptr_(NULL),
source_ptr_int_(NULL),
destination_frames_(destination_frames),
first_pass_(true),
source_available_(0) {
}
source_available_(0) {}
PushSincResampler::~PushSincResampler() {
}
@ -33,6 +33,21 @@ int PushSincResampler::Resample(const int16_t* source,
int source_length,
int16_t* destination,
int destination_capacity) {
if (!float_buffer_.get())
float_buffer_.reset(new float[destination_frames_]);
source_ptr_int_ = source;
// Pass NULL as the float source to have Run() read from the int16 source.
Resample(NULL, source_length, float_buffer_.get(), destination_frames_);
RoundToInt16(float_buffer_.get(), destination_frames_, destination);
source_ptr_int_ = NULL;
return destination_frames_;
}
int PushSincResampler::Resample(const float* source,
int source_length,
float* destination,
int destination_capacity) {
assert(source_length == resampler_->request_frames());
assert(destination_capacity >= destination_frames_);
// Cache the source pointer. Calling Resample() will immediately trigger
@ -54,16 +69,14 @@ int PushSincResampler::Resample(const int16_t* source,
// request in order to prime the buffer with a single Run() request for
// |source_frames|.
if (first_pass_)
resampler_->Resample(resampler_->ChunkSize(), float_buffer_.get());
resampler_->Resample(resampler_->ChunkSize(), destination);
resampler_->Resample(destination_frames_, float_buffer_.get());
RoundToInt16(float_buffer_.get(), destination_frames_, destination);
resampler_->Resample(destination_frames_, destination);
source_ptr_ = NULL;
return destination_frames_;
}
void PushSincResampler::Run(int frames, float* destination) {
assert(source_ptr_ != NULL);
// Ensure we are only asked for the available samples. This would fail if
// Run() was triggered more than once per Resample() call.
assert(source_available_ == frames);
@ -73,11 +86,16 @@ void PushSincResampler::Run(int frames, float* destination) {
// discarded, as described in Resample().
memset(destination, 0, frames * sizeof(float));
first_pass_ = false;
return;
}
if (source_ptr_) {
memcpy(destination, source_ptr_, frames * sizeof(float));
} else {
for (int i = 0; i < frames; ++i)
destination[i] = static_cast<float>(source_ptr_[i]);
source_available_ -= frames;
destination[i] = static_cast<float>(source_ptr_int_[i]);
}
source_available_ -= frames;
}
} // namespace webrtc

View File

@ -35,6 +35,10 @@ class PushSincResampler : public SincResamplerCallback {
// to |destination_frames|).
int Resample(const int16_t* source, int source_frames,
int16_t* destination, int destination_capacity);
int Resample(const float* source,
int source_frames,
float* destination,
int destination_capacity);
// Implements SincResamplerCallback.
virtual void Run(int frames, float* destination) OVERRIDE;
@ -43,8 +47,9 @@ class PushSincResampler : public SincResamplerCallback {
private:
scoped_ptr<SincResampler> resampler_;
scoped_array<float> float_buffer_;
const int16_t* source_ptr_;
scoped_ptr<float[]> float_buffer_;
const float* source_ptr_;
const int16_t* source_ptr_int_;
const int destination_frames_;
// True on the first call to Resample(), to prime the SincResampler buffer.

View File

@ -12,6 +12,7 @@
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "webrtc/common_audio/include/audio_util.h"
#include "webrtc/common_audio/resampler/push_sinc_resampler.h"
#include "webrtc/common_audio/resampler/sinusoidal_linear_chirp_source.h"
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
@ -34,6 +35,9 @@ class PushSincResamplerTest
virtual ~PushSincResamplerTest() {}
protected:
void ResampleBenchmarkTest(bool int_format);
void ResampleTest(bool int_format);
int input_rate_;
int output_rate_;
double rms_error_;
@ -47,20 +51,18 @@ class ZeroSource : public SincResamplerCallback {
}
};
// Disabled because it takes too long to run routinely. Use for performance
// benchmarking when needed.
TEST_P(PushSincResamplerTest, DISABLED_ResampleBenchmark) {
void PushSincResamplerTest::ResampleBenchmarkTest(bool int_format) {
const int input_samples = input_rate_ / 100;
const int output_samples = output_rate_ / 100;
const int kResampleIterations = 200000;
const int kResampleIterations = 500000;
// Source for data to be resampled.
ZeroSource resampler_source;
scoped_array<float> resampled_destination(new float[output_samples]);
scoped_array<float> source(new float[input_samples]);
scoped_array<int16_t> source_int(new int16_t[input_samples]);
scoped_array<int16_t> destination_int(new int16_t[output_samples]);
scoped_ptr<float[]> resampled_destination(new float[output_samples]);
scoped_ptr<float[]> source(new float[input_samples]);
scoped_ptr<int16_t[]> source_int(new int16_t[input_samples]);
scoped_ptr<int16_t[]> destination_int(new int16_t[output_samples]);
resampler_source.Run(input_samples, source.get());
for (int i = 0; i < input_samples; ++i) {
@ -82,10 +84,22 @@ TEST_P(PushSincResamplerTest, DISABLED_ResampleBenchmark) {
PushSincResampler resampler(input_samples, output_samples);
start = TickTime::Now();
for (int i = 0; i < kResampleIterations; ++i) {
EXPECT_EQ(output_samples,
resampler.Resample(source_int.get(), input_samples,
destination_int.get(), output_samples));
if (int_format) {
for (int i = 0; i < kResampleIterations; ++i) {
EXPECT_EQ(output_samples,
resampler.Resample(source_int.get(),
input_samples,
destination_int.get(),
output_samples));
}
} else {
for (int i = 0; i < kResampleIterations; ++i) {
EXPECT_EQ(output_samples,
resampler.Resample(source.get(),
input_samples,
resampled_destination.get(),
output_samples));
}
}
double total_time_us = (TickTime::Now() - start).Microseconds();
printf("PushSincResampler took %.2f us per frame; which is a %.1f%% overhead "
@ -93,8 +107,18 @@ TEST_P(PushSincResamplerTest, DISABLED_ResampleBenchmark) {
(total_time_us - total_time_sinc_us) / total_time_sinc_us * 100);
}
// Disabled because it takes too long to run routinely. Use for performance
// benchmarking when needed.
TEST_P(PushSincResamplerTest, DISABLED_BenchmarkInt) {
ResampleBenchmarkTest(true);
}
TEST_P(PushSincResamplerTest, DISABLED_BenchmarkFloat) {
ResampleBenchmarkTest(false);
}
// Tests resampling using a given input and output sample rate.
TEST_P(PushSincResamplerTest, Resample) {
void PushSincResamplerTest::ResampleTest(bool int_format) {
// Make comparisons using one second of data.
static const double kTestDurationSecs = 1;
// 10 ms blocks.
@ -115,11 +139,11 @@ TEST_P(PushSincResamplerTest, Resample) {
// 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]);
scoped_array<float> source(new float[input_samples]);
scoped_array<int16_t> source_int(new int16_t[input_block_size]);
scoped_array<int16_t> destination_int(new int16_t[output_block_size]);
scoped_ptr<float[]> resampled_destination(new float[output_samples]);
scoped_ptr<float[]> pure_destination(new float[output_samples]);
scoped_ptr<float[]> source(new float[input_samples]);
scoped_ptr<int16_t[]> source_int(new int16_t[input_block_size]);
scoped_ptr<int16_t[]> destination_int(new int16_t[output_block_size]);
// The sinc resampler has an implicit delay of approximately half the kernel
// size at the input sample rate. By moving to a push model, this delay
@ -134,17 +158,27 @@ TEST_P(PushSincResamplerTest, Resample) {
// With the PushSincResampler, we produce the signal block-by-10ms-block
// rather than in a single pass, to exercise how it will be used in WebRTC.
resampler_source.Run(input_samples, source.get());
for (int i = 0; i < kNumBlocks; ++i) {
for (int j = 0; j < input_block_size; ++j) {
source_int[j] = static_cast<int16_t>(floor(32767 *
source[i * input_block_size + j] + 0.5));
if (int_format) {
for (int i = 0; i < kNumBlocks; ++i) {
ScaleAndRoundToInt16(
&source[i * input_block_size], input_block_size, source_int.get());
EXPECT_EQ(output_block_size,
resampler.Resample(source_int.get(),
input_block_size,
destination_int.get(),
output_block_size));
ScaleToFloat(destination_int.get(),
output_block_size,
&resampled_destination[i * output_block_size]);
}
EXPECT_EQ(output_block_size,
resampler.Resample(source_int.get(), input_block_size,
destination_int.get(), output_block_size));
for (int j = 0; j < output_block_size; ++j) {
resampled_destination[i * output_block_size + j] =
static_cast<float>(destination_int[j]) / 32767;
} else {
for (int i = 0; i < kNumBlocks; ++i) {
EXPECT_EQ(
output_block_size,
resampler.Resample(&source[i * input_block_size],
input_block_size,
&resampled_destination[i * output_block_size],
output_block_size));
}
}
@ -204,13 +238,19 @@ TEST_P(PushSincResamplerTest, Resample) {
EXPECT_LE(high_freq_max_error, kHighFrequencyMaxError);
}
TEST_P(PushSincResamplerTest, ResampleInt) { ResampleTest(true); }
TEST_P(PushSincResamplerTest, ResampleFloat) { ResampleTest(false); }
// Almost all conversions have an RMS error of around -14 dbFS.
static const double kResamplingRMSError = -14.42;
// 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(
PushSincResamplerTest, PushSincResamplerTest, testing::Values(
PushSincResamplerTest,
PushSincResamplerTest,
testing::Values(
// First run through the rates tested in SincResamplerTest. The
// thresholds are identical.
//
@ -261,7 +301,7 @@ INSTANTIATE_TEST_CASE_P(
// practice anyway.
// To 8 kHz
std::tr1::make_tuple(8000, 8000, kResamplingRMSError, -75.51),
std::tr1::make_tuple(8000, 8000, kResamplingRMSError, -75.50),
std::tr1::make_tuple(16000, 8000, -18.56, -28.79),
std::tr1::make_tuple(32000, 8000, -20.36, -14.13),
std::tr1::make_tuple(44100, 8000, -21.00, -11.39),
@ -278,7 +318,7 @@ INSTANTIATE_TEST_CASE_P(
// To 32 kHz
std::tr1::make_tuple(8000, 32000, kResamplingRMSError, -70.30),
std::tr1::make_tuple(16000, 32000, kResamplingRMSError, -75.51),
std::tr1::make_tuple(32000, 32000, kResamplingRMSError, -75.56),
std::tr1::make_tuple(32000, 32000, kResamplingRMSError, -75.51),
std::tr1::make_tuple(44100, 32000, -16.44, -51.10),
std::tr1::make_tuple(48000, 32000, -16.90, -44.03),
std::tr1::make_tuple(96000, 32000, -19.61, -18.04),