Only reinitialize AudioProcessing when needed.

This takes away the burden from the user, resulting in cleaner code.

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

git-svn-id: http://webrtc.googlecode.com/svn/trunk@3010 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
andrew@webrtc.org 2012-10-27 00:28:27 +00:00
parent e08adf0957
commit 8186534111
4 changed files with 274 additions and 247 deletions

View File

@ -100,31 +100,32 @@ AudioProcessingImpl::AudioProcessingImpl(int id)
}
AudioProcessingImpl::~AudioProcessingImpl() {
crit_->Enter();
while (!component_list_.empty()) {
ProcessingComponent* component = component_list_.front();
component->Destroy();
delete component;
component_list_.pop_front();
}
{
CriticalSectionScoped crit_scoped(crit_);
while (!component_list_.empty()) {
ProcessingComponent* component = component_list_.front();
component->Destroy();
delete component;
component_list_.pop_front();
}
#ifdef WEBRTC_AUDIOPROC_DEBUG_DUMP
if (debug_file_->Open()) {
debug_file_->CloseFile();
}
if (debug_file_->Open()) {
debug_file_->CloseFile();
}
#endif
if (render_audio_) {
delete render_audio_;
render_audio_ = NULL;
if (render_audio_) {
delete render_audio_;
render_audio_ = NULL;
}
if (capture_audio_) {
delete capture_audio_;
capture_audio_ = NULL;
}
}
if (capture_audio_) {
delete capture_audio_;
capture_audio_ = NULL;
}
crit_->Leave();
delete crit_;
crit_ = NULL;
}
@ -162,7 +163,7 @@ int AudioProcessingImpl::InitializeLocked() {
// Initialize all components.
std::list<ProcessingComponent*>::iterator it;
for (it = component_list_.begin(); it != component_list_.end(); it++) {
for (it = component_list_.begin(); it != component_list_.end(); ++it) {
int err = (*it)->Initialize();
if (err != kNoError) {
return err;
@ -183,6 +184,9 @@ int AudioProcessingImpl::InitializeLocked() {
int AudioProcessingImpl::set_sample_rate_hz(int rate) {
CriticalSectionScoped crit_scoped(crit_);
if (rate == sample_rate_hz_) {
return kNoError;
}
if (rate != kSampleRate8kHz &&
rate != kSampleRate16kHz &&
rate != kSampleRate32kHz) {
@ -207,6 +211,9 @@ int AudioProcessingImpl::sample_rate_hz() const {
int AudioProcessingImpl::set_num_reverse_channels(int channels) {
CriticalSectionScoped crit_scoped(crit_);
if (channels == num_reverse_channels_) {
return kNoError;
}
// Only stereo supported currently.
if (channels > 2 || channels < 1) {
return kBadParameterError;
@ -225,16 +232,16 @@ int AudioProcessingImpl::set_num_channels(
int input_channels,
int output_channels) {
CriticalSectionScoped crit_scoped(crit_);
if (input_channels == num_input_channels_ &&
output_channels == num_output_channels_) {
return kNoError;
}
if (output_channels > input_channels) {
return kBadParameterError;
}
// Only stereo supported currently.
if (input_channels > 2 || input_channels < 1) {
return kBadParameterError;
}
if (output_channels > 2 || output_channels < 1) {
if (input_channels > 2 || input_channels < 1 ||
output_channels > 2 || output_channels < 1) {
return kBadParameterError;
}

View File

@ -13,8 +13,8 @@
#include <stddef.h> // size_t
#include "typedefs.h"
#include "module.h"
#include "typedefs.h"
namespace webrtc {
@ -124,6 +124,10 @@ class AudioProcessing : public Module {
// should be called before beginning to process a new audio stream. However,
// it is not necessary to call before processing the first stream after
// creation.
//
// set_sample_rate_hz(), set_num_channels() and set_num_reverse_channels()
// will trigger a full initialization if the settings are changed from their
// existing values. Otherwise they are no-ops.
virtual int Initialize() = 0;
// Sets the sample |rate| in Hz for both the primary and reverse audio

View File

@ -8,12 +8,15 @@
* be found in the AUTHORS file in the root of the source tree.
*/
#include <math.h>
#include <stdio.h>
#include <string.h>
#ifdef WEBRTC_ANDROID
#include <sys/stat.h>
#endif
#include <algorithm>
#include "gtest/gtest.h"
#include "audio_processing.h"
@ -131,6 +134,22 @@ void usage() {
printf(" --debug_file FILE Dump a debug recording.\n");
}
static double MicLevel2Gain(int level) {
return pow(10.0, ((level - 127.0) / 128.0 * 80.) / 20.);
}
static void SimulateMic(int mic_level, AudioFrame* frame) {
mic_level = std::min(std::max(mic_level, 0), 255);
double mic_gain = MicLevel2Gain(mic_level);
int num_samples = frame->samples_per_channel_ * frame->num_channels_;
double v;
for (int n = 0; n < num_samples; n++) {
v = floor(frame->data_[n] * mic_gain + 0.5);
v = std::max(std::min(32767., v), -32768.);
frame->data_[n] = static_cast<int16_t>(v);
}
}
// void function for gtest.
void void_main(int argc, char* argv[]) {
if (argc > 1 && strcmp(argv[1], "--help") == 0) {
@ -658,6 +677,10 @@ void void_main(int argc, char* argv[]) {
fflush(stdout);
}
if (apm->gain_control()->mode() == GainControl::kAdaptiveAnalog) {
SimulateMic(capture_level, &near_frame);
}
if (perf_testing) {
t0 = TickTime::Now();
}
@ -862,6 +885,10 @@ void void_main(int argc, char* argv[]) {
fread(&drift_samples, sizeof(drift_samples), 1, drift_file));
}
if (apm->gain_control()->mode() == GainControl::kAdaptiveAnalog) {
SimulateMic(capture_level, &near_frame);
}
if (perf_testing) {
t0 = TickTime::Now();
}

View File

@ -8,13 +8,14 @@
* be found in the AUTHORS file in the root of the source tree.
*/
#include "audio_processing.h"
#include <stdio.h>
#include <algorithm>
#include "gtest/gtest.h"
#include "audio_processing.h"
#include "event_wrapper.h"
#include "module_common_types.h"
#include "scoped_ptr.h"
@ -66,6 +67,138 @@ const int kProcessSampleRates[] = {8000, 16000, 32000};
const size_t kProcessSampleRatesSize = sizeof(kProcessSampleRates) /
sizeof(*kProcessSampleRates);
// TODO(andrew): Use the MonoToStereo routine from AudioFrameOperations.
void MixStereoToMono(const int16_t* stereo,
int16_t* mono,
int samples_per_channel) {
for (int i = 0; i < samples_per_channel; i++) {
int32_t int32 = (static_cast<int32_t>(stereo[i * 2]) +
static_cast<int32_t>(stereo[i * 2 + 1])) >> 1;
mono[i] = static_cast<int16_t>(int32);
}
}
void CopyLeftToRightChannel(int16_t* stereo, int samples_per_channel) {
for (int i = 0; i < samples_per_channel; i++) {
stereo[i * 2 + 1] = stereo[i * 2];
}
}
void VerifyChannelsAreEqual(int16_t* stereo, int samples_per_channel) {
for (int i = 0; i < samples_per_channel; i++) {
EXPECT_EQ(stereo[i * 2 + 1], stereo[i * 2]);
}
}
void SetFrameTo(AudioFrame* frame, int16_t value) {
for (int i = 0; i < frame->samples_per_channel_ * frame->num_channels_;
++i) {
frame->data_[i] = value;
}
}
void SetFrameTo(AudioFrame* frame, int16_t left, int16_t right) {
ASSERT_EQ(2, frame->num_channels_);
for (int i = 0; i < frame->samples_per_channel_ * 2; i += 2) {
frame->data_[i] = left;
frame->data_[i + 1] = right;
}
}
template <class T>
T AbsValue(T a) {
return a > 0 ? a: -a;
}
int16_t MaxAudioFrame(const AudioFrame& frame) {
const int length = frame.samples_per_channel_ * frame.num_channels_;
int16_t max_data = AbsValue(frame.data_[0]);
for (int i = 1; i < length; i++) {
max_data = std::max(max_data, AbsValue(frame.data_[i]));
}
return max_data;
}
bool FrameDataAreEqual(const AudioFrame& frame1, const AudioFrame& frame2) {
if (frame1.samples_per_channel_ !=
frame2.samples_per_channel_) {
return false;
}
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))) {
return false;
}
return true;
}
void TestStats(const AudioProcessing::Statistic& test,
const webrtc::audioproc::Test::Statistic& reference) {
EXPECT_EQ(reference.instant(), test.instant);
EXPECT_EQ(reference.average(), test.average);
EXPECT_EQ(reference.maximum(), test.maximum);
EXPECT_EQ(reference.minimum(), test.minimum);
}
void WriteStatsMessage(const AudioProcessing::Statistic& output,
webrtc::audioproc::Test::Statistic* message) {
message->set_instant(output.instant);
message->set_average(output.average);
message->set_maximum(output.maximum);
message->set_minimum(output.minimum);
}
void WriteMessageLiteToFile(const std::string filename,
const ::google::protobuf::MessageLite& message) {
FILE* file = fopen(filename.c_str(), "wb");
ASSERT_TRUE(file != NULL) << "Could not open " << filename;
int size = message.ByteSize();
ASSERT_GT(size, 0);
unsigned char* array = new unsigned char[size];
ASSERT_TRUE(message.SerializeToArray(array, size));
ASSERT_EQ(1u, fwrite(&size, sizeof(int), 1, file));
ASSERT_EQ(static_cast<size_t>(size),
fwrite(array, sizeof(unsigned char), size, file));
delete [] array;
fclose(file);
}
void ReadMessageLiteFromFile(const std::string filename,
::google::protobuf::MessageLite* message) {
assert(message != NULL);
FILE* file = fopen(filename.c_str(), "rb");
ASSERT_TRUE(file != NULL) << "Could not open " << filename;
int size = 0;
ASSERT_EQ(1u, fread(&size, sizeof(int), 1, file));
ASSERT_GT(size, 0);
unsigned char* array = new unsigned char[size];
ASSERT_EQ(static_cast<size_t>(size),
fread(array, sizeof(unsigned char), size, file));
ASSERT_TRUE(message->ParseFromArray(array, size));
delete [] array;
fclose(file);
}
struct ThreadData {
ThreadData(int thread_num_, AudioProcessing* ap_)
: thread_num(thread_num_),
error(false),
ap(ap_) {}
int thread_num;
bool error;
AudioProcessing* ap;
};
class ApmTest : public ::testing::Test {
protected:
ApmTest();
@ -75,7 +208,7 @@ class ApmTest : public ::testing::Test {
static void SetUpTestCase() {
Trace::CreateTrace();
std::string trace_filename = webrtc::test::OutputPath() +
"audioproc_trace.txt";
"audioproc_trace.txt";
ASSERT_EQ(0, Trace::SetTraceFile(trace_filename.c_str()));
}
@ -94,6 +227,10 @@ class ApmTest : public ::testing::Test {
int num_output_channels);
void EnableAllComponents();
bool ReadFrame(FILE* file, AudioFrame* frame);
void ProcessWithDefaultStreamParameters(AudioFrame* frame);
template <typename F>
void ChangeTriggersInit(F f, AudioProcessing* ap, int initial_value,
int changed_value);
const std::string output_path_;
const std::string ref_path_;
@ -241,28 +378,6 @@ void ApmTest::Init(int sample_rate_hz, int num_reverse_channels,
}
}
void MixStereoToMono(const int16_t* stereo,
int16_t* mono,
int samples_per_channel) {
for (int i = 0; i < samples_per_channel; i++) {
int32_t int32 = (static_cast<int32_t>(stereo[i * 2]) +
static_cast<int32_t>(stereo[i * 2 + 1])) >> 1;
mono[i] = static_cast<int16_t>(int32);
}
}
void CopyLeftToRightChannel(int16_t* stereo, int samples_per_channel) {
for (int i = 0; i < samples_per_channel; i++) {
stereo[i * 2 + 1] = stereo[i * 2];
}
}
void VerifyChannelsAreEqual(int16_t* stereo, int samples_per_channel) {
for (int i = 0; i < samples_per_channel; i++) {
EXPECT_EQ(stereo[i * 2 + 1], stereo[i * 2]);
}
}
void ApmTest::EnableAllComponents() {
#if defined(WEBRTC_AUDIOPROC_FIXED_PROFILE)
EXPECT_EQ(apm_->kNoError, apm_->set_sample_rate_hz(16000));
@ -321,206 +436,57 @@ bool ApmTest::ReadFrame(FILE* file, AudioFrame* frame) {
return true;
}
void SetFrameTo(AudioFrame* frame, int16_t value) {
for (int i = 0; i < frame->samples_per_channel_ * frame->num_channels_;
++i) {
frame->data_[i] = value;
}
void ApmTest::ProcessWithDefaultStreamParameters(AudioFrame* frame) {
EXPECT_EQ(apm_->kNoError, apm_->set_stream_delay_ms(0));
EXPECT_EQ(apm_->kNoError,
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));
}
void SetFrameTo(AudioFrame* frame, int16_t left, int16_t right) {
ASSERT_EQ(2, frame->num_channels_);
for (int i = 0; i < frame->samples_per_channel_ * 2; i += 2) {
frame->data_[i] = left;
frame->data_[i + 1] = right;
}
template <typename F>
void ApmTest::ChangeTriggersInit(F f, AudioProcessing* ap, int initial_value,
int changed_value) {
EnableAllComponents();
Init(16000, 2, 2, 2, false);
SetFrameTo(frame_, 1000);
AudioFrame frame_copy = *frame_;
ProcessWithDefaultStreamParameters(frame_);
// Verify the processing has actually changed the frame.
EXPECT_FALSE(FrameDataAreEqual(*frame_, frame_copy));
// Test that a change in value triggers an init.
f(apm_, changed_value);
f(apm_, initial_value);
ProcessWithDefaultStreamParameters(&frame_copy);
EXPECT_TRUE(FrameDataAreEqual(*frame_, frame_copy));
apm_->Initialize();
SetFrameTo(frame_, 1000);
AudioFrame initial_frame = *frame_;
ProcessWithDefaultStreamParameters(frame_);
ProcessWithDefaultStreamParameters(frame_);
// Verify the processing has actually changed the frame.
EXPECT_FALSE(FrameDataAreEqual(*frame_, initial_frame));
frame_copy = initial_frame;
apm_->Initialize();
ProcessWithDefaultStreamParameters(&frame_copy);
// Verify an init here would result in different output.
apm_->Initialize();
ProcessWithDefaultStreamParameters(&frame_copy);
EXPECT_FALSE(FrameDataAreEqual(*frame_, frame_copy));
frame_copy = initial_frame;
apm_->Initialize();
ProcessWithDefaultStreamParameters(&frame_copy);
// Test that the same value does not trigger an init.
f(apm_, initial_value);
ProcessWithDefaultStreamParameters(&frame_copy);
EXPECT_TRUE(FrameDataAreEqual(*frame_, frame_copy));
}
template <class T>
T AbsValue(T a) {
return a > 0 ? a: -a;
}
int16_t MaxAudioFrame(const AudioFrame& frame) {
const int length = frame.samples_per_channel_ * frame.num_channels_;
int16_t max_data = AbsValue(frame.data_[0]);
for (int i = 1; i < length; i++) {
max_data = std::max(max_data, AbsValue(frame.data_[i]));
}
return max_data;
}
bool FrameDataAreEqual(const AudioFrame& frame1, const AudioFrame& frame2) {
if (frame1.samples_per_channel_ !=
frame2.samples_per_channel_) {
return false;
}
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))) {
return false;
}
return true;
}
void TestStats(const AudioProcessing::Statistic& test,
const webrtc::audioproc::Test::Statistic& reference) {
EXPECT_EQ(reference.instant(), test.instant);
EXPECT_EQ(reference.average(), test.average);
EXPECT_EQ(reference.maximum(), test.maximum);
EXPECT_EQ(reference.minimum(), test.minimum);
}
void WriteStatsMessage(const AudioProcessing::Statistic& output,
webrtc::audioproc::Test::Statistic* message) {
message->set_instant(output.instant);
message->set_average(output.average);
message->set_maximum(output.maximum);
message->set_minimum(output.minimum);
}
void WriteMessageLiteToFile(const std::string filename,
const ::google::protobuf::MessageLite& message) {
FILE* file = fopen(filename.c_str(), "wb");
ASSERT_TRUE(file != NULL) << "Could not open " << filename;
int size = message.ByteSize();
ASSERT_GT(size, 0);
unsigned char* array = new unsigned char[size];
ASSERT_TRUE(message.SerializeToArray(array, size));
ASSERT_EQ(1u, fwrite(&size, sizeof(int), 1, file));
ASSERT_EQ(static_cast<size_t>(size),
fwrite(array, sizeof(unsigned char), size, file));
delete [] array;
fclose(file);
}
void ReadMessageLiteFromFile(const std::string filename,
::google::protobuf::MessageLite* message) {
assert(message != NULL);
FILE* file = fopen(filename.c_str(), "rb");
ASSERT_TRUE(file != NULL) << "Could not open " << filename;
int size = 0;
ASSERT_EQ(1u, fread(&size, sizeof(int), 1, file));
ASSERT_GT(size, 0);
unsigned char* array = new unsigned char[size];
ASSERT_EQ(static_cast<size_t>(size),
fread(array, sizeof(unsigned char), size, file));
ASSERT_TRUE(message->ParseFromArray(array, size));
delete [] array;
fclose(file);
}
struct ThreadData {
ThreadData(int thread_num_, AudioProcessing* ap_)
: thread_num(thread_num_),
error(false),
ap(ap_) {}
int thread_num;
bool error;
AudioProcessing* ap;
};
// Don't use GTest here; non-thread-safe on Windows (as of 1.5.0).
bool DeadlockProc(void* thread_object) {
ThreadData* thread_data = static_cast<ThreadData*>(thread_object);
AudioProcessing* ap = thread_data->ap;
int err = ap->kNoError;
AudioFrame primary_frame;
AudioFrame reverse_frame;
primary_frame.samples_per_channel_ = 320;
primary_frame.num_channels_ = 2;
primary_frame.sample_rate_hz_ = 32000;
reverse_frame.samples_per_channel_ = 320;
reverse_frame.num_channels_ = 2;
reverse_frame.sample_rate_hz_ = 32000;
ap->echo_cancellation()->Enable(true);
ap->gain_control()->Enable(true);
ap->high_pass_filter()->Enable(true);
ap->level_estimator()->Enable(true);
ap->noise_suppression()->Enable(true);
ap->voice_detection()->Enable(true);
if (thread_data->thread_num % 2 == 0) {
err = ap->AnalyzeReverseStream(&reverse_frame);
if (err != ap->kNoError) {
printf("Error in AnalyzeReverseStream(): %d\n", err);
thread_data->error = true;
return false;
}
}
if (thread_data->thread_num % 2 == 1) {
ap->set_stream_delay_ms(0);
ap->echo_cancellation()->set_stream_drift_samples(0);
ap->gain_control()->set_stream_analog_level(0);
err = ap->ProcessStream(&primary_frame);
if (err == ap->kStreamParameterNotSetError) {
printf("Expected kStreamParameterNotSetError in ProcessStream(): %d\n",
err);
} else if (err != ap->kNoError) {
printf("Error in ProcessStream(): %d\n", err);
thread_data->error = true;
return false;
}
ap->gain_control()->stream_analog_level();
}
EventWrapper* event = EventWrapper::Create();
event->Wait(1);
delete event;
event = NULL;
return true;
}
/*TEST_F(ApmTest, Deadlock) {
const int num_threads = 16;
std::vector<ThreadWrapper*> threads(num_threads);
std::vector<ThreadData*> thread_data(num_threads);
ASSERT_EQ(apm_->kNoError, apm_->set_sample_rate_hz(32000));
ASSERT_EQ(apm_->kNoError, apm_->set_num_channels(2, 2));
ASSERT_EQ(apm_->kNoError, apm_->set_num_reverse_channels(2));
for (int i = 0; i < num_threads; i++) {
thread_data[i] = new ThreadData(i, apm_);
threads[i] = ThreadWrapper::CreateThread(DeadlockProc,
thread_data[i],
kNormalPriority,
0);
ASSERT_TRUE(threads[i] != NULL);
unsigned int thread_id = 0;
threads[i]->Start(thread_id);
}
EventWrapper* event = EventWrapper::Create();
ASSERT_EQ(kEventTimeout, event->Wait(5000));
delete event;
event = NULL;
for (int i = 0; i < num_threads; i++) {
// This will return false if the thread has deadlocked.
ASSERT_TRUE(threads[i]->Stop());
ASSERT_FALSE(thread_data[i]->error);
delete threads[i];
threads[i] = NULL;
delete thread_data[i];
thread_data[i] = NULL;
}
}*/
TEST_F(ApmTest, StreamParameters) {
// No errors when the components are disabled.
EXPECT_EQ(apm_->kNoError,
@ -665,6 +631,29 @@ TEST_F(ApmTest, SampleRates) {
}
}
void SetSampleRate(AudioProcessing* ap, int value) {
EXPECT_EQ(ap->kNoError, ap->set_sample_rate_hz(value));
}
void SetNumReverseChannels(AudioProcessing* ap, int value) {
EXPECT_EQ(ap->kNoError, ap->set_num_reverse_channels(value));
}
void SetNumOutputChannels(AudioProcessing* ap, int value) {
EXPECT_EQ(ap->kNoError, ap->set_num_channels(2, value));
}
TEST_F(ApmTest, SampleRateChangeTriggersInit) {
ChangeTriggersInit(SetSampleRate, apm_, 16000, 8000);
}
TEST_F(ApmTest, ReverseChannelChangeTriggersInit) {
ChangeTriggersInit(SetNumReverseChannels, apm_, 2, 1);
}
TEST_F(ApmTest, ChannelChangeTriggersInit) {
ChangeTriggersInit(SetNumOutputChannels, apm_, 2, 1);
}
TEST_F(ApmTest, EchoCancellation) {
EXPECT_EQ(apm_->kNoError,