Add a deinterleaved float interface to AudioProcessing.

This is mainly to support the native audio format in Chrome. Although
this implementation just moves the float->int conversion under the hood,
we will transition AudioProcessing towards supporting this format
throughout.

- Add a test which verifies we get identical output with the float and
int interfaces.
- The float and int wrappers are tasked with conversion to the
AudioBuffer format. A new shared Process/Analyze method does most of
the work.
- Add a new field to the debug.proto to hold deinterleaved data.
- Add helpers to audio_utils.cc, and start using numeric_limits.
- Note that there was no performance difference between numeric_limits
and a literal value when measured on Linux using gcc or clang.

BUG=2894
R=aluebs@webrtc.org, bjornv@webrtc.org, henrikg@webrtc.org, tommi@webrtc.org, turaj@webrtc.org, xians@webrtc.org

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

git-svn-id: http://webrtc.googlecode.com/svn/trunk@5641 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
andrew@webrtc.org
2014-03-04 20:58:13 +00:00
parent b90991dade
commit 17e40641b3
12 changed files with 660 additions and 250 deletions

View File

@ -14,28 +14,19 @@
namespace webrtc {
void Deinterleave(const int16_t* interleaved, int samples_per_channel,
int num_channels, int16_t** deinterleaved) {
for (int i = 0; i < num_channels; i++) {
int16_t* channel = deinterleaved[i];
int interleaved_idx = i;
for (int j = 0; j < samples_per_channel; j++) {
channel[j] = interleaved[interleaved_idx];
interleaved_idx += num_channels;
}
}
void RoundToInt16(const float* src, int size, int16_t* dest) {
for (int i = 0; i < size; ++i)
dest[i] = RoundToInt16(src[i]);
}
void Interleave(const int16_t* const* deinterleaved, int samples_per_channel,
int num_channels, int16_t* interleaved) {
for (int i = 0; i < num_channels; ++i) {
const int16_t* channel = deinterleaved[i];
int interleaved_idx = i;
for (int j = 0; j < samples_per_channel; j++) {
interleaved[interleaved_idx] = channel[j];
interleaved_idx += num_channels;
}
}
void ScaleAndRoundToInt16(const float* src, int size, int16_t* dest) {
for (int i = 0; i < size; ++i)
dest[i] = ScaleAndRoundToInt16(src[i]);
}
void ScaleToFloat(const int16_t* src, int size, float* dest) {
for (int i = 0; i < size; ++i)
dest[i] = ScaleToFloat(src[i]);
}
} // namespace webrtc

View File

@ -16,25 +16,46 @@ namespace webrtc {
void ExpectArraysEq(const int16_t* ref, const int16_t* test, int length) {
for (int i = 0; i < length; ++i) {
EXPECT_EQ(test[i], ref[i]);
EXPECT_EQ(ref[i], test[i]);
}
}
TEST(AudioUtilTest, Clamp) {
EXPECT_EQ(1000.f, ClampInt16(1000.f));
EXPECT_EQ(32767.f, ClampInt16(32767.5f));
EXPECT_EQ(-32768.f, ClampInt16(-32768.5f));
void ExpectArraysEq(const float* ref, const float* test, int length) {
for (int i = 0; i < length; ++i) {
EXPECT_FLOAT_EQ(ref[i], test[i]);
}
}
TEST(AudioUtilTest, Round) {
TEST(AudioUtilTest, RoundToInt16) {
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);
for (int n = 0; n < kSize; ++n)
EXPECT_EQ(kReference[n], output[n]);
ExpectArraysEq(kReference, output, kSize);
}
TEST(AudioUtilTest, ScaleAndRoundToInt16) {
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);
ExpectArraysEq(kReference, output, kSize);
}
TEST(AudioUtilTest, ScaleToFloat) {
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];
ScaleToFloat(kInput, kSize, output);
ExpectArraysEq(kReference, output, kSize);
}
TEST(AudioUtilTest, InterleavingStereo) {
@ -47,12 +68,12 @@ TEST(AudioUtilTest, InterleavingStereo) {
Deinterleave(kInterleaved, kSamplesPerChannel, kNumChannels, deinterleaved);
const int16_t kRefLeft[] = {2, 4, 8, 16};
const int16_t kRefRight[] = {3, 9, 27, 81};
ExpectArraysEq(left, kRefLeft, kSamplesPerChannel);
ExpectArraysEq(right, kRefRight, kSamplesPerChannel);
ExpectArraysEq(kRefLeft, left, kSamplesPerChannel);
ExpectArraysEq(kRefRight, right, kSamplesPerChannel);
int16_t interleaved[kLength];
Interleave(deinterleaved, kSamplesPerChannel, kNumChannels, interleaved);
ExpectArraysEq(interleaved, kInterleaved, kLength);
ExpectArraysEq(kInterleaved, interleaved, kLength);
}
TEST(AudioUtilTest, InterleavingMonoIsIdentical) {
@ -62,11 +83,11 @@ TEST(AudioUtilTest, InterleavingMonoIsIdentical) {
int16_t mono[kSamplesPerChannel];
int16_t* deinterleaved[] = {mono};
Deinterleave(kInterleaved, kSamplesPerChannel, kNumChannels, deinterleaved);
ExpectArraysEq(mono, kInterleaved, kSamplesPerChannel);
ExpectArraysEq(kInterleaved, mono, kSamplesPerChannel);
int16_t interleaved[kSamplesPerChannel];
Interleave(deinterleaved, kSamplesPerChannel, kNumChannels, interleaved);
ExpectArraysEq(interleaved, mono, kSamplesPerChannel);
ExpectArraysEq(mono, interleaved, kSamplesPerChannel);
}
} // namespace webrtc

View File

@ -11,43 +11,83 @@
#ifndef WEBRTC_COMMON_AUDIO_INCLUDE_AUDIO_UTIL_H_
#define WEBRTC_COMMON_AUDIO_INCLUDE_AUDIO_UTIL_H_
#include <limits>
#include "webrtc/system_wrappers/interface/scoped_ptr.h"
#include "webrtc/typedefs.h"
namespace webrtc {
// Clamp the floating |value| to the range representable by an int16_t.
static inline float ClampInt16(float value) {
const float kMaxInt16 = 32767.f;
const float kMinInt16 = -32768.f;
return value < kMinInt16 ? kMinInt16 :
(value > kMaxInt16 ? kMaxInt16 : value);
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);
}
// Round |value| to the closest int16.
static inline int16_t RoundToInt16(float value) {
return static_cast<int16_t>(
value > 0 ? (value >= 32766.5 ? 32767 : value + 0.5f)
: (value <= -32767.5 ? -32768 : value - 0.5f));
// Scale (from [-1, 1]) and round to full-range int16 with clamping.
static inline int16_t ScaleAndRoundToInt16(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);
}
// Round |size| elements of |src| to the closest int16 and writes to |dest|.
static inline void RoundToInt16(const float* src, int size, int16_t* dest) {
for (int i = 0; i < size; ++i)
dest[i] = RoundToInt16(src[i]);
// 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();
return v * (v > 0 ? kMaxInt16Inverse : -kMinInt16Inverse);
}
// Round |size| elements of |src| to int16 with clamping and write to |dest|.
void RoundToInt16(const float* src, int size, int16_t* dest);
// 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, int size, int16_t* dest);
// Scale |size| elements of |src| to float [-1, 1] and write to |dest|.
void ScaleToFloat(const int16_t* src, int size, float* dest);
// Deinterleave audio from |interleaved| to the channel buffers pointed to
// by |deinterleaved|. There must be sufficient space allocated in the
// |deinterleaved| buffers (|num_channel| buffers with |samples_per_channel|
// per buffer).
void Deinterleave(const int16_t* interleaved, int samples_per_channel,
int num_channels, int16_t** deinterleaved);
template <typename T>
void Deinterleave(const T* interleaved, int samples_per_channel,
int num_channels, T** deinterleaved) {
for (int i = 0; i < num_channels; ++i) {
T* channel = deinterleaved[i];
int interleaved_idx = i;
for (int j = 0; j < samples_per_channel; ++j) {
channel[j] = interleaved[interleaved_idx];
interleaved_idx += num_channels;
}
}
}
// Interleave audio from the channel buffers pointed to by |deinterleaved| to
// |interleaved|. There must be sufficient space allocated in |interleaved|
// (|samples_per_channel| * |num_channels|).
void Interleave(const int16_t* const* deinterleaved, int samples_per_channel,
int num_channels, int16_t* interleaved);
template <typename T>
void Interleave(const T* const* deinterleaved, int samples_per_channel,
int num_channels, T* interleaved) {
for (int i = 0; i < num_channels; ++i) {
const T* channel = deinterleaved[i];
int interleaved_idx = i;
for (int j = 0; j < samples_per_channel; ++j) {
interleaved[interleaved_idx] = channel[j];
interleaved_idx += num_channels;
}
}
}
} // namespace webrtc

View File

@ -10,6 +10,7 @@
#include "webrtc/modules/audio_processing/audio_buffer.h"
#include "webrtc/common_audio/include/audio_util.h"
#include "webrtc/common_audio/signal_processing/include/signal_processing_library.h"
namespace webrtc {
@ -79,11 +80,9 @@ AudioBuffer::AudioBuffer(int max_num_channels,
mixed_channels_(NULL),
mixed_low_pass_channels_(NULL),
low_pass_reference_channels_(NULL) {
if (max_num_channels_ > 1) {
channels_.reset(new AudioChannel[max_num_channels_]);
mixed_channels_.reset(new AudioChannel[max_num_channels_]);
mixed_low_pass_channels_.reset(new AudioChannel[max_num_channels_]);
}
channels_.reset(new AudioChannel[max_num_channels_]);
mixed_channels_.reset(new AudioChannel[max_num_channels_]);
mixed_low_pass_channels_.reset(new AudioChannel[max_num_channels_]);
low_pass_reference_channels_.reset(new AudioChannel[max_num_channels_]);
if (samples_per_channel_ == kSamplesPer32kHzChannel) {
@ -94,6 +93,17 @@ AudioBuffer::AudioBuffer(int max_num_channels,
AudioBuffer::~AudioBuffer() {}
void AudioBuffer::InitForNewData(int num_channels) {
num_channels_ = num_channels;
data_ = NULL;
data_was_mixed_ = false;
num_mixed_channels_ = 0;
num_mixed_low_pass_channels_ = 0;
reference_copied_ = false;
activity_ = AudioFrame::kVadUnknown;
is_muted_ = false;
}
int16_t* AudioBuffer::data(int channel) const {
assert(channel >= 0 && channel < num_channels_);
if (data_ != NULL) {
@ -191,13 +201,8 @@ void AudioBuffer::DeinterleaveFrom(AudioFrame* frame) {
assert(frame->num_channels_ <= max_num_channels_);
assert(frame->samples_per_channel_ == samples_per_channel_);
num_channels_ = frame->num_channels_;
data_was_mixed_ = false;
num_mixed_channels_ = 0;
num_mixed_low_pass_channels_ = 0;
reference_copied_ = false;
InitForNewData(frame->num_channels_);
activity_ = frame->vad_activity_;
is_muted_ = false;
if (frame->energy_ == 0) {
is_muted_ = true;
}
@ -252,6 +257,26 @@ void AudioBuffer::InterleaveTo(AudioFrame* frame, bool data_changed) const {
}
}
void AudioBuffer::CopyFrom(const float* const* data, int samples_per_channel,
int num_channels) {
assert(num_channels <= max_num_channels_);
assert(samples_per_channel == samples_per_channel_);
InitForNewData(num_channels);
for (int i = 0; i < num_channels_; ++i) {
ScaleAndRoundToInt16(data[i], samples_per_channel, channels_[i].data);
}
}
void AudioBuffer::CopyTo(int samples_per_channel, int num_channels,
float* const* data) const {
assert(num_channels == num_channels_);
assert(samples_per_channel == samples_per_channel_);
for (int i = 0; i < num_channels_; ++i) {
ScaleToFloat(channels_[i].data, samples_per_channel, data[i]);
}
}
// TODO(andrew): would be good to support the no-mix case with pointer
// assignment.
// TODO(andrew): handle mixing to multiple channels?

View File

@ -46,17 +46,28 @@ class AudioBuffer {
bool is_muted() const;
// Use for int16 interleaved data.
void DeinterleaveFrom(AudioFrame* audioFrame);
void InterleaveTo(AudioFrame* audioFrame) const;
// If |data_changed| is false, only the non-audio data members will be copied
// to |frame|.
void InterleaveTo(AudioFrame* frame, bool data_changed) const;
// Use for float deinterleaved data.
void CopyFrom(const float* const* data, int samples_per_channel,
int num_channels);
void CopyTo(int samples_per_channel, int num_channels,
float* const* data) const;
void Mix(int num_mixed_channels);
void CopyAndMix(int num_mixed_channels);
void CopyAndMixLowPass(int num_mixed_channels);
void CopyLowPassToReference();
private:
// Called from DeinterleaveFrom() and CopyFrom().
void InitForNewData(int num_channels);
const int max_num_channels_;
int num_channels_;
int num_mixed_channels_;

View File

@ -12,6 +12,7 @@
#include <assert.h>
#include "webrtc/common_audio/include/audio_util.h"
#include "webrtc/common_audio/signal_processing/include/signal_processing_library.h"
#include "webrtc/modules/audio_processing/audio_buffer.h"
#include "webrtc/modules/audio_processing/echo_cancellation_impl.h"
@ -37,8 +38,6 @@
#endif
#endif // WEBRTC_AUDIOPROC_DEBUG_DUMP
static const int kChunkSizeMs = 10;
#define RETURN_ON_ERR(expr) \
do { \
int err = expr; \
@ -48,6 +47,24 @@ static const int kChunkSizeMs = 10;
} while (0)
namespace webrtc {
namespace {
const int kChunkSizeMs = 10;
int ChannelsFromLayout(AudioProcessing::ChannelLayout layout) {
switch (layout) {
case AudioProcessing::kMono:
case AudioProcessing::kMonoAndKeyboard:
return 1;
case AudioProcessing::kStereo:
case AudioProcessing::kStereoAndKeyboard:
return 2;
}
assert(false);
return -1;
}
} // namespace
// Throughout webrtc, it's assumed that success is represented by zero.
COMPILE_ASSERT(AudioProcessing::kNoError == 0, no_error_must_be_zero);
@ -299,6 +316,8 @@ bool AudioProcessingImpl::output_will_be_muted() const {
return output_will_be_muted_;
}
// Calls InitializeLocked() if any of the audio parameters have changed from
// their current values.
int AudioProcessingImpl::MaybeInitializeLocked(int sample_rate_hz,
int num_input_channels, int num_output_channels, int num_reverse_channels) {
if (sample_rate_hz == sample_rate_hz_ &&
@ -342,15 +361,62 @@ int AudioProcessingImpl::MaybeInitializeLocked(int sample_rate_hz,
return InitializeLocked();
}
int AudioProcessingImpl::ProcessStream(AudioFrame* frame) {
int AudioProcessingImpl::ProcessStream(float* const* data,
int samples_per_channel,
int sample_rate_hz,
ChannelLayout input_layout,
ChannelLayout output_layout) {
CriticalSectionScoped crit_scoped(crit_);
int err = kNoError;
if (frame == NULL) {
if (!data) {
return kNullPointerError;
}
const int num_input_channels = ChannelsFromLayout(input_layout);
// TODO(ajm): We now always set the output channels equal to the input
// channels here. Remove the ability to downmix entirely.
// channels here. Restore the ability to downmix.
RETURN_ON_ERR(MaybeInitializeLocked(sample_rate_hz,
num_input_channels, num_input_channels, num_reverse_channels_));
if (samples_per_channel != samples_per_channel_) {
return kBadDataLengthError;
}
#ifdef WEBRTC_AUDIOPROC_DEBUG_DUMP
if (debug_file_->Open()) {
event_msg_->set_type(audioproc::Event::STREAM);
audioproc::Stream* msg = event_msg_->mutable_stream();
const size_t channel_size = sizeof(float) * samples_per_channel;
for (int i = 0; i < num_input_channels; ++i)
msg->set_input_channel(i, data[i], channel_size);
}
#endif
capture_audio_->CopyFrom(data, samples_per_channel, num_output_channels_);
RETURN_ON_ERR(ProcessStreamLocked());
if (output_copy_needed(is_data_processed())) {
capture_audio_->CopyTo(samples_per_channel, num_output_channels_, data);
}
#ifdef WEBRTC_AUDIOPROC_DEBUG_DUMP
if (debug_file_->Open()) {
audioproc::Stream* msg = event_msg_->mutable_stream();
const size_t channel_size = sizeof(float) * samples_per_channel;
for (int i = 0; i < num_output_channels_; ++i)
msg->set_output_channel(i, data[i], channel_size);
RETURN_ON_ERR(WriteMessageToDebugFile());
}
#endif
return kNoError;
}
int AudioProcessingImpl::ProcessStream(AudioFrame* frame) {
CriticalSectionScoped crit_scoped(crit_);
if (!frame) {
return kNullPointerError;
}
// TODO(ajm): We now always set the output channels equal to the input
// channels here. Restore the ability to downmix.
RETURN_ON_ERR(MaybeInitializeLocked(frame->sample_rate_hz_,
frame->num_channels_, frame->num_channels_, num_reverse_channels_));
if (frame->samples_per_channel_ != samples_per_channel_) {
@ -365,6 +431,36 @@ int AudioProcessingImpl::ProcessStream(AudioFrame* frame) {
frame->samples_per_channel_ *
frame->num_channels_;
msg->set_input_data(frame->data_, data_size);
}
#endif
capture_audio_->DeinterleaveFrom(frame);
if (num_output_channels_ < num_input_channels_) {
capture_audio_->Mix(num_output_channels_);
frame->num_channels_ = num_output_channels_;
}
RETURN_ON_ERR(ProcessStreamLocked());
capture_audio_->InterleaveTo(frame, output_copy_needed(is_data_processed()));
#ifdef WEBRTC_AUDIOPROC_DEBUG_DUMP
if (debug_file_->Open()) {
audioproc::Stream* msg = event_msg_->mutable_stream();
const size_t data_size = sizeof(int16_t) *
frame->samples_per_channel_ *
frame->num_channels_;
msg->set_output_data(frame->data_, data_size);
RETURN_ON_ERR(WriteMessageToDebugFile());
}
#endif
return kNoError;
}
int AudioProcessingImpl::ProcessStreamLocked() {
#ifdef WEBRTC_AUDIOPROC_DEBUG_DUMP
if (debug_file_->Open()) {
audioproc::Stream* msg = event_msg_->mutable_stream();
msg->set_delay(stream_delay_ms_);
msg->set_drift(echo_cancellation_->stream_drift_samples());
msg->set_level(gain_control_->stream_analog_level());
@ -372,14 +468,6 @@ int AudioProcessingImpl::ProcessStream(AudioFrame* frame) {
}
#endif
capture_audio_->DeinterleaveFrom(frame);
// TODO(ajm): experiment with mixing and AEC placement.
if (num_output_channels_ < num_input_channels_) {
capture_audio_->Mix(num_output_channels_);
frame->num_channels_ = num_output_channels_;
}
bool data_processed = is_data_processed();
if (analysis_needed(data_processed)) {
for (int i = 0; i < num_output_channels_; i++) {
@ -393,45 +481,18 @@ int AudioProcessingImpl::ProcessStream(AudioFrame* frame) {
}
}
err = high_pass_filter_->ProcessCaptureAudio(capture_audio_);
if (err != kNoError) {
return err;
}
err = gain_control_->AnalyzeCaptureAudio(capture_audio_);
if (err != kNoError) {
return err;
}
err = echo_cancellation_->ProcessCaptureAudio(capture_audio_);
if (err != kNoError) {
return err;
}
RETURN_ON_ERR(high_pass_filter_->ProcessCaptureAudio(capture_audio_));
RETURN_ON_ERR(gain_control_->AnalyzeCaptureAudio(capture_audio_));
RETURN_ON_ERR(echo_cancellation_->ProcessCaptureAudio(capture_audio_));
if (echo_control_mobile_->is_enabled() &&
noise_suppression_->is_enabled()) {
capture_audio_->CopyLowPassToReference();
}
err = noise_suppression_->ProcessCaptureAudio(capture_audio_);
if (err != kNoError) {
return err;
}
err = echo_control_mobile_->ProcessCaptureAudio(capture_audio_);
if (err != kNoError) {
return err;
}
err = voice_detection_->ProcessCaptureAudio(capture_audio_);
if (err != kNoError) {
return err;
}
err = gain_control_->ProcessCaptureAudio(capture_audio_);
if (err != kNoError) {
return err;
}
RETURN_ON_ERR(noise_suppression_->ProcessCaptureAudio(capture_audio_));
RETURN_ON_ERR(echo_control_mobile_->ProcessCaptureAudio(capture_audio_));
RETURN_ON_ERR(voice_detection_->ProcessCaptureAudio(capture_audio_));
RETURN_ON_ERR(gain_control_->ProcessCaptureAudio(capture_audio_));
if (synthesis_needed(data_processed)) {
for (int i = 0; i < num_output_channels_; i++) {
@ -446,38 +507,48 @@ int AudioProcessingImpl::ProcessStream(AudioFrame* frame) {
}
// The level estimator operates on the recombined data.
err = level_estimator_->ProcessStream(capture_audio_);
if (err != kNoError) {
return err;
}
capture_audio_->InterleaveTo(frame, interleave_needed(data_processed));
#ifdef WEBRTC_AUDIOPROC_DEBUG_DUMP
if (debug_file_->Open()) {
audioproc::Stream* msg = event_msg_->mutable_stream();
const size_t data_size = sizeof(int16_t) *
frame->samples_per_channel_ *
frame->num_channels_;
msg->set_output_data(frame->data_, data_size);
err = WriteMessageToDebugFile();
if (err != kNoError) {
return err;
}
}
#endif
RETURN_ON_ERR(level_estimator_->ProcessStream(capture_audio_));
was_stream_delay_set_ = false;
return kNoError;
}
// TODO(ajm): Have AnalyzeReverseStream accept sample rates not matching the
// primary stream and convert ourselves rather than having the user manage it.
// We can be smarter and use the splitting filter when appropriate. Similarly,
// perform downmixing here.
int AudioProcessingImpl::AnalyzeReverseStream(const float* const* data,
int samples_per_channel,
int sample_rate_hz,
ChannelLayout layout) {
CriticalSectionScoped crit_scoped(crit_);
if (data == NULL) {
return kNullPointerError;
}
if (sample_rate_hz != sample_rate_hz_) {
return kBadSampleRateError;
}
const int num_channels = ChannelsFromLayout(layout);
RETURN_ON_ERR(MaybeInitializeLocked(sample_rate_hz_, num_input_channels_,
num_output_channels_, num_channels));
if (samples_per_channel != samples_per_channel_) {
return kBadDataLengthError;
}
#ifdef WEBRTC_AUDIOPROC_DEBUG_DUMP
if (debug_file_->Open()) {
event_msg_->set_type(audioproc::Event::REVERSE_STREAM);
audioproc::ReverseStream* msg = event_msg_->mutable_reverse_stream();
const size_t channel_size = sizeof(float) * samples_per_channel;
for (int i = 0; i < num_channels; ++i)
msg->set_channel(i, data[i], channel_size);
RETURN_ON_ERR(WriteMessageToDebugFile());
}
#endif
render_audio_->CopyFrom(data, samples_per_channel, num_channels);
return AnalyzeReverseStreamLocked();
}
int AudioProcessingImpl::AnalyzeReverseStream(AudioFrame* frame) {
CriticalSectionScoped crit_scoped(crit_);
int err = kNoError;
if (frame == NULL) {
return kNullPointerError;
}
@ -486,6 +557,9 @@ int AudioProcessingImpl::AnalyzeReverseStream(AudioFrame* frame) {
}
RETURN_ON_ERR(MaybeInitializeLocked(sample_rate_hz_, num_input_channels_,
num_output_channels_, frame->num_channels_));
if (frame->samples_per_channel_ != samples_per_channel_) {
return kBadDataLengthError;
}
#ifdef WEBRTC_AUDIOPROC_DEBUG_DUMP
if (debug_file_->Open()) {
@ -495,15 +569,19 @@ int AudioProcessingImpl::AnalyzeReverseStream(AudioFrame* frame) {
frame->samples_per_channel_ *
frame->num_channels_;
msg->set_data(frame->data_, data_size);
err = WriteMessageToDebugFile();
if (err != kNoError) {
return err;
}
RETURN_ON_ERR(WriteMessageToDebugFile());
}
#endif
render_audio_->DeinterleaveFrom(frame);
return AnalyzeReverseStreamLocked();
}
// TODO(ajm): Have AnalyzeReverseStream accept sample rates not matching the
// primary stream and convert ourselves rather than having the user manage it.
// We can be smarter and use the splitting filter when appropriate. Similarly,
// perform downmixing here.
int AudioProcessingImpl::AnalyzeReverseStreamLocked() {
if (sample_rate_hz_ == kSampleRate32kHz) {
for (int i = 0; i < num_reverse_channels_; i++) {
// Split into low and high band.
@ -516,23 +594,11 @@ int AudioProcessingImpl::AnalyzeReverseStream(AudioFrame* frame) {
}
}
// TODO(ajm): warnings possible from components?
err = echo_cancellation_->ProcessRenderAudio(render_audio_);
if (err != kNoError) {
return err;
}
RETURN_ON_ERR(echo_cancellation_->ProcessRenderAudio(render_audio_));
RETURN_ON_ERR(echo_control_mobile_->ProcessRenderAudio(render_audio_));
RETURN_ON_ERR(gain_control_->ProcessRenderAudio(render_audio_));
err = echo_control_mobile_->ProcessRenderAudio(render_audio_);
if (err != kNoError) {
return err;
}
err = gain_control_->ProcessRenderAudio(render_audio_);
if (err != kNoError) {
return err;
}
return err; // TODO(ajm): this is for returning warnings; necessary?
return kNoError;
}
int AudioProcessingImpl::set_stream_delay_ms(int delay) {
@ -563,6 +629,14 @@ bool AudioProcessingImpl::was_stream_delay_set() const {
return was_stream_delay_set_;
}
void AudioProcessingImpl::set_stream_key_pressed(bool key_pressed) {
key_pressed_ = key_pressed;
}
bool AudioProcessingImpl::stream_key_pressed() const {
return key_pressed_;
}
void AudioProcessingImpl::set_delay_offset_ms(int offset) {
CriticalSectionScoped crit_scoped(crit_);
delay_offset_ms_ = offset;
@ -572,14 +646,6 @@ int AudioProcessingImpl::delay_offset_ms() const {
return delay_offset_ms_;
}
void AudioProcessingImpl::set_stream_key_pressed(bool key_pressed) {
key_pressed_ = key_pressed;
}
bool AudioProcessingImpl::stream_key_pressed() const {
return key_pressed_;
}
int AudioProcessingImpl::StartDebugRecording(
const char filename[AudioProcessing::kMaxFilenameSize]) {
CriticalSectionScoped crit_scoped(crit_);
@ -710,7 +776,7 @@ bool AudioProcessingImpl::is_data_processed() const {
return true;
}
bool AudioProcessingImpl::interleave_needed(bool is_data_processed) const {
bool AudioProcessingImpl::output_copy_needed(bool is_data_processed) const {
// Check if we've upmixed or downmixed the audio.
return (num_output_channels_ != num_input_channels_ || is_data_processed);
}
@ -755,7 +821,7 @@ int AudioProcessingImpl::WriteMessageToDebugFile() {
event_msg_->Clear();
return 0;
return kNoError;
}
int AudioProcessingImpl::WriteInitMessage() {

View File

@ -63,7 +63,16 @@ class AudioProcessingImpl : public AudioProcessing {
virtual void set_output_will_be_muted(bool muted) OVERRIDE;
virtual bool output_will_be_muted() const OVERRIDE;
virtual int ProcessStream(AudioFrame* frame) OVERRIDE;
virtual int ProcessStream(float* const* data,
int samples_per_channel,
int sample_rate_hz,
ChannelLayout input_layout,
ChannelLayout output_layout) OVERRIDE;
virtual int AnalyzeReverseStream(AudioFrame* frame) OVERRIDE;
virtual int AnalyzeReverseStream(const float* const* data,
int samples_per_channel,
int sample_rate_hz,
ChannelLayout layout) OVERRIDE;
virtual int set_stream_delay_ms(int delay) OVERRIDE;
virtual int stream_delay_ms() const OVERRIDE;
virtual bool was_stream_delay_set() const OVERRIDE;
@ -89,8 +98,11 @@ class AudioProcessingImpl : public AudioProcessing {
private:
int MaybeInitializeLocked(int sample_rate_hz, int num_input_channels,
int num_output_channels, int num_reverse_channels);
int ProcessStreamLocked();
int AnalyzeReverseStreamLocked();
bool is_data_processed() const;
bool interleave_needed(bool is_data_processed) const;
bool output_copy_needed(bool is_data_processed) const;
bool synthesis_needed(bool is_data_processed) const;
bool analysis_needed(bool is_data_processed) const;

View File

@ -10,17 +10,31 @@ message Init {
optional int32 num_reverse_channels = 5;
}
// May contain interleaved or deinterleaved data, but don't store both formats.
message ReverseStream {
// int16 interleaved data.
optional bytes data = 1;
// float deinterleaved data, where each repeated element points to a single
// channel buffer of data.
repeated bytes channel = 2;
}
// May contain interleaved or deinterleaved data, but don't store both formats.
message Stream {
// int16 interleaved data.
optional bytes input_data = 1;
optional bytes output_data = 2;
optional int32 delay = 3;
optional sint32 drift = 4;
optional int32 level = 5;
optional bool keypress = 6;
// float deinterleaved data, where each repeated element points to a single
// channel buffer of data.
repeated bytes input_channel = 7;
repeated bytes output_channel = 8;
}
message Event {

View File

@ -135,6 +135,16 @@ struct ExperimentalAgc {
//
class AudioProcessing {
public:
enum ChannelLayout {
kMono,
// Left, right.
kStereo,
// Mono, keyboard mic.
kMonoAndKeyboard,
// Left, right, keyboard mic.
kStereoAndKeyboard
};
// Creates an APM instance. Use one instance for every primary audio stream
// requiring processing. On the client-side, this would typically be one
// instance for the near-end stream, and additional instances for each far-end
@ -205,6 +215,17 @@ class AudioProcessing {
// method, it will trigger an initialization.
virtual int ProcessStream(AudioFrame* frame) = 0;
// Accepts deinterleaved float audio with the range [-1, 1]. Each element
// of |data| points to a channel buffer, arranged according to
// |input_layout|. At output, the channels will be arranged according to
// |output_layout|.
// TODO(ajm): Output layout conversion does not yet work.
virtual int ProcessStream(float* const* data,
int samples_per_channel,
int sample_rate_hz,
ChannelLayout input_layout,
ChannelLayout output_layout) = 0;
// Analyzes a 10 ms |frame| of the reverse direction audio stream. The frame
// will not be modified. On the client-side, this is the far-end (or to be
// rendered) audio.
@ -222,6 +243,13 @@ class AudioProcessing {
// TODO(ajm): add const to input; requires an implementation fix.
virtual int AnalyzeReverseStream(AudioFrame* frame) = 0;
// Accepts deinterleaved float audio with the range [-1, 1]. Each element
// of |data| points to a channel buffer, arranged according to |layout|.
virtual int AnalyzeReverseStream(const float* const* data,
int samples_per_channel,
int sample_rate_hz,
ChannelLayout layout) = 0;
// This must be called if and only if echo processing is enabled.
//
// Sets the |delay| in ms between AnalyzeReverseStream() receiving a far-end

View File

@ -209,8 +209,15 @@ class MockAudioProcessing : public AudioProcessing {
bool());
MOCK_METHOD1(ProcessStream,
int(AudioFrame* frame));
MOCK_METHOD5(ProcessStream,
int(float* const* data, int frames, int sample_rate_hz,
ChannelLayout input_layout,
ChannelLayout output_layout));
MOCK_METHOD1(AnalyzeReverseStream,
int(AudioFrame* frame));
MOCK_METHOD4(AnalyzeReverseStream,
int(const float* const* data, int frames, int sample_rate_hz,
ChannelLayout input_layout));
MOCK_METHOD1(set_stream_delay_ms,
int(int delay));
MOCK_CONST_METHOD0(stream_delay_ms,
@ -242,16 +249,16 @@ class MockAudioProcessing : public AudioProcessing {
}
virtual MockHighPassFilter* high_pass_filter() const {
return high_pass_filter_.get();
};
}
virtual MockLevelEstimator* level_estimator() const {
return level_estimator_.get();
};
}
virtual MockNoiseSuppression* noise_suppression() const {
return noise_suppression_.get();
};
}
virtual MockVoiceDetection* voice_detection() const {
return voice_detection_.get();
};
}
private:
scoped_ptr<MockEchoCancellation> echo_cancellation_;

View File

@ -36,8 +36,11 @@
# define WEBRTC_AUDIOPROC_BIT_EXACT
#endif
#define EXPECT_NOERR(expr) EXPECT_EQ(AudioProcessing::kNoError, expr)
namespace webrtc {
namespace {
// TODO(bjornv): This is not feasible until the functionality has been
// re-implemented; see comment at the bottom of this file.
// When false, this will compare the output data with the results stored to
@ -61,6 +64,28 @@ const int kProcessSampleRates[] = {8000, 16000, 32000};
const size_t kProcessSampleRatesSize = sizeof(kProcessSampleRates) /
sizeof(*kProcessSampleRates);
// Helper to encapsulate a contiguous data buffer with access to a pointer
// array of the deinterleaved channels.
template <typename T>
class ChannelBuffer {
public:
ChannelBuffer(int samples_per_channel, int num_channels)
: data_(new T[samples_per_channel * num_channels]),
channels_(new T*[num_channels]) {
memset(data_.get(), 0, sizeof(T) * samples_per_channel * num_channels);
for (int i = 0; i < num_channels; ++i)
channels_[i] = &data_[i * samples_per_channel];
}
~ChannelBuffer() {}
T* data() { return data_.get(); }
T** channels() { return channels_.get(); }
private:
scoped_ptr<T[]> data_;
scoped_ptr<T*[]> channels_;
};
int TruncateToMultipleOf10(int value) {
return (value / 10) * 10;
}
@ -104,27 +129,61 @@ void SetFrameTo(AudioFrame* frame, int16_t left, int16_t right) {
void ScaleFrame(AudioFrame* frame, float scale) {
for (int i = 0; i < frame->samples_per_channel_ * frame->num_channels_; ++i) {
frame->data_[i] = RoundToInt16(ClampInt16(frame->data_[i] * scale));
frame->data_[i] = RoundToInt16(frame->data_[i] * scale);
}
}
bool FrameDataAreEqual(const AudioFrame& frame1, const AudioFrame& frame2) {
if (frame1.samples_per_channel_ !=
frame2.samples_per_channel_) {
if (frame1.samples_per_channel_ != frame2.samples_per_channel_) {
return false;
}
if (frame1.num_channels_ !=
frame2.num_channels_) {
if (frame1.num_channels_ != frame2.num_channels_) {
return false;
}
if (memcmp(frame1.data_, frame2.data_,
frame1.samples_per_channel_ * frame1.num_channels_ *
sizeof(int16_t))) {
sizeof(int16_t))) {
return false;
}
return true;
}
AudioProcessing::ChannelLayout LayoutFromChannels(int num_channels) {
switch (num_channels) {
case 1:
return AudioProcessing::kMono;
case 2:
return AudioProcessing::kStereo;
default:
assert(false);
return AudioProcessing::kMono;
}
}
void EnableAllAPComponents(AudioProcessing* ap) {
#if defined(WEBRTC_AUDIOPROC_FIXED_PROFILE)
EXPECT_NOERR(ap->echo_control_mobile()->Enable(true));
EXPECT_NOERR(ap->gain_control()->set_mode(GainControl::kAdaptiveDigital));
EXPECT_NOERR(ap->gain_control()->Enable(true));
#elif defined(WEBRTC_AUDIOPROC_FLOAT_PROFILE)
EXPECT_NOERR(ap->echo_cancellation()->enable_drift_compensation(true));
EXPECT_NOERR(ap->echo_cancellation()->enable_metrics(true));
EXPECT_NOERR(ap->echo_cancellation()->enable_delay_logging(true));
EXPECT_NOERR(ap->echo_cancellation()->Enable(true));
EXPECT_NOERR(ap->gain_control()->set_mode(GainControl::kAdaptiveAnalog));
EXPECT_NOERR(ap->gain_control()->set_analog_level_limits(0, 255));
EXPECT_NOERR(ap->gain_control()->Enable(true));
#endif
EXPECT_NOERR(ap->high_pass_filter()->Enable(true));
EXPECT_NOERR(ap->level_estimator()->Enable(true));
EXPECT_NOERR(ap->noise_suppression()->Enable(true));
EXPECT_NOERR(ap->voice_detection()->Enable(true));
}
#ifdef WEBRTC_AUDIOPROC_BIT_EXACT
// These functions are only used by the bit-exact test.
template <class T>
@ -176,6 +235,7 @@ void WriteMessageLiteToFile(const std::string filename,
delete [] array;
fclose(file);
}
#endif // WEBRTC_AUDIOPROC_BIT_EXACT
void ReadMessageLiteFromFile(const std::string filename,
::google::protobuf::MessageLite* message) {
@ -195,7 +255,6 @@ void ReadMessageLiteFromFile(const std::string filename,
delete [] array;
fclose(file);
}
#endif // WEBRTC_AUDIOPROC_BIT_EXACT
class ApmTest : public ::testing::Test {
protected:
@ -216,6 +275,7 @@ class ApmTest : public ::testing::Test {
void Init(int sample_rate_hz, int num_reverse_channels,
int num_input_channels, int num_output_channels,
bool open_output_file);
void Init(AudioProcessing* ap);
std::string ResourceFilePath(std::string name, int sample_rate_hz);
std::string OutputFilePath(std::string name,
int sample_rate_hz,
@ -224,7 +284,10 @@ class ApmTest : public ::testing::Test {
int num_output_channels);
void EnableAllComponents();
bool ReadFrame(FILE* file, AudioFrame* frame);
bool ReadFrame(FILE* file, AudioFrame* frame, ChannelBuffer<float>* cb);
void ReadFrameWithRewind(FILE* file, AudioFrame* frame);
void ReadFrameWithRewind(FILE* file, AudioFrame* frame,
ChannelBuffer<float>* cb);
void ProcessWithDefaultStreamParameters(AudioFrame* frame);
void ProcessDelayVerificationTest(int delay_ms, int system_delay_ms,
int delay_min, int delay_max);
@ -232,6 +295,10 @@ class ApmTest : public ::testing::Test {
AudioProcessing::Error expected_return);
void RunQuantizedVolumeDoesNotGetStuckTest(int sample_rate);
void RunManualVolumeChangeIsPossibleTest(int sample_rate);
void StreamParametersTest(bool int_format);
void SampleRatesTest(bool int_format);
int ProcessStreamChooser(bool int_format);
int AnalyzeReverseStreamChooser(bool int_format);
const std::string output_path_;
const std::string ref_path_;
@ -239,6 +306,8 @@ class ApmTest : public ::testing::Test {
scoped_ptr<AudioProcessing> apm_;
AudioFrame* frame_;
AudioFrame* revframe_;
scoped_ptr<ChannelBuffer<float> > float_cb_;
scoped_ptr<ChannelBuffer<float> > revfloat_cb_;
FILE* far_file_;
FILE* near_file_;
FILE* out_file_;
@ -330,6 +399,14 @@ std::string ApmTest::OutputFilePath(std::string name,
return output_path_ + ss.str();
}
void ApmTest::Init(AudioProcessing* ap) {
// Make one process call to ensure the audio parameters are set. It might
// result in a stream error which we can safely ignore.
int err = ap->ProcessStream(frame_);
ASSERT_TRUE(err == kNoErr || err == apm_->kStreamParameterNotSetError);
ASSERT_EQ(ap->kNoError, ap->Initialize());
}
void ApmTest::Init(int sample_rate_hz, int num_reverse_channels,
int num_input_channels, int num_output_channels,
bool open_output_file) {
@ -338,15 +415,15 @@ void ApmTest::Init(int sample_rate_hz, int num_reverse_channels,
frame_->samples_per_channel_ = samples_per_channel;
frame_->num_channels_ = num_input_channels;
frame_->sample_rate_hz_ = sample_rate_hz;
float_cb_.reset(new ChannelBuffer<float>(samples_per_channel,
num_input_channels));
revframe_->samples_per_channel_ = samples_per_channel;
revframe_->num_channels_ = num_reverse_channels;
revframe_->sample_rate_hz_ = sample_rate_hz;
revfloat_cb_.reset(new ChannelBuffer<float>(samples_per_channel,
num_reverse_channels));
// Make one process call to ensure the audio parameters are set. It might
// result in a stream error which we can safely ignore.
int err = apm_->ProcessStream(frame_);
ASSERT_TRUE(err == kNoErr || err == apm_->kStreamParameterNotSetError);
ASSERT_EQ(apm_->kNoError, apm_->Initialize());
Init(apm_.get());
if (far_file_) {
ASSERT_EQ(0, fclose(far_file_));
@ -377,42 +454,11 @@ void ApmTest::Init(int sample_rate_hz, int num_reverse_channels,
}
void ApmTest::EnableAllComponents() {
#if defined(WEBRTC_AUDIOPROC_FIXED_PROFILE)
EXPECT_EQ(apm_->kNoError, apm_->echo_control_mobile()->Enable(true));
EXPECT_EQ(apm_->kNoError,
apm_->gain_control()->set_mode(GainControl::kAdaptiveDigital));
EXPECT_EQ(apm_->kNoError, apm_->gain_control()->Enable(true));
#elif defined(WEBRTC_AUDIOPROC_FLOAT_PROFILE)
EXPECT_EQ(apm_->kNoError,
apm_->echo_cancellation()->enable_drift_compensation(true));
EXPECT_EQ(apm_->kNoError,
apm_->echo_cancellation()->enable_metrics(true));
EXPECT_EQ(apm_->kNoError,
apm_->echo_cancellation()->enable_delay_logging(true));
EXPECT_EQ(apm_->kNoError, apm_->echo_cancellation()->Enable(true));
EXPECT_EQ(apm_->kNoError,
apm_->gain_control()->set_mode(GainControl::kAdaptiveAnalog));
EXPECT_EQ(apm_->kNoError,
apm_->gain_control()->set_analog_level_limits(0, 255));
EXPECT_EQ(apm_->kNoError, apm_->gain_control()->Enable(true));
#endif
EXPECT_EQ(apm_->kNoError,
apm_->high_pass_filter()->Enable(true));
EXPECT_EQ(apm_->kNoError,
apm_->level_estimator()->Enable(true));
EXPECT_EQ(apm_->kNoError,
apm_->noise_suppression()->Enable(true));
EXPECT_EQ(apm_->kNoError,
apm_->voice_detection()->Enable(true));
EnableAllAPComponents(apm_.get());
}
bool ApmTest::ReadFrame(FILE* file, AudioFrame* frame) {
bool ApmTest::ReadFrame(FILE* file, AudioFrame* frame,
ChannelBuffer<float>* cb) {
// The files always contain stereo audio.
size_t frame_size = frame->samples_per_channel_ * 2;
size_t read_count = fread(frame->data_,
@ -430,18 +476,39 @@ bool ApmTest::ReadFrame(FILE* file, AudioFrame* frame) {
frame->samples_per_channel_);
}
// Convert to deinterleaved float.
if (cb) {
ChannelBuffer<int16_t> cb_int(frame->samples_per_channel_,
frame->num_channels_);
Deinterleave(frame->data_,
frame->samples_per_channel_,
frame->num_channels_,
cb_int.channels());
ScaleToFloat(cb_int.data(),
frame->samples_per_channel_ * frame->num_channels_,
cb->data());
}
return true;
}
bool ApmTest::ReadFrame(FILE* file, AudioFrame* frame) {
return ReadFrame(file, frame, NULL);
}
// If the end of the file has been reached, rewind it and attempt to read the
// frame again.
void ApmTest::ReadFrameWithRewind(FILE* file, AudioFrame* frame) {
if (!ReadFrame(near_file_, frame_)) {
void ApmTest::ReadFrameWithRewind(FILE* file, AudioFrame* frame,
ChannelBuffer<float>* cb) {
if (!ReadFrame(near_file_, frame_, cb)) {
rewind(near_file_);
ASSERT_TRUE(ReadFrame(near_file_, frame_));
ASSERT_TRUE(ReadFrame(near_file_, frame_, cb));
}
}
void ApmTest::ReadFrameWithRewind(FILE* file, AudioFrame* frame) {
ReadFrameWithRewind(file, frame, NULL);
}
void ApmTest::ProcessWithDefaultStreamParameters(AudioFrame* frame) {
EXPECT_EQ(apm_->kNoError, apm_->set_stream_delay_ms(0));
apm_->echo_cancellation()->set_stream_drift_samples(0);
@ -450,6 +517,30 @@ void ApmTest::ProcessWithDefaultStreamParameters(AudioFrame* frame) {
EXPECT_EQ(apm_->kNoError, apm_->ProcessStream(frame));
}
int ApmTest::ProcessStreamChooser(bool int_format) {
if (int_format) {
return apm_->ProcessStream(frame_);
}
// TODO(ajm): Update to match the number of output channels when supported.
return apm_->ProcessStream(float_cb_->channels(),
frame_->samples_per_channel_,
frame_->sample_rate_hz_,
LayoutFromChannels(frame_->num_channels_),
LayoutFromChannels(frame_->num_channels_));
}
int ApmTest::AnalyzeReverseStreamChooser(bool int_format) {
if (int_format) {
return apm_->AnalyzeReverseStream(revframe_);
}
// TODO(ajm): Update to match the number of output channels when supported.
return apm_->AnalyzeReverseStream(
revfloat_cb_->channels(),
revframe_->samples_per_channel_,
revframe_->sample_rate_hz_,
LayoutFromChannels(revframe_->num_channels_));
}
void ApmTest::ProcessDelayVerificationTest(int delay_ms, int system_delay_ms,
int delay_min, int delay_max) {
// The |revframe_| and |frame_| should include the proper frame information,
@ -537,20 +628,21 @@ void ApmTest::ProcessDelayVerificationTest(int delay_ms, int system_delay_ms,
EXPECT_LE(expected_median_low, median);
}
TEST_F(ApmTest, StreamParameters) {
void ApmTest::StreamParametersTest(bool int_format) {
// No errors when the components are disabled.
EXPECT_EQ(apm_->kNoError,
apm_->ProcessStream(frame_));
EXPECT_EQ(apm_->kNoError, ProcessStreamChooser(int_format));
// -- Missing AGC level --
EXPECT_EQ(apm_->kNoError, apm_->gain_control()->Enable(true));
EXPECT_EQ(apm_->kStreamParameterNotSetError, apm_->ProcessStream(frame_));
EXPECT_EQ(apm_->kStreamParameterNotSetError,
ProcessStreamChooser(int_format));
// Resets after successful ProcessStream().
EXPECT_EQ(apm_->kNoError,
apm_->gain_control()->set_stream_analog_level(127));
EXPECT_EQ(apm_->kNoError, apm_->ProcessStream(frame_));
EXPECT_EQ(apm_->kStreamParameterNotSetError, apm_->ProcessStream(frame_));
EXPECT_EQ(apm_->kNoError, ProcessStreamChooser(frame_));
EXPECT_EQ(apm_->kStreamParameterNotSetError,
ProcessStreamChooser(int_format));
// Other stream parameters set correctly.
EXPECT_EQ(apm_->kNoError, apm_->echo_cancellation()->Enable(true));
@ -559,20 +651,22 @@ TEST_F(ApmTest, StreamParameters) {
EXPECT_EQ(apm_->kNoError, apm_->set_stream_delay_ms(100));
apm_->echo_cancellation()->set_stream_drift_samples(0);
EXPECT_EQ(apm_->kStreamParameterNotSetError,
apm_->ProcessStream(frame_));
ProcessStreamChooser(int_format));
EXPECT_EQ(apm_->kNoError, apm_->gain_control()->Enable(false));
EXPECT_EQ(apm_->kNoError,
apm_->echo_cancellation()->enable_drift_compensation(false));
// -- Missing delay --
EXPECT_EQ(apm_->kNoError, apm_->echo_cancellation()->Enable(true));
EXPECT_EQ(apm_->kNoError, apm_->ProcessStream(frame_));
EXPECT_EQ(apm_->kStreamParameterNotSetError, apm_->ProcessStream(frame_));
EXPECT_EQ(apm_->kNoError, ProcessStreamChooser(int_format));
EXPECT_EQ(apm_->kStreamParameterNotSetError,
ProcessStreamChooser(int_format));
// Resets after successful ProcessStream().
EXPECT_EQ(apm_->kNoError, apm_->set_stream_delay_ms(100));
EXPECT_EQ(apm_->kNoError, apm_->ProcessStream(frame_));
EXPECT_EQ(apm_->kStreamParameterNotSetError, apm_->ProcessStream(frame_));
EXPECT_EQ(apm_->kNoError, ProcessStreamChooser(int_format));
EXPECT_EQ(apm_->kStreamParameterNotSetError,
ProcessStreamChooser(int_format));
// Other stream parameters set correctly.
EXPECT_EQ(apm_->kNoError, apm_->gain_control()->Enable(true));
@ -581,37 +675,49 @@ TEST_F(ApmTest, StreamParameters) {
apm_->echo_cancellation()->set_stream_drift_samples(0);
EXPECT_EQ(apm_->kNoError,
apm_->gain_control()->set_stream_analog_level(127));
EXPECT_EQ(apm_->kStreamParameterNotSetError, apm_->ProcessStream(frame_));
EXPECT_EQ(apm_->kStreamParameterNotSetError,
ProcessStreamChooser(int_format));
EXPECT_EQ(apm_->kNoError, apm_->gain_control()->Enable(false));
// -- Missing drift --
EXPECT_EQ(apm_->kStreamParameterNotSetError, apm_->ProcessStream(frame_));
EXPECT_EQ(apm_->kStreamParameterNotSetError,
ProcessStreamChooser(int_format));
// Resets after successful ProcessStream().
EXPECT_EQ(apm_->kNoError, apm_->set_stream_delay_ms(100));
apm_->echo_cancellation()->set_stream_drift_samples(0);
EXPECT_EQ(apm_->kNoError, apm_->ProcessStream(frame_));
EXPECT_EQ(apm_->kStreamParameterNotSetError, apm_->ProcessStream(frame_));
EXPECT_EQ(apm_->kNoError, ProcessStreamChooser(int_format));
EXPECT_EQ(apm_->kStreamParameterNotSetError,
ProcessStreamChooser(int_format));
// Other stream parameters set correctly.
EXPECT_EQ(apm_->kNoError, apm_->gain_control()->Enable(true));
EXPECT_EQ(apm_->kNoError, apm_->set_stream_delay_ms(100));
EXPECT_EQ(apm_->kNoError,
apm_->gain_control()->set_stream_analog_level(127));
EXPECT_EQ(apm_->kStreamParameterNotSetError, apm_->ProcessStream(frame_));
EXPECT_EQ(apm_->kStreamParameterNotSetError,
ProcessStreamChooser(int_format));
// -- No stream parameters --
EXPECT_EQ(apm_->kNoError,
apm_->AnalyzeReverseStream(revframe_));
AnalyzeReverseStreamChooser(int_format));
EXPECT_EQ(apm_->kStreamParameterNotSetError,
apm_->ProcessStream(frame_));
ProcessStreamChooser(int_format));
// -- All there --
EXPECT_EQ(apm_->kNoError, apm_->set_stream_delay_ms(100));
apm_->echo_cancellation()->set_stream_drift_samples(0);
EXPECT_EQ(apm_->kNoError,
apm_->gain_control()->set_stream_analog_level(127));
EXPECT_EQ(apm_->kNoError, apm_->ProcessStream(frame_));
EXPECT_EQ(apm_->kNoError, ProcessStreamChooser(int_format));
}
TEST_F(ApmTest, StreamParametersInt) {
StreamParametersTest(false);
}
TEST_F(ApmTest, StreamParametersFloat) {
StreamParametersTest(true);
}
TEST_F(ApmTest, DefaultDelayOffsetIsZero) {
@ -657,19 +763,27 @@ TEST_F(ApmTest, Channels) {
}
}
TEST_F(ApmTest, SampleRates) {
void ApmTest::SampleRatesTest(bool int_format) {
// Testing invalid sample rates
SetFrameSampleRate(frame_, 10000);
EXPECT_EQ(apm_->kBadSampleRateError, apm_->ProcessStream(frame_));
EXPECT_EQ(apm_->kBadSampleRateError, ProcessStreamChooser(int_format));
// Testing valid sample rates
int fs[] = {8000, 16000, 32000};
for (size_t i = 0; i < sizeof(fs) / sizeof(*fs); i++) {
SetFrameSampleRate(frame_, fs[i]);
EXPECT_EQ(kNoErr, apm_->ProcessStream(frame_));
EXPECT_EQ(kNoErr, ProcessStreamChooser(int_format));
EXPECT_EQ(fs[i], apm_->sample_rate_hz());
}
}
TEST_F(ApmTest, SampleRatesInt) {
SampleRatesTest(false);
}
TEST_F(ApmTest, SampleRatesFloat) {
SampleRatesTest(true);
}
TEST_F(ApmTest, EchoCancellation) {
EXPECT_EQ(apm_->kNoError,
apm_->echo_cancellation()->enable_drift_compensation(true));
@ -1256,13 +1370,11 @@ TEST_F(ApmTest, IdenticalInputChannelsResultInIdenticalOutputChannels) {
int analog_level = 127;
EXPECT_EQ(0, feof(far_file_));
EXPECT_EQ(0, feof(near_file_));
while (1) {
if (!ReadFrame(far_file_, revframe_)) break;
while (ReadFrame(far_file_, revframe_) && ReadFrame(near_file_, frame_)) {
CopyLeftToRightChannel(revframe_->data_, revframe_->samples_per_channel_);
EXPECT_EQ(apm_->kNoError, apm_->AnalyzeReverseStream(revframe_));
if (!ReadFrame(near_file_, frame_)) break;
CopyLeftToRightChannel(frame_->data_, frame_->samples_per_channel_);
frame_->vad_activity_ = AudioFrame::kVadUnknown;
@ -1416,6 +1528,90 @@ TEST_F(ApmTest, DebugDumpFromFileHandle) {
#endif // WEBRTC_AUDIOPROC_DEBUG_DUMP
}
TEST_F(ApmTest, FloatAndIntInterfacesGiveIdenticalResults) {
audioproc::OutputData ref_data;
ReadMessageLiteFromFile(ref_filename_, &ref_data);
Config config;
config.Set<ExperimentalAgc>(new ExperimentalAgc(false));
scoped_ptr<AudioProcessing> fapm(AudioProcessing::Create(config));
EnableAllComponents();
EnableAllAPComponents(fapm.get());
for (int i = 0; i < ref_data.test_size(); i++) {
printf("Running test %d of %d...\n", i + 1, ref_data.test_size());
audioproc::Test* test = ref_data.mutable_test(i);
// TODO(ajm): Restore downmixing test cases.
if (test->num_input_channels() != test->num_output_channels())
continue;
const int num_render_channels = test->num_reverse_channels();
const int num_input_channels = test->num_input_channels();
const int num_output_channels = test->num_output_channels();
const int samples_per_channel = test->sample_rate() * kChunkSizeMs / 1000;
const int output_length = samples_per_channel * num_output_channels;
Init(test->sample_rate(), num_render_channels, num_input_channels,
num_output_channels, true);
Init(fapm.get());
ChannelBuffer<int16_t> output_cb(samples_per_channel, num_input_channels);
scoped_ptr<int16_t[]> output_int16(new int16_t[output_length]);
int analog_level = 127;
while (ReadFrame(far_file_, revframe_, revfloat_cb_.get()) &&
ReadFrame(near_file_, frame_, float_cb_.get())) {
frame_->vad_activity_ = AudioFrame::kVadUnknown;
EXPECT_NOERR(apm_->AnalyzeReverseStream(revframe_));
EXPECT_NOERR(fapm->AnalyzeReverseStream(
revfloat_cb_->channels(),
samples_per_channel,
test->sample_rate(),
LayoutFromChannels(num_render_channels)));
EXPECT_NOERR(apm_->set_stream_delay_ms(0));
EXPECT_NOERR(fapm->set_stream_delay_ms(0));
apm_->echo_cancellation()->set_stream_drift_samples(0);
fapm->echo_cancellation()->set_stream_drift_samples(0);
EXPECT_NOERR(apm_->gain_control()->set_stream_analog_level(analog_level));
EXPECT_NOERR(fapm->gain_control()->set_stream_analog_level(analog_level));
EXPECT_NOERR(apm_->ProcessStream(frame_));
EXPECT_NOERR(fapm->ProcessStream(
float_cb_->channels(),
samples_per_channel,
test->sample_rate(),
LayoutFromChannels(num_input_channels),
LayoutFromChannels(num_output_channels)));
// Convert to interleaved int16.
ScaleAndRoundToInt16(float_cb_->data(), output_length, output_cb.data());
Interleave(output_cb.channels(),
samples_per_channel,
num_output_channels,
output_int16.get());
// Verify float and int16 paths produce identical output.
EXPECT_EQ(0, memcmp(frame_->data_, output_int16.get(), output_length));
analog_level = fapm->gain_control()->stream_analog_level();
EXPECT_EQ(apm_->gain_control()->stream_analog_level(),
fapm->gain_control()->stream_analog_level());
EXPECT_EQ(apm_->echo_cancellation()->stream_has_echo(),
fapm->echo_cancellation()->stream_has_echo());
EXPECT_EQ(apm_->voice_detection()->stream_has_voice(),
fapm->voice_detection()->stream_has_voice());
EXPECT_EQ(apm_->noise_suppression()->speech_probability(),
fapm->noise_suppression()->speech_probability());
// Reset in case of downmixing.
frame_->num_channels_ = test->num_input_channels();
}
rewind(far_file_);
rewind(near_file_);
}
}
// TODO(andrew): Add a test to process a few frames with different combinations
// of enabled components.
@ -1466,11 +1662,9 @@ TEST_F(ApmTest, DISABLED_ON_ANDROID(Process)) {
int max_output_average = 0;
float ns_speech_prob_average = 0.0f;
while (1) {
if (!ReadFrame(far_file_, revframe_)) break;
while (ReadFrame(far_file_, revframe_) && ReadFrame(near_file_, frame_)) {
EXPECT_EQ(apm_->kNoError, apm_->AnalyzeReverseStream(revframe_));
if (!ReadFrame(near_file_, frame_)) break;
frame_->vad_activity_ = AudioFrame::kVadUnknown;
EXPECT_EQ(apm_->kNoError, apm_->set_stream_delay_ms(0));
@ -1479,6 +1673,7 @@ TEST_F(ApmTest, DISABLED_ON_ANDROID(Process)) {
apm_->gain_control()->set_stream_analog_level(analog_level));
EXPECT_EQ(apm_->kNoError, apm_->ProcessStream(frame_));
// Ensure the frame was downmixed properly.
EXPECT_EQ(test->num_output_channels(), frame_->num_channels_);

View File

@ -68,7 +68,7 @@
#if !defined(_MSC_VER)
#include <stdint.h>
#else
// Define C99 equivalent types, since MSVC doesn't provide stdint.h.
// Define C99 equivalent types, since pre-2010 MSVC doesn't provide stdint.h.
typedef signed char int8_t;
typedef signed short int16_t;
typedef signed int int32_t;