diff --git a/src/modules/audio_processing/aecm/main/interface/echo_control_mobile.h b/src/modules/audio_processing/aecm/main/interface/echo_control_mobile.h index 26b117272..d50da7144 100644 --- a/src/modules/audio_processing/aecm/main/interface/echo_control_mobile.h +++ b/src/modules/audio_processing/aecm/main/interface/echo_control_mobile.h @@ -74,16 +74,14 @@ WebRtc_Word32 WebRtcAecm_Free(void *aecmInst); * ------------------------------------------------------------------- * void *aecmInst Pointer to the AECM instance * WebRtc_Word32 sampFreq Sampling frequency of data - * WebRtc_Word32 scSampFreq Soundcard sampling frequency * * Outputs Description * ------------------------------------------------------------------- - * WebRtc_Word32 return 0: OK + * WebRtc_Word32 return 0: OK * -1: error */ WebRtc_Word32 WebRtcAecm_Init(void* aecmInst, - WebRtc_Word32 sampFreq, - WebRtc_Word32 scSampFreq); + WebRtc_Word32 sampFreq); /* * Inserts an 80 or 160 sample block of data into the farend buffer. @@ -170,6 +168,52 @@ WebRtc_Word32 WebRtcAecm_set_config(void* aecmInst, WebRtc_Word32 WebRtcAecm_get_config(void *aecmInst, AecmConfig *config); +/* + * This function enables the user to set the echo path on-the-fly. + * + * Inputs Description + * ------------------------------------------------------------------- + * void* aecmInst Pointer to the AECM instance + * void* echo_path Pointer to the echo path to be set + * int size_bytes Size in bytes of the echo path + * + * Outputs Description + * ------------------------------------------------------------------- + * WebRtc_Word32 return 0: OK + * -1: error + */ +WebRtc_Word32 WebRtcAecm_InitEchoPath(void* aecmInst, + const void* echo_path, + int size_bytes); + +/* + * This function enables the user to get the currently used echo path + * on-the-fly + * + * Inputs Description + * ------------------------------------------------------------------- + * void* aecmInst Pointer to the AECM instance + * void* echo_path Pointer to echo path + * int size_bytes Size in bytes of the echo path + * + * Outputs Description + * ------------------------------------------------------------------- + * WebRtc_Word32 return 0: OK + * -1: error + */ +WebRtc_Word32 WebRtcAecm_GetEchoPath(void* aecmInst, + void* echo_path, + int size_bytes); + +/* + * This function enables the user to get the echo path size in bytes + * + * Outputs Description + * ------------------------------------------------------------------- + * int return : size in bytes + */ +int WebRtcAecm_echo_path_size_bytes(); + /* * Gets the last error code. * diff --git a/src/modules/audio_processing/aecm/main/source/aecm_core.c b/src/modules/audio_processing/aecm/main/source/aecm_core.c index f17f1bf23..28a3b1c64 100644 --- a/src/modules/audio_processing/aecm/main/source/aecm_core.c +++ b/src/modules/audio_processing/aecm/main/source/aecm_core.c @@ -253,6 +253,27 @@ int WebRtcAecm_CreateCore(AecmCore_t **aecmInst) return 0; } +void WebRtcAecm_InitEchoPathCore(AecmCore_t* aecm, const WebRtc_Word16* echo_path) +{ + int i = 0; + + // Reset the stored channel + memcpy(aecm->channelStored, echo_path, sizeof(WebRtc_Word16) * PART_LEN1); + // Reset the adapted channels + memcpy(aecm->channelAdapt16, echo_path, sizeof(WebRtc_Word16) * PART_LEN1); + for (i = 0; i < PART_LEN1; i++) + { + aecm->channelAdapt32[i] = WEBRTC_SPL_LSHIFT_W32( + (WebRtc_Word32)(aecm->channelAdapt16[i]), 16); + } + + // Reset channel storing variables + aecm->mseAdaptOld = 1000; + aecm->mseStoredOld = 1000; + aecm->mseThreshold = WEBRTC_SPL_WORD32_MAX; + aecm->mseChannelCount = 0; +} + // WebRtcAecm_InitCore(...) // // This function initializes the AECM instant created with WebRtcAecm_CreateCore(...) @@ -329,17 +350,11 @@ int WebRtcAecm_InitCore(AecmCore_t * const aecm, int samplingFreq) // Initialize the echo channels with a stored shape. if (samplingFreq == 8000) { - memcpy(aecm->channelAdapt16, kChannelStored8kHz, sizeof(WebRtc_Word16) * PART_LEN1); + WebRtcAecm_InitEchoPathCore(aecm, kChannelStored8kHz); } else { - memcpy(aecm->channelAdapt16, kChannelStored16kHz, sizeof(WebRtc_Word16) * PART_LEN1); - } - memcpy(aecm->channelStored, aecm->channelAdapt16, sizeof(WebRtc_Word16) * PART_LEN1); - for (i = 0; i < PART_LEN1; i++) - { - aecm->channelAdapt32[i] = WEBRTC_SPL_LSHIFT_W32( - (WebRtc_Word32)(aecm->channelAdapt16[i]), 16); + WebRtcAecm_InitEchoPathCore(aecm, kChannelStored16kHz); } memset(aecm->echoFilt, 0, sizeof(WebRtc_Word32) * PART_LEN1); @@ -390,10 +405,6 @@ int WebRtcAecm_InitCore(AecmCore_t * const aecm, int samplingFreq) } #endif - aecm->mseAdaptOld = 1000; - aecm->mseStoredOld = 1000; - aecm->mseThreshold = WEBRTC_SPL_WORD32_MAX; - aecm->farEnergyMin = WEBRTC_SPL_WORD16_MAX; aecm->farEnergyMax = WEBRTC_SPL_WORD16_MIN; aecm->farEnergyMaxMin = 0; @@ -410,7 +421,6 @@ int WebRtcAecm_InitCore(AecmCore_t * const aecm, int samplingFreq) memset(aecm->delayCorrelation, 0, sizeof(WebRtc_Word16) * ((CORR_MAX << 1) + 1)); aecm->startupState = 0; - aecm->mseChannelCount = 0; aecm->supGain = SUPGAIN_DEFAULT; aecm->supGainOld = SUPGAIN_DEFAULT; aecm->delayOffsetFlag = 0; diff --git a/src/modules/audio_processing/aecm/main/source/aecm_core.h b/src/modules/audio_processing/aecm/main/source/aecm_core.h index 5defbe46c..0088e4887 100644 --- a/src/modules/audio_processing/aecm/main/source/aecm_core.h +++ b/src/modules/audio_processing/aecm/main/source/aecm_core.h @@ -24,7 +24,7 @@ #include "typedefs.h" #include "signal_processing_library.h" // TODO(bjornv): Will be removed in final version. -//#include +#include // Algorithm parameters @@ -267,6 +267,19 @@ int WebRtcAecm_FreeCore(AecmCore_t *aecm); int WebRtcAecm_Control(AecmCore_t *aecm, int delay, int nlpFlag, int delayOffsetFlag); +/////////////////////////////////////////////////////////////////////////////////////////////// +// WebRtcAecm_InitEchoPathCore(...) +// +// This function resets the echo channel adaptation with the specified channel. +// Input: +// - aecm : Pointer to the AECM instance +// - echo_path : Pointer to the data that should initialize the echo path +// +// Output: +// - aecm : Initialized instance +// +void WebRtcAecm_InitEchoPathCore(AecmCore_t* aecm, const WebRtc_Word16* echo_path); + /////////////////////////////////////////////////////////////////////////////////////////////// // WebRtcAecm_ProcessFrame(...) // diff --git a/src/modules/audio_processing/aecm/main/source/echo_control_mobile.c b/src/modules/audio_processing/aecm/main/source/echo_control_mobile.c index f9d84f0c4..fc4c3897f 100644 --- a/src/modules/audio_processing/aecm/main/source/echo_control_mobile.c +++ b/src/modules/audio_processing/aecm/main/source/echo_control_mobile.c @@ -159,7 +159,7 @@ WebRtc_Word32 WebRtcAecm_Free(void *aecmInst) return 0; } -WebRtc_Word32 WebRtcAecm_Init(void *aecmInst, WebRtc_Word32 sampFreq, WebRtc_Word32 scSampFreq) +WebRtc_Word32 WebRtcAecm_Init(void *aecmInst, WebRtc_Word32 sampFreq) { aecmob_t *aecm = aecmInst; AecmConfig aecConfig; @@ -176,13 +176,6 @@ WebRtc_Word32 WebRtcAecm_Init(void *aecmInst, WebRtc_Word32 sampFreq, WebRtc_Wor } aecm->sampFreq = sampFreq; - if (scSampFreq < 1 || scSampFreq > 96000) - { - aecm->lastError = AECM_BAD_PARAMETER_ERROR; - return -1; - } - aecm->scSampFreq = scSampFreq; - // Initialize AECM core if (WebRtcAecm_InitCore(aecm->aecmCore, aecm->sampFreq) == -1) { @@ -627,6 +620,68 @@ WebRtc_Word32 WebRtcAecm_get_config(void *aecmInst, AecmConfig *config) return 0; } +WebRtc_Word32 WebRtcAecm_InitEchoPath(void* aecmInst, + const void* echo_path, + int size_bytes) +{ + aecmob_t *aecm = aecmInst; + const WebRtc_Word16* echo_path_ptr = echo_path; + + if ((aecm == NULL) || (echo_path == NULL)) + { + aecm->lastError = AECM_NULL_POINTER_ERROR; + return -1; + } + if (size_bytes != WebRtcAecm_echo_path_size_bytes()) + { + // Input channel size does not match the size of AECM + aecm->lastError = AECM_BAD_PARAMETER_ERROR; + return -1; + } + if (aecm->initFlag != kInitCheck) + { + aecm->lastError = AECM_UNINITIALIZED_ERROR; + return -1; + } + + WebRtcAecm_InitEchoPathCore(aecm->aecmCore, echo_path_ptr); + + return 0; +} + +WebRtc_Word32 WebRtcAecm_GetEchoPath(void* aecmInst, + void* echo_path, + int size_bytes) +{ + aecmob_t *aecm = aecmInst; + WebRtc_Word16* echo_path_ptr = echo_path; + + if ((aecm == NULL) || (echo_path == NULL)) + { + aecm->lastError = AECM_NULL_POINTER_ERROR; + return -1; + } + if (size_bytes != WebRtcAecm_echo_path_size_bytes()) + { + // Input channel size does not match the size of AECM + aecm->lastError = AECM_BAD_PARAMETER_ERROR; + return -1; + } + if (aecm->initFlag != kInitCheck) + { + aecm->lastError = AECM_UNINITIALIZED_ERROR; + return -1; + } + + memcpy(echo_path_ptr, aecm->aecmCore->channelStored, size_bytes); + return 0; +} + +int WebRtcAecm_echo_path_size_bytes() +{ + return (PART_LEN1 * sizeof(WebRtc_Word16)); +} + WebRtc_Word32 WebRtcAecm_get_version(WebRtc_Word8 *versionStr, WebRtc_Word16 len) { const char version[] = "AECM 1.2.0"; diff --git a/src/modules/audio_processing/main/interface/audio_processing.h b/src/modules/audio_processing/main/interface/audio_processing.h index dc9c2325a..4d5df2f48 100644 --- a/src/modules/audio_processing/main/interface/audio_processing.h +++ b/src/modules/audio_processing/main/interface/audio_processing.h @@ -356,6 +356,20 @@ class EchoControlMobile { virtual int enable_comfort_noise(bool enable) = 0; virtual bool is_comfort_noise_enabled() const = 0; + // A typical use case is to initialize the component with an echo path from a + // previous call. The echo path is retrieved using |GetEchoPath()| typically + // at the end of a call. The data can then be stored for later use as + // initializer, using |SetEchoPath()|. + // Controlling the echo path this way requires the data |size_bytes| to match + // the internal echo path size. This size can be acquired using + // |echo_path_size_bytes()|. |SetEchoPath()| causes an entire reset, worth + // noting if it is to be called during an ongoing call. It is possible that + // version incompatibilities may result in a stored echo path of the + // incorrect size. In this case, the stored path should be discarded. + virtual int SetEchoPath(const void* echo_path, int size_bytes) = 0; + virtual int GetEchoPath(void* echo_path, int size_bytes) const = 0; + virtual const int echo_path_size_bytes() const = 0; + protected: virtual ~EchoControlMobile() {}; }; diff --git a/src/modules/audio_processing/main/source/echo_control_mobile_impl.cc b/src/modules/audio_processing/main/source/echo_control_mobile_impl.cc index 1cd2502e2..ce940ebe4 100644 --- a/src/modules/audio_processing/main/source/echo_control_mobile_impl.cc +++ b/src/modules/audio_processing/main/source/echo_control_mobile_impl.cc @@ -11,6 +11,7 @@ #include "echo_control_mobile_impl.h" #include +#include #include "critical_section_wrapper.h" #include "echo_control_mobile.h" @@ -44,14 +45,15 @@ int MapError(int err) { switch (err) { case AECM_UNSUPPORTED_FUNCTION_ERROR: return AudioProcessing::kUnsupportedFunctionError; + case AECM_NULL_POINTER_ERROR: + return AudioProcessing::kNullPointerError; case AECM_BAD_PARAMETER_ERROR: return AudioProcessing::kBadParameterError; case AECM_BAD_PARAMETER_WARNING: return AudioProcessing::kBadStreamParameterWarning; default: - // AECMOBFIX_UNSPECIFIED_ERROR - // AECMOBFIX_UNINITIALIZED_ERROR - // AECMOBFIX_NULL_POINTER_ERROR + // AECM_UNSPECIFIED_ERROR + // AECM_UNINITIALIZED_ERROR return AudioProcessing::kUnspecifiedError; } } @@ -61,9 +63,16 @@ EchoControlMobileImpl::EchoControlMobileImpl(const AudioProcessingImpl* apm) : ProcessingComponent(apm), apm_(apm), routing_mode_(kSpeakerphone), - comfort_noise_enabled_(true) {} + comfort_noise_enabled_(true), + echo_path_size_bytes_(WebRtcAecm_echo_path_size_bytes()), + external_echo_path_(NULL) {} -EchoControlMobileImpl::~EchoControlMobileImpl() {} +EchoControlMobileImpl::~EchoControlMobileImpl() { + if (external_echo_path_ != NULL) { + delete [] external_echo_path_; + external_echo_path_ = NULL; + } +} int EchoControlMobileImpl::ProcessRenderAudio(const AudioBuffer* audio) { if (!is_component_enabled()) { @@ -181,6 +190,52 @@ bool EchoControlMobileImpl::is_comfort_noise_enabled() const { return comfort_noise_enabled_; } +int EchoControlMobileImpl::SetEchoPath(const void* echo_path, + int size_bytes) { + CriticalSectionScoped crit_scoped(*apm_->crit()); + if (echo_path == NULL) { + return apm_->kNullPointerError; + } + if (size_bytes != echo_path_size_bytes_) { + // Size mismatch + return apm_->kBadParameterError; + } + + if (external_echo_path_ == NULL) { + external_echo_path_ = new unsigned char[size_bytes]; + } + memcpy(external_echo_path_, echo_path, size_bytes); + + return Initialize(); +} + +int EchoControlMobileImpl::GetEchoPath(void* echo_path, + int size_bytes) const { + CriticalSectionScoped crit_scoped(*apm_->crit()); + if (echo_path == NULL) { + return apm_->kNullPointerError; + } + if (size_bytes != echo_path_size_bytes_) { + // Size mismatch + return apm_->kBadParameterError; + } + if (!is_component_enabled()) { + return apm_->kNotEnabledError; + } + + // Get the echo path from the first channel + Handle* my_handle = static_cast(handle(0)); + if (WebRtcAecm_GetEchoPath(my_handle, echo_path, size_bytes) != 0) { + return GetHandleError(my_handle); + } + + return apm_->kNoError; +} + +const int EchoControlMobileImpl::echo_path_size_bytes() const { + return echo_path_size_bytes_; +} + int EchoControlMobileImpl::Initialize() { if (!is_component_enabled()) { return apm_->kNoError; @@ -197,7 +252,7 @@ int EchoControlMobileImpl::Initialize() { int EchoControlMobileImpl::get_version(char* version, int version_len_bytes) const { if (WebRtcAecm_get_version(version, version_len_bytes) != 0) { - return apm_->kBadParameterError; + return apm_->kBadParameterError; } return apm_->kNoError; @@ -219,10 +274,20 @@ int EchoControlMobileImpl::DestroyHandle(void* handle) const { } int EchoControlMobileImpl::InitializeHandle(void* handle) const { - return WebRtcAecm_Init(static_cast(handle), - apm_->sample_rate_hz(), - 48000); // Dummy value. This isn't actually - // required by AECM. + assert(handle != NULL); + Handle* my_handle = static_cast(handle); + if (WebRtcAecm_Init(my_handle, apm_->sample_rate_hz()) != 0) { + return GetHandleError(my_handle); + } + if (external_echo_path_ != NULL) { + if (WebRtcAecm_InitEchoPath(my_handle, + external_echo_path_, + echo_path_size_bytes_) != 0) { + return GetHandleError(my_handle); + } + } + + return apm_->kNoError; } int EchoControlMobileImpl::ConfigureHandle(void* handle) const { diff --git a/src/modules/audio_processing/main/source/echo_control_mobile_impl.h b/src/modules/audio_processing/main/source/echo_control_mobile_impl.h index 2fd624810..5e5bd4ef1 100644 --- a/src/modules/audio_processing/main/source/echo_control_mobile_impl.h +++ b/src/modules/audio_processing/main/source/echo_control_mobile_impl.h @@ -41,6 +41,9 @@ class EchoControlMobileImpl : public EchoControlMobile, virtual RoutingMode routing_mode() const; virtual int enable_comfort_noise(bool enable); virtual bool is_comfort_noise_enabled() const; + virtual int SetEchoPath(const void* echo_path, int size_bytes); + virtual int GetEchoPath(void* echo_path, int size_bytes) const; + virtual const int echo_path_size_bytes() const; // ProcessingComponent implementation. virtual void* CreateHandle() const; @@ -53,6 +56,8 @@ class EchoControlMobileImpl : public EchoControlMobile, const AudioProcessingImpl* apm_; RoutingMode routing_mode_; bool comfort_noise_enabled_; + const int echo_path_size_bytes_; + unsigned char* external_echo_path_; }; } // namespace webrtc diff --git a/src/modules/audio_processing/main/test/process_test/process_test.cc b/src/modules/audio_processing/main/test/process_test/process_test.cc index c62345fcf..2e659ec27 100644 --- a/src/modules/audio_processing/main/test/process_test/process_test.cc +++ b/src/modules/audio_processing/main/test/process_test/process_test.cc @@ -56,6 +56,8 @@ void usage() { printf(" --drift_compensation\n"); printf(" --no_drift_compensation\n"); printf("\n -aecm Echo control mobile\n"); + printf(" --aecm_echo_path_in_file FILE"); + printf(" --aecm_echo_path_out_file FILE"); printf("\n -agc Gain control\n"); printf(" --analog\n"); printf(" --adaptive_digital\n"); @@ -103,6 +105,8 @@ void void_main(int argc, char* argv[]) { const char* near_filename = NULL; const char* out_filename = NULL; const char* vad_out_filename = NULL; + const char* aecm_echo_path_in_filename = NULL; + const char* aecm_echo_path_out_filename = NULL; int32_t sample_rate_hz = 16000; int32_t device_sample_rate_hz = 16000; @@ -185,6 +189,16 @@ void void_main(int argc, char* argv[]) { } else if (strcmp(argv[i], "-aecm") == 0) { ASSERT_EQ(apm->kNoError, apm->echo_control_mobile()->Enable(true)); + } else if (strcmp(argv[i], "--aecm_echo_path_in_file") == 0) { + i++; + ASSERT_LT(i, argc) << "Specify filename after --aecm_echo_path_in_file"; + aecm_echo_path_in_filename = argv[i]; + + } else if (strcmp(argv[i], "--aecm_echo_path_out_file") == 0) { + i++; + ASSERT_LT(i, argc) << "Specify filename after --aecm_echo_path_out_file"; + aecm_echo_path_out_filename = argv[i]; + } else if (strcmp(argv[i], "-agc") == 0) { ASSERT_EQ(apm->kNoError, apm->gain_control()->Enable(true)); @@ -323,6 +337,8 @@ void void_main(int argc, char* argv[]) { FILE* delay_file = NULL; FILE* drift_file = NULL; FILE* vad_out_file = NULL; + FILE* aecm_echo_path_in_file = NULL; + FILE* aecm_echo_path_out_file = NULL; if (far_filename != NULL) { far_file = fopen(far_filename, "rb"); @@ -361,6 +377,30 @@ void void_main(int argc, char* argv[]) { << vad_out_file; } + if (aecm_echo_path_in_filename != NULL) { + aecm_echo_path_in_file = fopen(aecm_echo_path_in_filename, "rb"); + ASSERT_TRUE(NULL != aecm_echo_path_in_file) << "Unable to open file " + << aecm_echo_path_in_filename; + + const int path_size = apm->echo_control_mobile()->echo_path_size_bytes(); + unsigned char echo_path[path_size]; + ASSERT_EQ(path_size, fread(echo_path, + sizeof(unsigned char), + path_size, + aecm_echo_path_in_file)); + EXPECT_EQ(apm->kNoError, + apm->echo_control_mobile()->SetEchoPath(echo_path, path_size)); + fclose(aecm_echo_path_in_file); + aecm_echo_path_in_file = NULL; + } + + if (aecm_echo_path_out_filename != NULL) { + aecm_echo_path_out_file = fopen(aecm_echo_path_out_filename, "wb"); + ASSERT_TRUE(NULL != aecm_echo_path_out_file) << "Unable to open file " + << aecm_echo_path_out_filename; + + } + enum Events { kInitializeEvent, kRenderEvent, @@ -579,6 +619,18 @@ void void_main(int argc, char* argv[]) { } } + if (aecm_echo_path_out_file != NULL) { + const int path_size = apm->echo_control_mobile()->echo_path_size_bytes(); + unsigned char echo_path[path_size]; + apm->echo_control_mobile()->GetEchoPath(echo_path, path_size); + ASSERT_EQ(path_size, fwrite(echo_path, + sizeof(unsigned char), + path_size, + aecm_echo_path_out_file)); + fclose(aecm_echo_path_out_file); + aecm_echo_path_out_file = NULL; + } + if (verbose) { printf("\nProcessed frames: %d (primary), %d (reverse)\n", primary_count, reverse_count); diff --git a/src/modules/audio_processing/main/test/unit_test/unit_test.cc b/src/modules/audio_processing/main/test/unit_test/unit_test.cc index 3a6fce5a3..275ac3fdd 100644 --- a/src/modules/audio_processing/main/test/unit_test/unit_test.cc +++ b/src/modules/audio_processing/main/test/unit_test/unit_test.cc @@ -638,6 +638,31 @@ TEST_F(ApmTest, EchoControlMobile) { EXPECT_EQ(apm_->kNoError, apm_->echo_control_mobile()->enable_comfort_noise(true)); EXPECT_TRUE(apm_->echo_control_mobile()->is_comfort_noise_enabled()); + // Set and get echo path + const int echo_path_size = apm_->echo_control_mobile()->echo_path_size_bytes(); + unsigned char echo_path_in[echo_path_size]; + unsigned char echo_path_out[echo_path_size]; + EXPECT_EQ(apm_->kNullPointerError, + apm_->echo_control_mobile()->SetEchoPath(NULL, echo_path_size)); + EXPECT_EQ(apm_->kNullPointerError, + apm_->echo_control_mobile()->GetEchoPath(NULL, echo_path_size)); + EXPECT_EQ(apm_->kBadParameterError, + apm_->echo_control_mobile()->GetEchoPath(echo_path_out, 1)); + EXPECT_EQ(apm_->kNoError, + apm_->echo_control_mobile()->GetEchoPath(echo_path_out, + echo_path_size)); + for (int i = 0; i < echo_path_size; i++) { + echo_path_in[i] = echo_path_out[i] + 1; + } + EXPECT_EQ(apm_->kBadParameterError, + apm_->echo_control_mobile()->SetEchoPath(echo_path_in, 1)); + EXPECT_EQ(apm_->kNoError, + apm_->echo_control_mobile()->SetEchoPath(echo_path_in, echo_path_size)); + EXPECT_EQ(apm_->kNoError, + apm_->echo_control_mobile()->GetEchoPath(echo_path_out, echo_path_size)); + for (int i = 0; i < echo_path_size; i++) { + EXPECT_EQ(echo_path_in[i], echo_path_out[i]); + } // Turn AECM off EXPECT_EQ(apm_->kNoError, apm_->echo_control_mobile()->Enable(false)); EXPECT_FALSE(apm_->echo_control_mobile()->is_enabled());