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:
@ -9,22 +9,22 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "webrtc/common_audio/include/audio_util.h"
|
#include "webrtc/common_audio/include/audio_util.h"
|
||||||
#include "webrtc/common_audio/resampler/push_sinc_resampler.h"
|
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "webrtc/common_audio/resampler/push_sinc_resampler.h"
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
|
|
||||||
PushSincResampler::PushSincResampler(int source_frames,
|
PushSincResampler::PushSincResampler(int source_frames, int destination_frames)
|
||||||
int destination_frames)
|
|
||||||
: resampler_(new SincResampler(source_frames * 1.0 / destination_frames,
|
: resampler_(new SincResampler(source_frames * 1.0 / destination_frames,
|
||||||
source_frames, this)),
|
source_frames,
|
||||||
float_buffer_(new float[destination_frames]),
|
this)),
|
||||||
source_ptr_(NULL),
|
source_ptr_(NULL),
|
||||||
|
source_ptr_int_(NULL),
|
||||||
destination_frames_(destination_frames),
|
destination_frames_(destination_frames),
|
||||||
first_pass_(true),
|
first_pass_(true),
|
||||||
source_available_(0) {
|
source_available_(0) {}
|
||||||
}
|
|
||||||
|
|
||||||
PushSincResampler::~PushSincResampler() {
|
PushSincResampler::~PushSincResampler() {
|
||||||
}
|
}
|
||||||
@ -33,6 +33,21 @@ int PushSincResampler::Resample(const int16_t* source,
|
|||||||
int source_length,
|
int source_length,
|
||||||
int16_t* destination,
|
int16_t* destination,
|
||||||
int destination_capacity) {
|
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(source_length == resampler_->request_frames());
|
||||||
assert(destination_capacity >= destination_frames_);
|
assert(destination_capacity >= destination_frames_);
|
||||||
// Cache the source pointer. Calling Resample() will immediately trigger
|
// 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
|
// request in order to prime the buffer with a single Run() request for
|
||||||
// |source_frames|.
|
// |source_frames|.
|
||||||
if (first_pass_)
|
if (first_pass_)
|
||||||
resampler_->Resample(resampler_->ChunkSize(), float_buffer_.get());
|
resampler_->Resample(resampler_->ChunkSize(), destination);
|
||||||
|
|
||||||
resampler_->Resample(destination_frames_, float_buffer_.get());
|
resampler_->Resample(destination_frames_, destination);
|
||||||
RoundToInt16(float_buffer_.get(), destination_frames_, destination);
|
|
||||||
source_ptr_ = NULL;
|
source_ptr_ = NULL;
|
||||||
return destination_frames_;
|
return destination_frames_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PushSincResampler::Run(int frames, float* destination) {
|
void PushSincResampler::Run(int frames, float* destination) {
|
||||||
assert(source_ptr_ != NULL);
|
|
||||||
// Ensure we are only asked for the available samples. This would fail if
|
// Ensure we are only asked for the available samples. This would fail if
|
||||||
// Run() was triggered more than once per Resample() call.
|
// Run() was triggered more than once per Resample() call.
|
||||||
assert(source_available_ == frames);
|
assert(source_available_ == frames);
|
||||||
@ -73,11 +86,16 @@ void PushSincResampler::Run(int frames, float* destination) {
|
|||||||
// discarded, as described in Resample().
|
// discarded, as described in Resample().
|
||||||
memset(destination, 0, frames * sizeof(float));
|
memset(destination, 0, frames * sizeof(float));
|
||||||
first_pass_ = false;
|
first_pass_ = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source_ptr_) {
|
||||||
|
memcpy(destination, source_ptr_, frames * sizeof(float));
|
||||||
} else {
|
} else {
|
||||||
for (int i = 0; i < frames; ++i)
|
for (int i = 0; i < frames; ++i)
|
||||||
destination[i] = static_cast<float>(source_ptr_[i]);
|
destination[i] = static_cast<float>(source_ptr_int_[i]);
|
||||||
source_available_ -= frames;
|
|
||||||
}
|
}
|
||||||
|
source_available_ -= frames;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
@ -35,6 +35,10 @@ class PushSincResampler : public SincResamplerCallback {
|
|||||||
// to |destination_frames|).
|
// to |destination_frames|).
|
||||||
int Resample(const int16_t* source, int source_frames,
|
int Resample(const int16_t* source, int source_frames,
|
||||||
int16_t* destination, int destination_capacity);
|
int16_t* destination, int destination_capacity);
|
||||||
|
int Resample(const float* source,
|
||||||
|
int source_frames,
|
||||||
|
float* destination,
|
||||||
|
int destination_capacity);
|
||||||
|
|
||||||
// Implements SincResamplerCallback.
|
// Implements SincResamplerCallback.
|
||||||
virtual void Run(int frames, float* destination) OVERRIDE;
|
virtual void Run(int frames, float* destination) OVERRIDE;
|
||||||
@ -43,8 +47,9 @@ class PushSincResampler : public SincResamplerCallback {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
scoped_ptr<SincResampler> resampler_;
|
scoped_ptr<SincResampler> resampler_;
|
||||||
scoped_array<float> float_buffer_;
|
scoped_ptr<float[]> float_buffer_;
|
||||||
const int16_t* source_ptr_;
|
const float* source_ptr_;
|
||||||
|
const int16_t* source_ptr_int_;
|
||||||
const int destination_frames_;
|
const int destination_frames_;
|
||||||
|
|
||||||
// True on the first call to Resample(), to prime the SincResampler buffer.
|
// True on the first call to Resample(), to prime the SincResampler buffer.
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include "testing/gmock/include/gmock/gmock.h"
|
#include "testing/gmock/include/gmock/gmock.h"
|
||||||
#include "testing/gtest/include/gtest/gtest.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/push_sinc_resampler.h"
|
||||||
#include "webrtc/common_audio/resampler/sinusoidal_linear_chirp_source.h"
|
#include "webrtc/common_audio/resampler/sinusoidal_linear_chirp_source.h"
|
||||||
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
|
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
|
||||||
@ -34,6 +35,9 @@ class PushSincResamplerTest
|
|||||||
virtual ~PushSincResamplerTest() {}
|
virtual ~PushSincResamplerTest() {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
void ResampleBenchmarkTest(bool int_format);
|
||||||
|
void ResampleTest(bool int_format);
|
||||||
|
|
||||||
int input_rate_;
|
int input_rate_;
|
||||||
int output_rate_;
|
int output_rate_;
|
||||||
double rms_error_;
|
double rms_error_;
|
||||||
@ -47,20 +51,18 @@ class ZeroSource : public SincResamplerCallback {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Disabled because it takes too long to run routinely. Use for performance
|
void PushSincResamplerTest::ResampleBenchmarkTest(bool int_format) {
|
||||||
// benchmarking when needed.
|
|
||||||
TEST_P(PushSincResamplerTest, DISABLED_ResampleBenchmark) {
|
|
||||||
const int input_samples = input_rate_ / 100;
|
const int input_samples = input_rate_ / 100;
|
||||||
const int output_samples = output_rate_ / 100;
|
const int output_samples = output_rate_ / 100;
|
||||||
const int kResampleIterations = 200000;
|
const int kResampleIterations = 500000;
|
||||||
|
|
||||||
// Source for data to be resampled.
|
// Source for data to be resampled.
|
||||||
ZeroSource resampler_source;
|
ZeroSource resampler_source;
|
||||||
|
|
||||||
scoped_array<float> resampled_destination(new float[output_samples]);
|
scoped_ptr<float[]> resampled_destination(new float[output_samples]);
|
||||||
scoped_array<float> source(new float[input_samples]);
|
scoped_ptr<float[]> source(new float[input_samples]);
|
||||||
scoped_array<int16_t> source_int(new int16_t[input_samples]);
|
scoped_ptr<int16_t[]> source_int(new int16_t[input_samples]);
|
||||||
scoped_array<int16_t> destination_int(new int16_t[output_samples]);
|
scoped_ptr<int16_t[]> destination_int(new int16_t[output_samples]);
|
||||||
|
|
||||||
resampler_source.Run(input_samples, source.get());
|
resampler_source.Run(input_samples, source.get());
|
||||||
for (int i = 0; i < input_samples; ++i) {
|
for (int i = 0; i < input_samples; ++i) {
|
||||||
@ -82,10 +84,22 @@ TEST_P(PushSincResamplerTest, DISABLED_ResampleBenchmark) {
|
|||||||
|
|
||||||
PushSincResampler resampler(input_samples, output_samples);
|
PushSincResampler resampler(input_samples, output_samples);
|
||||||
start = TickTime::Now();
|
start = TickTime::Now();
|
||||||
|
if (int_format) {
|
||||||
for (int i = 0; i < kResampleIterations; ++i) {
|
for (int i = 0; i < kResampleIterations; ++i) {
|
||||||
EXPECT_EQ(output_samples,
|
EXPECT_EQ(output_samples,
|
||||||
resampler.Resample(source_int.get(), input_samples,
|
resampler.Resample(source_int.get(),
|
||||||
destination_int.get(), output_samples));
|
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();
|
double total_time_us = (TickTime::Now() - start).Microseconds();
|
||||||
printf("PushSincResampler took %.2f us per frame; which is a %.1f%% overhead "
|
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);
|
(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.
|
// 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.
|
// Make comparisons using one second of data.
|
||||||
static const double kTestDurationSecs = 1;
|
static const double kTestDurationSecs = 1;
|
||||||
// 10 ms blocks.
|
// 10 ms blocks.
|
||||||
@ -115,11 +139,11 @@ TEST_P(PushSincResamplerTest, Resample) {
|
|||||||
|
|
||||||
// TODO(dalecurtis): If we switch to AVX/SSE optimization, we'll need to
|
// 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.
|
// allocate these on 32-byte boundaries and ensure they're sized % 32 bytes.
|
||||||
scoped_array<float> resampled_destination(new float[output_samples]);
|
scoped_ptr<float[]> resampled_destination(new float[output_samples]);
|
||||||
scoped_array<float> pure_destination(new float[output_samples]);
|
scoped_ptr<float[]> pure_destination(new float[output_samples]);
|
||||||
scoped_array<float> source(new float[input_samples]);
|
scoped_ptr<float[]> source(new float[input_samples]);
|
||||||
scoped_array<int16_t> source_int(new int16_t[input_block_size]);
|
scoped_ptr<int16_t[]> source_int(new int16_t[input_block_size]);
|
||||||
scoped_array<int16_t> destination_int(new int16_t[output_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
|
// 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
|
// 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
|
// 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.
|
// rather than in a single pass, to exercise how it will be used in WebRTC.
|
||||||
resampler_source.Run(input_samples, source.get());
|
resampler_source.Run(input_samples, source.get());
|
||||||
|
if (int_format) {
|
||||||
for (int i = 0; i < kNumBlocks; ++i) {
|
for (int i = 0; i < kNumBlocks; ++i) {
|
||||||
for (int j = 0; j < input_block_size; ++j) {
|
ScaleAndRoundToInt16(
|
||||||
source_int[j] = static_cast<int16_t>(floor(32767 *
|
&source[i * input_block_size], input_block_size, source_int.get());
|
||||||
source[i * input_block_size + j] + 0.5));
|
|
||||||
}
|
|
||||||
EXPECT_EQ(output_block_size,
|
EXPECT_EQ(output_block_size,
|
||||||
resampler.Resample(source_int.get(), input_block_size,
|
resampler.Resample(source_int.get(),
|
||||||
destination_int.get(), output_block_size));
|
input_block_size,
|
||||||
for (int j = 0; j < output_block_size; ++j) {
|
destination_int.get(),
|
||||||
resampled_destination[i * output_block_size + j] =
|
output_block_size));
|
||||||
static_cast<float>(destination_int[j]) / 32767;
|
ScaleToFloat(destination_int.get(),
|
||||||
|
output_block_size,
|
||||||
|
&resampled_destination[i * output_block_size]);
|
||||||
|
}
|
||||||
|
} 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);
|
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.
|
// Almost all conversions have an RMS error of around -14 dbFS.
|
||||||
static const double kResamplingRMSError = -14.42;
|
static const double kResamplingRMSError = -14.42;
|
||||||
|
|
||||||
// Thresholds chosen arbitrarily based on what each resampling reported during
|
// Thresholds chosen arbitrarily based on what each resampling reported during
|
||||||
// testing. All thresholds are in dbFS, http://en.wikipedia.org/wiki/DBFS.
|
// testing. All thresholds are in dbFS, http://en.wikipedia.org/wiki/DBFS.
|
||||||
INSTANTIATE_TEST_CASE_P(
|
INSTANTIATE_TEST_CASE_P(
|
||||||
PushSincResamplerTest, PushSincResamplerTest, testing::Values(
|
PushSincResamplerTest,
|
||||||
|
PushSincResamplerTest,
|
||||||
|
testing::Values(
|
||||||
// First run through the rates tested in SincResamplerTest. The
|
// First run through the rates tested in SincResamplerTest. The
|
||||||
// thresholds are identical.
|
// thresholds are identical.
|
||||||
//
|
//
|
||||||
@ -261,7 +301,7 @@ INSTANTIATE_TEST_CASE_P(
|
|||||||
// practice anyway.
|
// practice anyway.
|
||||||
|
|
||||||
// To 8 kHz
|
// 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(16000, 8000, -18.56, -28.79),
|
||||||
std::tr1::make_tuple(32000, 8000, -20.36, -14.13),
|
std::tr1::make_tuple(32000, 8000, -20.36, -14.13),
|
||||||
std::tr1::make_tuple(44100, 8000, -21.00, -11.39),
|
std::tr1::make_tuple(44100, 8000, -21.00, -11.39),
|
||||||
@ -278,7 +318,7 @@ INSTANTIATE_TEST_CASE_P(
|
|||||||
// To 32 kHz
|
// To 32 kHz
|
||||||
std::tr1::make_tuple(8000, 32000, kResamplingRMSError, -70.30),
|
std::tr1::make_tuple(8000, 32000, kResamplingRMSError, -70.30),
|
||||||
std::tr1::make_tuple(16000, 32000, kResamplingRMSError, -75.51),
|
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(44100, 32000, -16.44, -51.10),
|
||||||
std::tr1::make_tuple(48000, 32000, -16.90, -44.03),
|
std::tr1::make_tuple(48000, 32000, -16.90, -44.03),
|
||||||
std::tr1::make_tuple(96000, 32000, -19.61, -18.04),
|
std::tr1::make_tuple(96000, 32000, -19.61, -18.04),
|
||||||
|
Reference in New Issue
Block a user