Skip to content

Commit

Permalink
Refactor audio conversion functions.
Browse files Browse the repository at this point in the history
Use a consistent naming scheme that can be understood at the callsite
without having to refer to documentation.

Remove hacks in AudioBuffer intended to maintain bit-exactness with the
float path. The conversions etc. are now all natural, and instead we
enforce close but not bit-exact output between the two paths.

Output of ApmTest.Process:
https://paste.googleplex.com/5931055831842816

R=aluebs@webrtc.org, bjornv@webrtc.org, kwiberg@webrtc.org

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

git-svn-id: http://webrtc.googlecode.com/svn/trunk@7561 4adac7df-926f-26a2-2b94-8c16560cd09d
  • Loading branch information
andrew@webrtc.org committed Oct 30, 2014
1 parent 776e6f2 commit 4fc4add
Show file tree
Hide file tree
Showing 11 changed files with 155 additions and 96 deletions.
Binary file modified data/audio_processing/output_data_fixed.pb
Binary file not shown.
Binary file modified data/audio_processing/output_data_float.pb
Binary file not shown.
22 changes: 16 additions & 6 deletions webrtc/common_audio/audio_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,29 @@

namespace webrtc {

void RoundToInt16(const float* src, size_t size, int16_t* dest) {
void FloatToS16(const float* src, size_t size, int16_t* dest) {
for (size_t i = 0; i < size; ++i)
dest[i] = RoundToInt16(src[i]);
dest[i] = FloatToS16(src[i]);
}

void ScaleAndRoundToInt16(const float* src, size_t size, int16_t* dest) {
void S16ToFloat(const int16_t* src, size_t size, float* dest) {
for (size_t i = 0; i < size; ++i)
dest[i] = ScaleAndRoundToInt16(src[i]);
dest[i] = S16ToFloat(src[i]);
}

void ScaleToFloat(const int16_t* src, size_t size, float* dest) {
void FloatS16ToS16(const float* src, size_t size, int16_t* dest) {
for (size_t i = 0; i < size; ++i)
dest[i] = ScaleToFloat(src[i]);
dest[i] = FloatS16ToS16(src[i]);
}

void FloatToFloatS16(const float* src, size_t size, float* dest) {
for (size_t i = 0; i < size; ++i)
dest[i] = FloatToFloatS16(src[i]);
}

void FloatS16ToFloat(const float* src, size_t size, float* dest) {
for (size_t i = 0; i < size; ++i)
dest[i] = FloatS16ToFloat(src[i]);
}

} // namespace webrtc
48 changes: 36 additions & 12 deletions webrtc/common_audio/audio_util_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,35 +26,59 @@ void ExpectArraysEq(const float* ref, const float* test, int length) {
}
}

TEST(AudioUtilTest, RoundToInt16) {
TEST(AudioUtilTest, FloatToS16) {
const int kSize = 9;
const float kInput[kSize] = {
0.f, 0.4f / 32767.f, 0.6f / 32767.f, -0.4f / 32768.f, -0.6f / 32768.f,
1.f, -1.f, 1.1f, -1.1f};
const int16_t kReference[kSize] = {
0, 0, 1, 0, -1, 32767, -32768, 32767, -32768};
int16_t output[kSize];
FloatToS16(kInput, kSize, output);
ExpectArraysEq(kReference, output, kSize);
}

TEST(AudioUtilTest, S16ToFloat) {
const int kSize = 7;
const int16_t kInput[kSize] = {0, 1, -1, 16384, -16384, 32767, -32768};
const float kReference[kSize] = {
0.f, 1.f / 32767.f, -1.f / 32768.f, 16384.f / 32767.f, -0.5f, 1.f, -1.f};
float output[kSize];
S16ToFloat(kInput, kSize, output);
ExpectArraysEq(kReference, output, kSize);
}

TEST(AudioUtilTest, FloatS16ToS16) {
const int kSize = 7;
const float kInput[kSize] = {
0.f, 0.4f, 0.5f, -0.4f, -0.5f, 32768.f, -32769.f};
const int16_t kReference[kSize] = {0, 0, 1, 0, -1, 32767, -32768};
int16_t output[kSize];
RoundToInt16(kInput, kSize, output);
FloatS16ToS16(kInput, kSize, output);
ExpectArraysEq(kReference, output, kSize);
}

TEST(AudioUtilTest, ScaleAndRoundToInt16) {
TEST(AudioUtilTest, FloatToFloatS16) {
const int kSize = 9;
const float kInput[kSize] = {
0.f, 0.4f / 32767.f, 0.6f / 32767.f, -0.4f / 32768.f, -0.6f / 32768.f,
1.f, -1.f, 1.1f, -1.1f};
const int16_t kReference[kSize] = {
0, 0, 1, 0, -1, 32767, -32768, 32767, -32768};
int16_t output[kSize];
ScaleAndRoundToInt16(kInput, kSize, output);
const float kReference[kSize] = {
0.f, 0.4f, 0.6f, -0.4f, -0.6f, 32767.f, -32768.f, 36043.7f, -36044.8f};
float output[kSize];
FloatToFloatS16(kInput, kSize, output);
ExpectArraysEq(kReference, output, kSize);
}

TEST(AudioUtilTest, ScaleToFloat) {
const int kSize = 7;
const int16_t kInput[kSize] = {0, 1, -1, 16384, -16384, 32767, -32768};
TEST(AudioUtilTest, FloatS16ToFloat) {
const int kSize = 9;
const float kInput[kSize] = {
0.f, 0.4f, 0.6f, -0.4f, -0.6f, 32767.f, -32768.f, 36043.7f, -36044.8f};
const float kReference[kSize] = {
0.f, 1.f / 32767.f, -1.f / 32768.f, 16384.f / 32767.f, -0.5f, 1.f, -1.f};
0.f, 0.4f / 32767.f, 0.6f / 32767.f, -0.4f / 32768.f, -0.6f / 32768.f,
1.f, -1.f, 1.1f, -1.1f};
float output[kSize];
ScaleToFloat(kInput, kSize, output);
FloatS16ToFloat(kInput, kSize, output);
ExpectArraysEq(kReference, output, kSize);
}

Expand Down
54 changes: 31 additions & 23 deletions webrtc/common_audio/include/audio_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,41 +20,49 @@ namespace webrtc {

typedef std::numeric_limits<int16_t> limits_int16;

static inline int16_t RoundToInt16(float v) {
const float kMaxRound = limits_int16::max() - 0.5f;
const float kMinRound = limits_int16::min() + 0.5f;
if (v > 0)
return v >= kMaxRound ? limits_int16::max() :
static_cast<int16_t>(v + 0.5f);
return v <= kMinRound ? limits_int16::min() :
static_cast<int16_t>(v - 0.5f);
}

// Scale (from [-1, 1]) and round to full-range int16 with clamping.
static inline int16_t ScaleAndRoundToInt16(float v) {
// The conversion functions use the following naming convention:
// S16: int16_t [-32768, 32767]
// Float: float [-1.0, 1.0]
// FloatS16: float [-32768.0, 32767.0]
static inline int16_t FloatToS16(float v) {
if (v > 0)
return v >= 1 ? limits_int16::max() :
static_cast<int16_t>(v * limits_int16::max() + 0.5f);
return v <= -1 ? limits_int16::min() :
static_cast<int16_t>(-v * limits_int16::min() - 0.5f);
}

// Scale to float [-1, 1].
static inline float ScaleToFloat(int16_t v) {
const float kMaxInt16Inverse = 1.f / limits_int16::max();
const float kMinInt16Inverse = 1.f / limits_int16::min();
static inline float S16ToFloat(int16_t v) {
static const float kMaxInt16Inverse = 1.f / limits_int16::max();
static const float kMinInt16Inverse = 1.f / limits_int16::min();
return v * (v > 0 ? kMaxInt16Inverse : -kMinInt16Inverse);
}

// Round |size| elements of |src| to int16 with clamping and write to |dest|.
void RoundToInt16(const float* src, size_t size, int16_t* dest);
static inline int16_t FloatS16ToS16(float v) {
static const float kMaxRound = limits_int16::max() - 0.5f;
static const float kMinRound = limits_int16::min() + 0.5f;
if (v > 0)
return v >= kMaxRound ? limits_int16::max() :
static_cast<int16_t>(v + 0.5f);
return v <= kMinRound ? limits_int16::min() :
static_cast<int16_t>(v - 0.5f);
}

// Scale (from [-1, 1]) and round |size| elements of |src| to full-range int16
// with clamping and write to |dest|.
void ScaleAndRoundToInt16(const float* src, size_t size, int16_t* dest);
static inline float FloatToFloatS16(float v) {
return v > 0 ? v * limits_int16::max() : -v * limits_int16::min();
}

static inline float FloatS16ToFloat(float v) {
static const float kMaxInt16Inverse = 1.f / limits_int16::max();
static const float kMinInt16Inverse = 1.f / limits_int16::min();
return v * (v > 0 ? kMaxInt16Inverse : -kMinInt16Inverse);
}

// Scale |size| elements of |src| to float [-1, 1] and write to |dest|.
void ScaleToFloat(const int16_t* src, size_t size, float* dest);
void FloatToS16(const float* src, size_t size, int16_t* dest);
void S16ToFloat(const int16_t* src, size_t size, float* dest);
void FloatS16ToS16(const float* src, size_t size, int16_t* dest);
void FloatToFloatS16(const float* src, size_t size, float* dest);
void FloatS16ToFloat(const float* src, size_t size, float* dest);

// Deinterleave audio from |interleaved| to the channel buffers pointed to
// by |deinterleaved|. There must be sufficient space allocated in the
Expand Down
2 changes: 1 addition & 1 deletion webrtc/common_audio/resampler/push_sinc_resampler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ int PushSincResampler::Resample(const int16_t* source,
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);
FloatS16ToS16(float_buffer_.get(), destination_frames_, destination);
source_ptr_int_ = NULL;
return destination_frames_;
}
Expand Down
9 changes: 4 additions & 5 deletions webrtc/common_audio/resampler/push_sinc_resampler_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -160,16 +160,15 @@ void PushSincResamplerTest::ResampleTest(bool int_format) {
resampler_source.Run(input_samples, source.get());
if (int_format) {
for (int i = 0; i < kNumBlocks; ++i) {
ScaleAndRoundToInt16(
&source[i * input_block_size], input_block_size, source_int.get());
FloatToS16(&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]);
S16ToFloat(destination_int.get(), output_block_size,
&resampled_destination[i * output_block_size]);
}
} else {
for (int i = 0; i < kNumBlocks; ++i) {
Expand Down
2 changes: 1 addition & 1 deletion webrtc/common_audio/wav_writer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ void WavFile::WriteSamples(const float* samples, size_t num_samples) {
for (size_t i = 0; i < num_samples; i += kChunksize) {
int16_t isamples[kChunksize];
const size_t chunk = std::min(kChunksize, num_samples - i);
RoundToInt16(samples + i, chunk, isamples);
FloatS16ToS16(samples + i, chunk, isamples);
WriteSamples(isamples, chunk);
}
}
Expand Down
38 changes: 10 additions & 28 deletions webrtc/modules/audio_processing/audio_buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,11 @@ int KeyboardChannelIndex(AudioProcessing::ChannelLayout layout) {
return -1;
}

void StereoToMono(const float* left, const float* right, float* out,
template <typename T>
void StereoToMono(const T* left, const T* right, T* out,
int samples_per_channel) {
for (int i = 0; i < samples_per_channel; ++i) {
for (int i = 0; i < samples_per_channel; ++i)
out[i] = (left[i] + right[i]) / 2;
}
}

void StereoToMono(const int16_t* left, const int16_t* right, int16_t* out,
int samples_per_channel) {
for (int i = 0; i < samples_per_channel; ++i) {
out[i] = (left[i] + right[i]) >> 1;
}
}

} // namespace
Expand Down Expand Up @@ -114,13 +107,7 @@ class IFChannelBuffer {
void RefreshI() {
if (!ivalid_) {
assert(fvalid_);
const float* const float_data = fbuf_.data();
int16_t* const int_data = ibuf_.data();
const int length = ibuf_.length();
for (int i = 0; i < length; ++i)
int_data[i] = WEBRTC_SPL_SAT(std::numeric_limits<int16_t>::max(),
float_data[i],
std::numeric_limits<int16_t>::min());
FloatS16ToS16(fbuf_.data(), ibuf_.length(), ibuf_.data());
ivalid_ = true;
}
}
Expand Down Expand Up @@ -230,8 +217,8 @@ void AudioBuffer::CopyFrom(const float* const* data,

// Convert to int16.
for (int i = 0; i < num_proc_channels_; ++i) {
ScaleAndRoundToInt16(data_ptr[i], proc_samples_per_channel_,
channels_->ibuf()->channel(i));
FloatToFloatS16(data_ptr[i], proc_samples_per_channel_,
channels_->fbuf()->channel(i));
}
}

Expand All @@ -248,9 +235,9 @@ void AudioBuffer::CopyTo(int samples_per_channel,
data_ptr = process_buffer_->channels();
}
for (int i = 0; i < num_proc_channels_; ++i) {
ScaleToFloat(channels_->ibuf()->channel(i),
proc_samples_per_channel_,
data_ptr[i]);
FloatS16ToFloat(channels_->fbuf()->channel(i),
proc_samples_per_channel_,
data_ptr[i]);
}

// Resample.
Expand Down Expand Up @@ -449,12 +436,7 @@ void AudioBuffer::DeinterleaveFrom(AudioFrame* frame) {
// Downmix directly; no explicit deinterleaving needed.
int16_t* downmixed = channels_->ibuf()->channel(0);
for (int i = 0; i < input_samples_per_channel_; ++i) {
// HACK(ajm): The downmixing in the int16_t path is in practice never
// called from production code. We do this weird scaling to and from float
// to satisfy tests checking for bit-exactness with the float path.
float downmix_float = (ScaleToFloat(frame->data_[i * 2]) +
ScaleToFloat(frame->data_[i * 2 + 1])) / 2;
downmixed[i] = ScaleAndRoundToInt16(downmix_float);
downmixed[i] = (frame->data_[i * 2] + frame->data_[i * 2 + 1]) / 2;
}
} else {
assert(num_proc_channels_ == num_input_channels_);
Expand Down
Loading

0 comments on commit 4fc4add

Please sign in to comment.