diff --git a/webrtc/modules/audio_processing/audio_processing_impl.cc b/webrtc/modules/audio_processing/audio_processing_impl.cc index 2f488c8e40..3877a389db 100644 --- a/webrtc/modules/audio_processing/audio_processing_impl.cc +++ b/webrtc/modules/audio_processing/audio_processing_impl.cc @@ -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::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; } diff --git a/webrtc/modules/audio_processing/include/audio_processing.h b/webrtc/modules/audio_processing/include/audio_processing.h index 75b3e2073c..a70dd2cd2b 100644 --- a/webrtc/modules/audio_processing/include/audio_processing.h +++ b/webrtc/modules/audio_processing/include/audio_processing.h @@ -13,8 +13,8 @@ #include // 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 diff --git a/webrtc/modules/audio_processing/test/process_test.cc b/webrtc/modules/audio_processing/test/process_test.cc index 57f1719014..e68d1e9128 100644 --- a/webrtc/modules/audio_processing/test/process_test.cc +++ b/webrtc/modules/audio_processing/test/process_test.cc @@ -8,12 +8,15 @@ * be found in the AUTHORS file in the root of the source tree. */ +#include #include #include #ifdef WEBRTC_ANDROID #include #endif +#include + #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(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(); } diff --git a/webrtc/modules/audio_processing/test/unit_test.cc b/webrtc/modules/audio_processing/test/unit_test.cc index d0520a03af..51340e959a 100644 --- a/webrtc/modules/audio_processing/test/unit_test.cc +++ b/webrtc/modules/audio_processing/test/unit_test.cc @@ -8,13 +8,14 @@ * be found in the AUTHORS file in the root of the source tree. */ +#include "audio_processing.h" + #include #include #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(stereo[i * 2]) + + static_cast(stereo[i * 2 + 1])) >> 1; + mono[i] = static_cast(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 +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), + 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), + 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 + 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(stereo[i * 2]) + - static_cast(stereo[i * 2 + 1])) >> 1; - mono[i] = static_cast(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 +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 -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), - 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), - 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(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 threads(num_threads); - std::vector 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,