diff --git a/api/audio/echo_canceller3_config.h b/api/audio/echo_canceller3_config.h index 33875265c3..c8de79702b 100644 --- a/api/audio/echo_canceller3_config.h +++ b/api/audio/echo_canceller3_config.h @@ -79,6 +79,7 @@ struct RTC_EXPORT EchoCanceller3Config { bool conservative_initial_phase = false; bool enable_shadow_filter_output_usage = true; bool use_linear_filter = true; + bool export_linear_aec_output = false; } filter; struct Erle { @@ -185,8 +186,6 @@ struct RTC_EXPORT EchoCanceller3Config { } high_bands_suppression; float floor_first_increase = 0.00001f; - bool enforce_transparent = false; - bool enforce_empty_higher_bands = false; } suppressor; }; } // namespace webrtc diff --git a/api/audio/echo_canceller3_config_json.cc b/api/audio/echo_canceller3_config_json.cc index c6ee7083b8..ab051bd7ea 100644 --- a/api/audio/echo_canceller3_config_json.cc +++ b/api/audio/echo_canceller3_config_json.cc @@ -190,6 +190,8 @@ void Aec3ConfigFromJsonString(absl::string_view json_string, ReadParam(section, "enable_shadow_filter_output_usage", &cfg.filter.enable_shadow_filter_output_usage); ReadParam(section, "use_linear_filter", &cfg.filter.use_linear_filter); + ReadParam(section, "export_linear_aec_output", + &cfg.filter.export_linear_aec_output); } if (rtc::GetValueFromJsonObject(aec3_root, "erle", §ion)) { @@ -314,10 +316,6 @@ void Aec3ConfigFromJsonString(absl::string_view json_string, ReadParam(section, "floor_first_increase", &cfg.suppressor.floor_first_increase); - ReadParam(section, "enforce_transparent", - &cfg.suppressor.enforce_transparent); - ReadParam(section, "enforce_empty_higher_bands", - &cfg.suppressor.enforce_empty_higher_bands); } } @@ -408,7 +406,12 @@ std::string Aec3ConfigToJsonString(const EchoCanceller3Config& config) { ost << "\"conservative_initial_phase\": " << (config.filter.conservative_initial_phase ? "true" : "false") << ","; ost << "\"enable_shadow_filter_output_usage\": " - << (config.filter.enable_shadow_filter_output_usage ? "true" : "false"); + << (config.filter.enable_shadow_filter_output_usage ? "true" : "false") + << ","; + ost << "\"use_linear_filter\": " + << (config.filter.use_linear_filter ? "true" : "false") << ","; + ost << "\"export_linear_aec_output\": " + << (config.filter.export_linear_aec_output ? "true" : "false"); ost << "},"; @@ -545,12 +548,7 @@ std::string Aec3ConfigToJsonString(const EchoCanceller3Config& config) { ost << "\"max_gain_during_echo\": " << config.suppressor.high_bands_suppression.max_gain_during_echo; ost << "},"; - ost << "\"floor_first_increase\": " << config.suppressor.floor_first_increase - << ","; - ost << "\"enforce_transparent\": " - << (config.suppressor.enforce_transparent ? "true" : "false") << ","; - ost << "\"enforce_empty_higher_bands\": " - << (config.suppressor.enforce_empty_higher_bands ? "true" : "false"); + ost << "\"floor_first_increase\": " << config.suppressor.floor_first_increase; ost << "}"; ost << "}"; ost << "}"; diff --git a/api/audio/echo_control.h b/api/audio/echo_control.h index de80f500d1..b63f123df7 100644 --- a/api/audio/echo_control.h +++ b/api/audio/echo_control.h @@ -31,6 +31,12 @@ class EchoControl { // Processes the capture signal in order to remove the echo. virtual void ProcessCapture(AudioBuffer* capture, bool echo_path_change) = 0; + // As above, but also returns the linear filter output. + // TODO(peah): Make pure virtual. + virtual void ProcessCapture(AudioBuffer* capture, + AudioBuffer* linear_output, + bool level_change) {} + struct Metrics { double echo_return_loss; double echo_return_loss_enhancement; diff --git a/modules/audio_processing/aec3/aec3_common.h b/modules/audio_processing/aec3/aec3_common.h index bf554e315b..d778e50f24 100644 --- a/modules/audio_processing/aec3/aec3_common.h +++ b/modules/audio_processing/aec3/aec3_common.h @@ -42,7 +42,8 @@ constexpr int kMaxAdaptiveFilterLength = 50; constexpr int kRenderTransferQueueSizeFrames = 100; constexpr size_t kMaxNumBands = 3; -constexpr size_t kSubFrameLength = 80; +constexpr size_t kFrameSize = 160; +constexpr size_t kSubFrameLength = kFrameSize / 2; constexpr size_t kBlockSize = kFftLengthBy2; constexpr size_t kBlockSizeLog2 = kFftLengthBy2Log2; diff --git a/modules/audio_processing/aec3/block_processor.cc b/modules/audio_processing/aec3/block_processor.cc index 89425707a4..bda2589395 100644 --- a/modules/audio_processing/aec3/block_processor.cc +++ b/modules/audio_processing/aec3/block_processor.cc @@ -52,6 +52,7 @@ class BlockProcessorImpl final : public BlockProcessor { void ProcessCapture( bool echo_path_gain_change, bool capture_signal_saturation, + std::vector>>* linear_output, std::vector>>* capture_block) override; void BufferRender( @@ -105,6 +106,7 @@ BlockProcessorImpl::~BlockProcessorImpl() = default; void BlockProcessorImpl::ProcessCapture( bool echo_path_gain_change, bool capture_signal_saturation, + std::vector>>* linear_output, std::vector>>* capture_block) { RTC_DCHECK(capture_block); RTC_DCHECK_EQ(NumBandsForRate(sample_rate_hz_), capture_block->size()); @@ -191,7 +193,7 @@ void BlockProcessorImpl::ProcessCapture( if (has_delay_estimator || render_buffer_->HasReceivedBufferDelay()) { echo_remover_->ProcessCapture( echo_path_variability, capture_signal_saturation, estimated_delay_, - render_buffer_->GetRenderBuffer(), capture_block); + render_buffer_->GetRenderBuffer(), linear_output, capture_block); } // Update the metrics. diff --git a/modules/audio_processing/aec3/block_processor.h b/modules/audio_processing/aec3/block_processor.h index 755444aad4..9bb0cf19f3 100644 --- a/modules/audio_processing/aec3/block_processor.h +++ b/modules/audio_processing/aec3/block_processor.h @@ -59,6 +59,7 @@ class BlockProcessor { virtual void ProcessCapture( bool echo_path_gain_change, bool capture_signal_saturation, + std::vector>>* linear_output, std::vector>>* capture_block) = 0; // Buffers a block of render data supplied by a FrameBlocker object. diff --git a/modules/audio_processing/aec3/block_processor_unittest.cc b/modules/audio_processing/aec3/block_processor_unittest.cc index 9c315e19f0..2b928e877b 100644 --- a/modules/audio_processing/aec3/block_processor_unittest.cc +++ b/modules/audio_processing/aec3/block_processor_unittest.cc @@ -48,7 +48,7 @@ void RunBasicSetupAndApiCallTest(int sample_rate_hz, int num_iterations) { std::vector(kBlockSize, 1000.f))); for (int k = 0; k < num_iterations; ++k) { block_processor->BufferRender(block); - block_processor->ProcessCapture(false, false, &block); + block_processor->ProcessCapture(false, false, nullptr, &block); block_processor->UpdateEchoLeakageStatus(false); } } @@ -81,7 +81,8 @@ void RunCaptureBlockSizeVerificationTest(int sample_rate_hz) { std::vector>(kNumRenderChannels, std::vector(kBlockSize - 1, 0.f))); - EXPECT_DEATH(block_processor->ProcessCapture(false, false, &block), ""); + EXPECT_DEATH(block_processor->ProcessCapture(false, false, nullptr, &block), + ""); } void RunRenderNumBandsVerificationTest(int sample_rate_hz) { @@ -117,7 +118,8 @@ void RunCaptureNumBandsVerificationTest(int sample_rate_hz) { std::vector>(kNumRenderChannels, std::vector(kBlockSize, 0.f))); - EXPECT_DEATH(block_processor->ProcessCapture(false, false, &block), ""); + EXPECT_DEATH(block_processor->ProcessCapture(false, false, nullptr, &block), + ""); } #endif @@ -172,7 +174,7 @@ TEST(BlockProcessor, DISABLED_DelayControllerIntegration) { RandomizeSampleVector(&random_generator, render_block[0][0]); signal_delay_buffer.Delay(render_block[0][0], capture_block[0][0]); block_processor->BufferRender(render_block); - block_processor->ProcessCapture(false, false, &capture_block); + block_processor->ProcessCapture(false, false, nullptr, &capture_block); } } } @@ -207,7 +209,7 @@ TEST(BlockProcessor, DISABLED_SubmoduleIntegration) { .WillRepeatedly(Return(0)); EXPECT_CALL(*render_delay_controller_mock, GetDelay(_, _, _)) .Times(kNumBlocks); - EXPECT_CALL(*echo_remover_mock, ProcessCapture(_, _, _, _, _)) + EXPECT_CALL(*echo_remover_mock, ProcessCapture(_, _, _, _, _, _)) .Times(kNumBlocks); EXPECT_CALL(*echo_remover_mock, UpdateEchoLeakageStatus(_)) .Times(kNumBlocks); @@ -230,7 +232,7 @@ TEST(BlockProcessor, DISABLED_SubmoduleIntegration) { RandomizeSampleVector(&random_generator, render_block[0][0]); signal_delay_buffer.Delay(render_block[0][0], capture_block[0][0]); block_processor->BufferRender(render_block); - block_processor->ProcessCapture(false, false, &capture_block); + block_processor->ProcessCapture(false, false, nullptr, &capture_block); block_processor->UpdateEchoLeakageStatus(false); } } @@ -284,7 +286,7 @@ TEST(BlockProcessor, VerifyCaptureNumBandsCheck) { TEST(BlockProcessor, NullProcessCaptureParameter) { EXPECT_DEATH(std::unique_ptr( BlockProcessor::Create(EchoCanceller3Config(), 16000, 1, 1)) - ->ProcessCapture(false, false, nullptr), + ->ProcessCapture(false, false, nullptr, nullptr), ""); } diff --git a/modules/audio_processing/aec3/echo_canceller3.cc b/modules/audio_processing/aec3/echo_canceller3.cc index 2b50e613f8..a68ae01a98 100644 --- a/modules/audio_processing/aec3/echo_canceller3.cc +++ b/modules/audio_processing/aec3/echo_canceller3.cc @@ -16,6 +16,7 @@ #include "modules/audio_processing/high_pass_filter.h" #include "modules/audio_processing/logging/apm_data_dumper.h" #include "rtc_base/atomic_ops.h" +#include "rtc_base/logging.h" #include "system_wrappers/include/field_trial.h" namespace webrtc { @@ -87,28 +88,52 @@ void FillSubFrameView( } void ProcessCaptureFrameContent( + AudioBuffer* linear_output, AudioBuffer* capture, bool level_change, bool saturated_microphone_signal, size_t sub_frame_index, FrameBlocker* capture_blocker, + BlockFramer* linear_output_framer, BlockFramer* output_framer, BlockProcessor* block_processor, - std::vector>>* block, - std::vector>>* sub_frame_view) { - FillSubFrameView(capture, sub_frame_index, sub_frame_view); - capture_blocker->InsertSubFrameAndExtractBlock(*sub_frame_view, block); + std::vector>>* linear_output_block, + std::vector>>* + linear_output_sub_frame_view, + std::vector>>* capture_block, + std::vector>>* capture_sub_frame_view) { + FillSubFrameView(capture, sub_frame_index, capture_sub_frame_view); + + if (linear_output) { + RTC_DCHECK(linear_output_framer); + RTC_DCHECK(linear_output_block); + RTC_DCHECK(linear_output_sub_frame_view); + FillSubFrameView(linear_output, sub_frame_index, + linear_output_sub_frame_view); + } + + capture_blocker->InsertSubFrameAndExtractBlock(*capture_sub_frame_view, + capture_block); block_processor->ProcessCapture(level_change, saturated_microphone_signal, - block); - output_framer->InsertBlockAndExtractSubFrame(*block, sub_frame_view); + linear_output_block, capture_block); + output_framer->InsertBlockAndExtractSubFrame(*capture_block, + capture_sub_frame_view); + + if (linear_output) { + RTC_DCHECK(linear_output_framer); + linear_output_framer->InsertBlockAndExtractSubFrame( + *linear_output_block, linear_output_sub_frame_view); + } } void ProcessRemainingCaptureFrameContent( bool level_change, bool saturated_microphone_signal, FrameBlocker* capture_blocker, + BlockFramer* linear_output_framer, BlockFramer* output_framer, BlockProcessor* block_processor, + std::vector>>* linear_output_block, std::vector>>* block) { if (!capture_blocker->IsBlockAvailable()) { return; @@ -116,8 +141,13 @@ void ProcessRemainingCaptureFrameContent( capture_blocker->ExtractBlock(block); block_processor->ProcessCapture(level_change, saturated_microphone_signal, - block); + linear_output_block, block); output_framer->InsertBlock(*block); + + if (linear_output_framer) { + RTC_DCHECK(linear_output_block); + linear_output_framer->InsertBlock(*linear_output_block); + } } void BufferRenderFrameContent( @@ -295,12 +325,24 @@ EchoCanceller3::EchoCanceller3(const EchoCanceller3Config& config, RTC_DCHECK_EQ(num_bands_, std::max(sample_rate_hz_, 16000) / 16000); RTC_DCHECK_GE(kMaxNumBands, num_bands_); + + if (config_.filter.export_linear_aec_output) { + linear_output_framer_.reset(new BlockFramer(1, num_capture_channels_)); + linear_output_block_ = + std::make_unique>>>( + 1, std::vector>( + num_capture_channels_, std::vector(kBlockSize, 0.f))); + linear_output_sub_frame_view_ = + std::vector>>( + 1, std::vector>(num_capture_channels_)); + } } EchoCanceller3::~EchoCanceller3() = default; void EchoCanceller3::AnalyzeRender(const AudioBuffer& render) { RTC_DCHECK_RUNS_SERIALIZED(&render_race_checker_); + RTC_DCHECK_EQ(render.num_channels(), num_render_channels_); data_dumper_->DumpRaw("aec3_call_order", static_cast(EchoCanceller3ApiCall::kRender)); @@ -312,7 +354,6 @@ void EchoCanceller3::AnalyzeCapture(const AudioBuffer& capture) { RTC_DCHECK_RUNS_SERIALIZED(&capture_race_checker_); data_dumper_->DumpWav("aec3_capture_analyze_input", capture.num_frames(), capture.channels_const()[0], sample_rate_hz_, 1); - saturated_microphone_signal_ = false; for (size_t channel = 0; channel < capture.num_channels(); ++channel) { saturated_microphone_signal_ |= @@ -325,6 +366,12 @@ void EchoCanceller3::AnalyzeCapture(const AudioBuffer& capture) { } void EchoCanceller3::ProcessCapture(AudioBuffer* capture, bool level_change) { + ProcessCapture(capture, nullptr, level_change); +} + +void EchoCanceller3::ProcessCapture(AudioBuffer* capture, + AudioBuffer* linear_output, + bool level_change) { RTC_DCHECK_RUNS_SERIALIZED(&capture_race_checker_); RTC_DCHECK(capture); RTC_DCHECK_EQ(num_bands_, capture->num_bands()); @@ -333,6 +380,12 @@ void EchoCanceller3::ProcessCapture(AudioBuffer* capture, bool level_change) { data_dumper_->DumpRaw("aec3_call_order", static_cast(EchoCanceller3ApiCall::kCapture)); + if (linear_output && !linear_output_framer_) { + RTC_LOG(LS_ERROR) << "Trying to retrieve the linear AEC output without " + "properly configuring AEC3."; + RTC_NOTREACHED(); + } + // Report capture call in the metrics and periodically update API call // metrics. api_call_metrics_.ReportCaptureCall(); @@ -349,19 +402,24 @@ void EchoCanceller3::ProcessCapture(AudioBuffer* capture, bool level_change) { EmptyRenderQueue(); - ProcessCaptureFrameContent(capture, level_change, + ProcessCaptureFrameContent(linear_output, capture, level_change, saturated_microphone_signal_, 0, &capture_blocker_, - &output_framer_, block_processor_.get(), - &capture_block_, &capture_sub_frame_view_); + linear_output_framer_.get(), &output_framer_, + block_processor_.get(), linear_output_block_.get(), + &linear_output_sub_frame_view_, &capture_block_, + &capture_sub_frame_view_); - ProcessCaptureFrameContent(capture, level_change, + ProcessCaptureFrameContent(linear_output, capture, level_change, saturated_microphone_signal_, 1, &capture_blocker_, - &output_framer_, block_processor_.get(), - &capture_block_, &capture_sub_frame_view_); + linear_output_framer_.get(), &output_framer_, + block_processor_.get(), linear_output_block_.get(), + &linear_output_sub_frame_view_, &capture_block_, + &capture_sub_frame_view_); ProcessRemainingCaptureFrameContent( level_change, saturated_microphone_signal_, &capture_blocker_, - &output_framer_, block_processor_.get(), &capture_block_); + linear_output_framer_.get(), &output_framer_, block_processor_.get(), + linear_output_block_.get(), &capture_block_); data_dumper_->DumpWav("aec3_capture_output", AudioBuffer::kSplitBandSize, &capture->split_bands(0)[0][0], 16000, 1); diff --git a/modules/audio_processing/aec3/echo_canceller3.h b/modules/audio_processing/aec3/echo_canceller3.h index ce36cc1bfc..a828d5712b 100644 --- a/modules/audio_processing/aec3/echo_canceller3.h +++ b/modules/audio_processing/aec3/echo_canceller3.h @@ -70,8 +70,6 @@ class Aec3RenderQueueItemVerifier { // Main class for the echo canceller3. // It does 4 things: // -Receives 10 ms frames of band-split audio. -// -Optionally applies an anti-hum (high-pass) filter on the -// received signals. // -Provides the lower level echo canceller functionality with // blocks of 64 samples of audio data. // -Partially handles the jitter in the render and capture API @@ -106,6 +104,10 @@ class EchoCanceller3 : public EchoControl { // Processes the split-band domain capture signal in order to remove any echo // present in the signal. void ProcessCapture(AudioBuffer* capture, bool level_change) override; + // As above, but also returns the linear filter output. + void ProcessCapture(AudioBuffer* capture, + AudioBuffer* linear_output, + bool level_change) override; // Collect current metrics from the echo canceller. Metrics GetMetrics() const override; // Provides an optional external estimate of the audio buffer delay. @@ -149,6 +151,8 @@ class EchoCanceller3 : public EchoControl { const int num_bands_; const size_t num_render_channels_; const size_t num_capture_channels_; + std::unique_ptr linear_output_framer_ + RTC_GUARDED_BY(capture_race_checker_); BlockFramer output_framer_ RTC_GUARDED_BY(capture_race_checker_); FrameBlocker capture_blocker_ RTC_GUARDED_BY(capture_race_checker_); FrameBlocker render_blocker_ RTC_GUARDED_BY(capture_race_checker_); @@ -163,10 +167,14 @@ class EchoCanceller3 : public EchoControl { false; std::vector>> render_block_ RTC_GUARDED_BY(capture_race_checker_); + std::unique_ptr>>> + linear_output_block_ RTC_GUARDED_BY(capture_race_checker_); std::vector>> capture_block_ RTC_GUARDED_BY(capture_race_checker_); std::vector>> render_sub_frame_view_ RTC_GUARDED_BY(capture_race_checker_); + std::vector>> linear_output_sub_frame_view_ + RTC_GUARDED_BY(capture_race_checker_); std::vector>> capture_sub_frame_view_ RTC_GUARDED_BY(capture_race_checker_); BlockDelayBuffer block_delay_buffer_ RTC_GUARDED_BY(capture_race_checker_); diff --git a/modules/audio_processing/aec3/echo_canceller3_unittest.cc b/modules/audio_processing/aec3/echo_canceller3_unittest.cc index e7b9ddb957..8d9199c830 100644 --- a/modules/audio_processing/aec3/echo_canceller3_unittest.cc +++ b/modules/audio_processing/aec3/echo_canceller3_unittest.cc @@ -112,6 +112,7 @@ class CaptureTransportVerificationProcessor : public BlockProcessor { void ProcessCapture( bool level_change, bool saturated_microphone_signal, + std::vector>>* linear_output, std::vector>>* capture_block) override {} void BufferRender( @@ -137,6 +138,7 @@ class RenderTransportVerificationProcessor : public BlockProcessor { void ProcessCapture( bool level_change, bool saturated_microphone_signal, + std::vector>>* linear_output, std::vector>>* capture_block) override { std::vector>> render_block = received_render_blocks_.front(); @@ -267,17 +269,17 @@ class EchoCanceller3Tester { switch (echo_path_change_test_variant) { case EchoPathChangeTestVariant::kNone: - EXPECT_CALL(*block_processor_mock, ProcessCapture(false, _, _)) + EXPECT_CALL(*block_processor_mock, ProcessCapture(false, _, _, _)) .Times(kExpectedNumBlocksToProcess); break; case EchoPathChangeTestVariant::kOneSticky: - EXPECT_CALL(*block_processor_mock, ProcessCapture(true, _, _)) + EXPECT_CALL(*block_processor_mock, ProcessCapture(true, _, _, _)) .Times(kExpectedNumBlocksToProcess); break; case EchoPathChangeTestVariant::kOneNonSticky: - EXPECT_CALL(*block_processor_mock, ProcessCapture(true, _, _)) + EXPECT_CALL(*block_processor_mock, ProcessCapture(true, _, _, _)) .Times(kNumFullBlocksPerFrame); - EXPECT_CALL(*block_processor_mock, ProcessCapture(false, _, _)) + EXPECT_CALL(*block_processor_mock, ProcessCapture(false, _, _, _)) .Times(kExpectedNumBlocksToProcess - kNumFullBlocksPerFrame); break; } @@ -338,7 +340,7 @@ class EchoCanceller3Tester { new StrictMock()); EXPECT_CALL(*block_processor_mock, BufferRender(_)) .Times(kExpectedNumBlocksToProcess); - EXPECT_CALL(*block_processor_mock, ProcessCapture(_, _, _)) + EXPECT_CALL(*block_processor_mock, ProcessCapture(_, _, _, _)) .Times(kExpectedNumBlocksToProcess); switch (leakage_report_variant) { @@ -429,21 +431,21 @@ class EchoCanceller3Tester { switch (saturation_variant) { case SaturationTestVariant::kNone: - EXPECT_CALL(*block_processor_mock, ProcessCapture(_, false, _)) + EXPECT_CALL(*block_processor_mock, ProcessCapture(_, false, _, _)) .Times(kExpectedNumBlocksToProcess); break; case SaturationTestVariant::kOneNegative: { ::testing::InSequence s; - EXPECT_CALL(*block_processor_mock, ProcessCapture(_, true, _)) + EXPECT_CALL(*block_processor_mock, ProcessCapture(_, true, _, _)) .Times(kNumFullBlocksPerFrame); - EXPECT_CALL(*block_processor_mock, ProcessCapture(_, false, _)) + EXPECT_CALL(*block_processor_mock, ProcessCapture(_, false, _, _)) .Times(kExpectedNumBlocksToProcess - kNumFullBlocksPerFrame); } break; case SaturationTestVariant::kOnePositive: { ::testing::InSequence s; - EXPECT_CALL(*block_processor_mock, ProcessCapture(_, true, _)) + EXPECT_CALL(*block_processor_mock, ProcessCapture(_, true, _, _)) .Times(kNumFullBlocksPerFrame); - EXPECT_CALL(*block_processor_mock, ProcessCapture(_, false, _)) + EXPECT_CALL(*block_processor_mock, ProcessCapture(_, false, _, _)) .Times(kExpectedNumBlocksToProcess - kNumFullBlocksPerFrame); } break; } diff --git a/modules/audio_processing/aec3/echo_remover.cc b/modules/audio_processing/aec3/echo_remover.cc index bf68f36e63..89ba736a9f 100644 --- a/modules/audio_processing/aec3/echo_remover.cc +++ b/modules/audio_processing/aec3/echo_remover.cc @@ -123,6 +123,7 @@ class EchoRemoverImpl final : public EchoRemover { bool capture_signal_saturation, const absl::optional& external_delay, RenderBuffer* render_buffer, + std::vector>>* linear_output, std::vector>>* capture) override; // Updates the status on whether echo leakage is detected in the output of the @@ -235,6 +236,7 @@ void EchoRemoverImpl::ProcessCapture( bool capture_signal_saturation, const absl::optional& external_delay, RenderBuffer* render_buffer, + std::vector>>* linear_output, std::vector>>* capture) { ++block_counter_; const std::vector>>& x = @@ -367,6 +369,16 @@ void EchoRemoverImpl::ProcessCapture( E[ch].Spectrum(optimization_, E2[ch]); } + // Optionally return the linear filter output. + if (linear_output) { + RTC_DCHECK_GE(1, linear_output->size()); + RTC_DCHECK_EQ(num_capture_channels_, linear_output[0].size()); + for (size_t ch = 0; ch < num_capture_channels_; ++ch) { + RTC_DCHECK_EQ(kBlockSize, (*linear_output)[0][ch].size()); + std::copy(e[ch].begin(), e[ch].end(), (*linear_output)[0][ch].begin()); + } + } + // Update the AEC state information. aec_state_.Update(external_delay, subtractor_.FilterFrequencyResponses(), subtractor_.FilterImpulseResponses(), *render_buffer, E2, diff --git a/modules/audio_processing/aec3/echo_remover.h b/modules/audio_processing/aec3/echo_remover.h index 6098a68f14..ef4164688b 100644 --- a/modules/audio_processing/aec3/echo_remover.h +++ b/modules/audio_processing/aec3/echo_remover.h @@ -42,6 +42,7 @@ class EchoRemover { bool capture_signal_saturation, const absl::optional& external_delay, RenderBuffer* render_buffer, + std::vector>>* linear_output, std::vector>>* capture) = 0; // Updates the status on whether echo leakage is detected in the output of the diff --git a/modules/audio_processing/aec3/echo_remover_unittest.cc b/modules/audio_processing/aec3/echo_remover_unittest.cc index 15d091357a..d79993ac69 100644 --- a/modules/audio_processing/aec3/echo_remover_unittest.cc +++ b/modules/audio_processing/aec3/echo_remover_unittest.cc @@ -73,9 +73,9 @@ TEST(EchoRemover, BasicApiCalls) { render_buffer->Insert(render); render_buffer->PrepareCaptureProcessing(); - remover->ProcessCapture(echo_path_variability, - k % 2 == 0 ? true : false, delay_estimate, - render_buffer->GetRenderBuffer(), &capture); + remover->ProcessCapture( + echo_path_variability, k % 2 == 0 ? true : false, delay_estimate, + render_buffer->GetRenderBuffer(), nullptr, &capture); } } } @@ -107,10 +107,10 @@ TEST(EchoRemover, WrongCaptureBlockSize) { 1, std::vector(kBlockSize - 1, 0.f))); EchoPathVariability echo_path_variability( false, EchoPathVariability::DelayAdjustment::kNone, false); - EXPECT_DEATH( - remover->ProcessCapture(echo_path_variability, false, delay_estimate, - render_buffer->GetRenderBuffer(), &capture), - ""); + EXPECT_DEATH(remover->ProcessCapture( + echo_path_variability, false, delay_estimate, + render_buffer->GetRenderBuffer(), nullptr, &capture), + ""); } } @@ -131,10 +131,10 @@ TEST(EchoRemover, DISABLED_WrongCaptureNumBands) { std::vector(kBlockSize, 0.f))); EchoPathVariability echo_path_variability( false, EchoPathVariability::DelayAdjustment::kNone, false); - EXPECT_DEATH( - remover->ProcessCapture(echo_path_variability, false, delay_estimate, - render_buffer->GetRenderBuffer(), &capture), - ""); + EXPECT_DEATH(remover->ProcessCapture( + echo_path_variability, false, delay_estimate, + render_buffer->GetRenderBuffer(), nullptr, &capture), + ""); } } @@ -147,10 +147,10 @@ TEST(EchoRemover, NullCapture) { RenderDelayBuffer::Create(EchoCanceller3Config(), 16000, 1)); EchoPathVariability echo_path_variability( false, EchoPathVariability::DelayAdjustment::kNone, false); - EXPECT_DEATH( - remover->ProcessCapture(echo_path_variability, false, delay_estimate, - render_buffer->GetRenderBuffer(), nullptr), - ""); + EXPECT_DEATH(remover->ProcessCapture( + echo_path_variability, false, delay_estimate, + render_buffer->GetRenderBuffer(), nullptr, nullptr), + ""); } #endif @@ -222,7 +222,8 @@ TEST(EchoRemover, BasicEchoRemoval) { render_buffer->PrepareCaptureProcessing(); remover->ProcessCapture(echo_path_variability, false, delay_estimate, - render_buffer->GetRenderBuffer(), &y); + render_buffer->GetRenderBuffer(), nullptr, + &y); if (k > kNumBlocksToProcess / 2) { output_energy = std::inner_product(y[0][0].begin(), y[0][0].end(), diff --git a/modules/audio_processing/aec3/mock/mock_block_processor.h b/modules/audio_processing/aec3/mock/mock_block_processor.h index 634d26e691..e9a95c837d 100644 --- a/modules/audio_processing/aec3/mock/mock_block_processor.h +++ b/modules/audio_processing/aec3/mock/mock_block_processor.h @@ -24,10 +24,11 @@ class MockBlockProcessor : public BlockProcessor { MockBlockProcessor(); virtual ~MockBlockProcessor(); - MOCK_METHOD3( + MOCK_METHOD4( ProcessCapture, void(bool level_change, bool saturated_microphone_signal, + std::vector>>* linear_output, std::vector>>* capture_block)); MOCK_METHOD1(BufferRender, void(const std::vector>>& block)); diff --git a/modules/audio_processing/aec3/mock/mock_echo_remover.h b/modules/audio_processing/aec3/mock/mock_echo_remover.h index f8dd348975..6c580f3a91 100644 --- a/modules/audio_processing/aec3/mock/mock_echo_remover.h +++ b/modules/audio_processing/aec3/mock/mock_echo_remover.h @@ -27,11 +27,12 @@ class MockEchoRemover : public EchoRemover { MockEchoRemover(); virtual ~MockEchoRemover(); - MOCK_METHOD5(ProcessCapture, + MOCK_METHOD6(ProcessCapture, void(EchoPathVariability echo_path_variability, bool capture_signal_saturation, const absl::optional& delay_estimate, RenderBuffer* render_buffer, + std::vector>>* linear_output, std::vector>>* capture)); MOCK_CONST_METHOD0(Delay, absl::optional()); MOCK_METHOD1(UpdateEchoLeakageStatus, void(bool leakage_detected)); diff --git a/modules/audio_processing/aec3/suppression_gain.cc b/modules/audio_processing/aec3/suppression_gain.cc index d1ef326dfa..bd7a3d68fb 100644 --- a/modules/audio_processing/aec3/suppression_gain.cc +++ b/modules/audio_processing/aec3/suppression_gain.cc @@ -343,13 +343,6 @@ void SuppressionGain::GetGain( std::array* low_band_gain) { RTC_DCHECK(high_bands_gain); RTC_DCHECK(low_band_gain); - const auto& cfg = config_.suppressor; - - if (cfg.enforce_transparent) { - low_band_gain->fill(1.f); - *high_bands_gain = cfg.enforce_empty_higher_bands ? 0.f : 1.f; - return; - } // Update the nearend state selection. dominant_nearend_detector_.Update(nearend_spectrum, residual_echo_spectrum, @@ -360,11 +353,6 @@ void SuppressionGain::GetGain( LowerBandGain(low_noise_render, aec_state, nearend_spectrum, residual_echo_spectrum, comfort_noise_spectrum, low_band_gain); - if (cfg.enforce_empty_higher_bands) { - *high_bands_gain = 0.f; - return; - } - // Compute the gain for the upper bands. const absl::optional narrow_peak_band = render_signal_analyzer.NarrowPeakBand(); diff --git a/modules/audio_processing/audio_processing_impl.cc b/modules/audio_processing/audio_processing_impl.cc index 59d0c32ba3..fad02a038f 100644 --- a/modules/audio_processing/audio_processing_impl.cc +++ b/modules/audio_processing/audio_processing_impl.cc @@ -1342,8 +1342,9 @@ int AudioProcessingImpl::ProcessCaptureStreamLocked() { submodules_.echo_controller->SetAudioBufferDelay(stream_delay_ms()); } + AudioBuffer* linear_aec_buffer = capture_.linear_aec_output.get(); submodules_.echo_controller->ProcessCapture( - capture_buffer, capture_.echo_path_gain_change); + capture_buffer, linear_aec_buffer, capture_.echo_path_gain_change); } else if (submodules_.echo_cancellation) { // Ensure that the stream delay was set before the call to the // AEC ProcessCaptureAudio function. @@ -1625,6 +1626,31 @@ int AudioProcessingImpl::set_stream_delay_ms(int delay) { return retval; } +bool AudioProcessingImpl::GetLinearAecOutput( + rtc::ArrayView> linear_output) const { + rtc::CritScope cs(&crit_capture_); + AudioBuffer* linear_aec_buffer = capture_.linear_aec_output.get(); + + RTC_DCHECK(linear_aec_buffer); + if (linear_aec_buffer) { + RTC_DCHECK_EQ(1, linear_aec_buffer->num_bands()); + RTC_DCHECK_EQ(linear_output.size(), linear_aec_buffer->num_channels()); + + for (size_t ch = 0; ch < linear_aec_buffer->num_channels(); ++ch) { + RTC_DCHECK_EQ(linear_output[ch].size(), linear_aec_buffer->num_frames()); + rtc::ArrayView channel_view = + rtc::ArrayView(linear_aec_buffer->channels_const()[ch], + linear_aec_buffer->num_frames()); + std::copy(channel_view.begin(), channel_view.end(), + linear_output[ch].begin()); + } + return true; + } + RTC_LOG(LS_ERROR) << "No linear AEC output available"; + RTC_NOTREACHED(); + return false; +} + int AudioProcessingImpl::stream_delay_ms() const { // Used as callback from submodules, hence locking is not allowed. return capture_nonlocked_.stream_delay_ms; @@ -1790,6 +1816,16 @@ void AudioProcessingImpl::InitializeEchoController() { num_proc_channels()); } + // Setup the storage for returning the linear AEC output. + if (config_.echo_canceller.export_linear_aec_output) { + constexpr int kLinearOutputRateHz = 16000; + capture_.linear_aec_output = std::make_unique( + kLinearOutputRateHz, num_proc_channels(), kLinearOutputRateHz, + num_proc_channels(), kLinearOutputRateHz, num_proc_channels()); + } else { + capture_.linear_aec_output.reset(); + } + capture_nonlocked_.echo_controller_enabled = true; submodules_.echo_cancellation.reset(); @@ -1801,6 +1837,7 @@ void AudioProcessingImpl::InitializeEchoController() { submodules_.echo_controller.reset(); capture_nonlocked_.echo_controller_enabled = false; + capture_.linear_aec_output.reset(); if (!config_.echo_canceller.enabled) { submodules_.echo_cancellation.reset(); diff --git a/modules/audio_processing/audio_processing_impl.h b/modules/audio_processing/audio_processing_impl.h index e13034fb37..f7320ac469 100644 --- a/modules/audio_processing/audio_processing_impl.h +++ b/modules/audio_processing/audio_processing_impl.h @@ -87,6 +87,8 @@ class AudioProcessingImpl : public AudioProcessing { const StreamConfig& input_config, const StreamConfig& output_config, float* const* dest) override; + bool GetLinearAecOutput( + rtc::ArrayView> linear_output) const override; void set_output_will_be_muted(bool muted) override; int set_stream_delay_ms(int delay) override; void set_delay_offset_ms(int offset) override; @@ -412,6 +414,7 @@ class AudioProcessingImpl : public AudioProcessing { bool transient_suppressor_enabled; std::unique_ptr capture_audio; std::unique_ptr capture_fullband_audio; + std::unique_ptr linear_aec_output; // Only the rate and samples fields of capture_processing_format_ are used // because the capture processing number of channels is mutable and is // tracked by the capture_audio_. diff --git a/modules/audio_processing/audio_processing_impl_unittest.cc b/modules/audio_processing/audio_processing_impl_unittest.cc index 5707f470c8..c7e25a9635 100644 --- a/modules/audio_processing/audio_processing_impl_unittest.cc +++ b/modules/audio_processing/audio_processing_impl_unittest.cc @@ -242,13 +242,13 @@ TEST(AudioProcessingImplTest, EXPECT_CALL(*echo_control_mock, AnalyzeCapture(testing::_)).Times(1); EXPECT_CALL(*echo_control_mock, - ProcessCapture(NotNull(), /*echo_path_change=*/false)) + ProcessCapture(NotNull(), testing::_, /*echo_path_change=*/false)) .Times(1); apm->ProcessStream(&frame); EXPECT_CALL(*echo_control_mock, AnalyzeCapture(testing::_)).Times(1); EXPECT_CALL(*echo_control_mock, - ProcessCapture(NotNull(), /*echo_path_change=*/true)) + ProcessCapture(NotNull(), testing::_, /*echo_path_change=*/true)) .Times(1); apm->SetRuntimeSetting( AudioProcessing::RuntimeSetting::CreateCapturePreGain(2.f)); @@ -286,7 +286,8 @@ TEST(AudioProcessingImplTest, const int initial_analog_gain = apm->recommended_stream_analog_level(); EXPECT_CALL(*echo_control_mock, AnalyzeCapture(testing::_)).Times(1); - EXPECT_CALL(*echo_control_mock, ProcessCapture(NotNull(), false)).Times(1); + EXPECT_CALL(*echo_control_mock, ProcessCapture(NotNull(), testing::_, false)) + .Times(1); apm->ProcessStream(&frame); // Force an analog gain change if it did not happen. @@ -295,7 +296,8 @@ TEST(AudioProcessingImplTest, } EXPECT_CALL(*echo_control_mock, AnalyzeCapture(testing::_)).Times(1); - EXPECT_CALL(*echo_control_mock, ProcessCapture(NotNull(), true)).Times(1); + EXPECT_CALL(*echo_control_mock, ProcessCapture(NotNull(), testing::_, true)) + .Times(1); apm->ProcessStream(&frame); } @@ -326,13 +328,13 @@ TEST(AudioProcessingImplTest, EchoControllerObservesPlayoutVolumeChange) { EXPECT_CALL(*echo_control_mock, AnalyzeCapture(testing::_)).Times(1); EXPECT_CALL(*echo_control_mock, - ProcessCapture(NotNull(), /*echo_path_change=*/false)) + ProcessCapture(NotNull(), testing::_, /*echo_path_change=*/false)) .Times(1); apm->ProcessStream(&frame); EXPECT_CALL(*echo_control_mock, AnalyzeCapture(testing::_)).Times(1); EXPECT_CALL(*echo_control_mock, - ProcessCapture(NotNull(), /*echo_path_change=*/false)) + ProcessCapture(NotNull(), testing::_, /*echo_path_change=*/false)) .Times(1); apm->SetRuntimeSetting( AudioProcessing::RuntimeSetting::CreatePlayoutVolumeChange(50)); @@ -340,7 +342,7 @@ TEST(AudioProcessingImplTest, EchoControllerObservesPlayoutVolumeChange) { EXPECT_CALL(*echo_control_mock, AnalyzeCapture(testing::_)).Times(1); EXPECT_CALL(*echo_control_mock, - ProcessCapture(NotNull(), /*echo_path_change=*/false)) + ProcessCapture(NotNull(), testing::_, /*echo_path_change=*/false)) .Times(1); apm->SetRuntimeSetting( AudioProcessing::RuntimeSetting::CreatePlayoutVolumeChange(50)); @@ -348,7 +350,7 @@ TEST(AudioProcessingImplTest, EchoControllerObservesPlayoutVolumeChange) { EXPECT_CALL(*echo_control_mock, AnalyzeCapture(testing::_)).Times(1); EXPECT_CALL(*echo_control_mock, - ProcessCapture(NotNull(), /*echo_path_change=*/true)) + ProcessCapture(NotNull(), testing::_, /*echo_path_change=*/true)) .Times(1); apm->SetRuntimeSetting( AudioProcessing::RuntimeSetting::CreatePlayoutVolumeChange(100)); diff --git a/modules/audio_processing/audio_processing_unittest.cc b/modules/audio_processing/audio_processing_unittest.cc index 06dbba7a5b..0e17db795c 100644 --- a/modules/audio_processing/audio_processing_unittest.cc +++ b/modules/audio_processing/audio_processing_unittest.cc @@ -2425,7 +2425,8 @@ class MyEchoControlFactory : public EchoControlFactory { auto ec = new test::MockEchoControl(); EXPECT_CALL(*ec, AnalyzeRender(::testing::_)).Times(1); EXPECT_CALL(*ec, AnalyzeCapture(::testing::_)).Times(2); - EXPECT_CALL(*ec, ProcessCapture(::testing::_, ::testing::_)).Times(2); + EXPECT_CALL(*ec, ProcessCapture(::testing::_, ::testing::_, ::testing::_)) + .Times(2); return std::unique_ptr(ec); } diff --git a/modules/audio_processing/include/audio_processing.h b/modules/audio_processing/include/audio_processing.h index f1242a7636..113bd2a2b8 100644 --- a/modules/audio_processing/include/audio_processing.h +++ b/modules/audio_processing/include/audio_processing.h @@ -280,6 +280,7 @@ class RTC_EXPORT AudioProcessing : public rtc::RefCountInterface { bool legacy_moderate_suppression_level = false; // Recommended not to use. Will be removed in the future. bool use_legacy_aec = false; + bool export_linear_aec_output = false; } echo_canceller; // Enables background noise suppression. @@ -611,6 +612,13 @@ class RTC_EXPORT AudioProcessing : public rtc::RefCountInterface { virtual int AnalyzeReverseStream(const float* const* data, const StreamConfig& reverse_config) = 0; + // Returns the most recently produced 10 ms of the linear AEC output at a rate + // of 16 kHz. If there is more than one capture channel, a mono representation + // of the input is returned. Returns true/false to indicate whether an output + // returned. + virtual bool GetLinearAecOutput( + rtc::ArrayView> linear_output) const = 0; + // This must be called prior to ProcessStream() if and only if adaptive analog // gain control is enabled, to pass the current analog level from the audio // HAL. Must be within the range provided in Config::GainController1. diff --git a/modules/audio_processing/include/mock_audio_processing.h b/modules/audio_processing/include/mock_audio_processing.h index 6b123923da..093269605d 100644 --- a/modules/audio_processing/include/mock_audio_processing.h +++ b/modules/audio_processing/include/mock_audio_processing.h @@ -47,6 +47,10 @@ class MockEchoControl : public EchoControl { MOCK_METHOD1(AnalyzeCapture, void(AudioBuffer* capture)); MOCK_METHOD2(ProcessCapture, void(AudioBuffer* capture, bool echo_path_change)); + MOCK_METHOD3(ProcessCapture, + void(AudioBuffer* capture, + AudioBuffer* linear_output, + bool echo_path_change)); MOCK_CONST_METHOD0(GetMetrics, Metrics()); MOCK_METHOD1(SetAudioBufferDelay, void(int delay_ms)); MOCK_CONST_METHOD0(ActiveProcessing, bool()); @@ -105,6 +109,9 @@ class MockAudioProcessing : public ::testing::NiceMock { const StreamConfig& input_config, const StreamConfig& output_config, float* const* dest)); + MOCK_CONST_METHOD1( + GetLinearAecOutput, + bool(rtc::ArrayView> linear_output)); MOCK_METHOD1(set_stream_delay_ms, int(int delay)); MOCK_CONST_METHOD0(stream_delay_ms, int()); MOCK_CONST_METHOD0(was_stream_delay_set, bool()); diff --git a/modules/audio_processing/test/audio_processing_simulator.cc b/modules/audio_processing/test/audio_processing_simulator.cc index 7f354a9164..38b97cabff 100644 --- a/modules/audio_processing/test/audio_processing_simulator.cc +++ b/modules/audio_processing/test/audio_processing_simulator.cc @@ -227,6 +227,20 @@ void AudioProcessingSimulator::ProcessStream(bool fixed_interface) { buffer_file_writer_->Write(*out_buf_); } + if (linear_aec_output_file_writer_) { + bool output_available = ap_->GetLinearAecOutput(linear_aec_output_buf_); + RTC_CHECK(output_available); + RTC_CHECK_GT(linear_aec_output_buf_.size(), 0); + RTC_CHECK_EQ(linear_aec_output_buf_[0].size(), 160); + for (size_t k = 0; k < linear_aec_output_buf_[0].size(); ++k) { + for (size_t ch = 0; ch < linear_aec_output_buf_.size(); ++ch) { + RTC_CHECK_EQ(linear_aec_output_buf_[ch].size(), 160); + linear_aec_output_file_writer_->WriteSamples( + &linear_aec_output_buf_[ch][k], 1); + } + } + } + if (residual_echo_likelihood_graph_writer_.is_open()) { auto stats = ap_->GetStatistics(true /*has_remote_tracks*/); residual_echo_likelihood_graph_writer_ @@ -342,6 +356,21 @@ void AudioProcessingSimulator::SetupOutput() { settings_.processed_capture_samples); } + if (settings_.linear_aec_output_filename) { + std::string filename; + if (settings_.store_intermediate_output) { + filename = GetIndexedOutputWavFilename( + *settings_.linear_aec_output_filename, output_reset_counter_); + } else { + filename = *settings_.linear_aec_output_filename; + } + + linear_aec_output_file_writer_.reset( + new WavWriter(filename, 16000, out_config_.num_channels())); + + linear_aec_output_buf_.resize(out_config_.num_channels()); + } + if (settings_.reverse_output_filename) { std::string filename; if (settings_.store_intermediate_output) { @@ -410,6 +439,8 @@ void AudioProcessingSimulator::CreateAudioProcessor() { apm_config.echo_canceller.mobile_mode = use_aecm; apm_config.echo_canceller.use_legacy_aec = use_legacy_aec; } + apm_config.echo_canceller.export_linear_aec_output = + !!settings_.linear_aec_output_filename; RTC_CHECK(!(use_legacy_aec && settings_.aec_settings_filename)) << "The legacy AEC cannot be configured using settings"; @@ -421,9 +452,14 @@ void AudioProcessingSimulator::CreateAudioProcessor() { std::cout << "Reading AEC Parameters from JSON input." << std::endl; } cfg = ReadAec3ConfigFromJsonFile(*settings_.aec_settings_filename); - echo_control_factory.reset(new EchoCanceller3Factory(cfg)); } + if (settings_.linear_aec_output_filename) { + cfg.filter.export_linear_aec_output = true; + } + + echo_control_factory.reset(new EchoCanceller3Factory(cfg)); + if (settings_.print_aec_parameter_values) { if (!settings_.use_quiet_output) { std::cout << "AEC settings:" << std::endl; diff --git a/modules/audio_processing/test/audio_processing_simulator.h b/modules/audio_processing/test/audio_processing_simulator.h index bf718b2fe5..8ee2db8d40 100644 --- a/modules/audio_processing/test/audio_processing_simulator.h +++ b/modules/audio_processing/test/audio_processing_simulator.h @@ -47,6 +47,7 @@ struct SimulationSettings { absl::optional input_filename; absl::optional reverse_input_filename; absl::optional artificial_nearend_filename; + absl::optional linear_aec_output_filename; absl::optional use_aec; absl::optional use_aecm; absl::optional use_ed; // Residual Echo Detector. @@ -156,6 +157,7 @@ class AudioProcessingSimulator { std::unique_ptr> out_buf_; std::unique_ptr> reverse_in_buf_; std::unique_ptr> reverse_out_buf_; + std::vector> linear_aec_output_buf_; StreamConfig in_config_; StreamConfig out_config_; StreamConfig reverse_in_config_; @@ -178,6 +180,7 @@ class AudioProcessingSimulator { std::unique_ptr buffer_file_writer_; std::unique_ptr reverse_buffer_file_writer_; std::unique_ptr buffer_memory_writer_; + std::unique_ptr linear_aec_output_file_writer_; ApiCallStatistics api_call_statistics_; std::ofstream residual_echo_likelihood_graph_writer_; int analog_mic_level_; diff --git a/modules/audio_processing/test/audioproc_float_impl.cc b/modules/audio_processing/test/audioproc_float_impl.cc index 3e755b585c..8301c4ecd4 100644 --- a/modules/audio_processing/test/audioproc_float_impl.cc +++ b/modules/audio_processing/test/audioproc_float_impl.cc @@ -40,6 +40,7 @@ ABSL_FLAG(std::string, artificial_nearend, "", "Artificial nearend wav filename"); +ABSL_FLAG(std::string, linear_aec_output, "", "Linear AEC output wav filename"); ABSL_FLAG(int, output_num_channels, kParameterNotSpecifiedValue, @@ -364,6 +365,8 @@ SimulationSettings CreateSettings() { &settings.reverse_output_filename); SetSettingIfSpecified(absl::GetFlag(FLAGS_artificial_nearend), &settings.artificial_nearend_filename); + SetSettingIfSpecified(absl::GetFlag(FLAGS_linear_aec_output), + &settings.linear_aec_output_filename); SetSettingIfSpecified(absl::GetFlag(FLAGS_output_num_channels), &settings.output_num_channels); SetSettingIfSpecified(absl::GetFlag(FLAGS_reverse_output_num_channels), @@ -508,6 +511,19 @@ void PerformBasicParameterSanityChecks(const SimulationSettings& settings) { "aec dump input string!\n"); } + ReportConditionalErrorAndExit(settings.use_aec && !(*settings.use_aec) && + settings.linear_aec_output_filename, + "Error: The linear AEC ouput filename cannot " + "be specified without the AEC being active"); + + ReportConditionalErrorAndExit( + ((settings.use_aec && *settings.use_aec && settings.use_legacy_aec && + *settings.use_legacy_aec) || + (settings.use_aecm && *settings.use_aecm)) && + !!settings.linear_aec_output_filename, + "Error: The linear AEC ouput filename cannot be specified when the " + "legacy AEC or the AECm are used"); + ReportConditionalErrorAndExit( settings.use_aec && *settings.use_aec && settings.use_aecm && *settings.use_aecm, @@ -617,6 +633,11 @@ void PerformBasicParameterSanityChecks(const SimulationSettings& settings) { !valid_wav_name(*settings.artificial_nearend_filename), "Error: --artifical_nearend must be a valid .wav file name.\n"); + ReportConditionalErrorAndExit( + settings.linear_aec_output_filename && + (!valid_wav_name(*settings.linear_aec_output_filename)), + "Error: --linear_aec_output must be a valid .wav file name.\n"); + ReportConditionalErrorAndExit( WEBRTC_APM_DEBUG_DUMP == 0 && settings.dump_internal_data, "Error: --dump_data cannot be set without proper build support.\n"); diff --git a/modules/audio_processing/test/echo_control_mock.h b/modules/audio_processing/test/echo_control_mock.h index c2082c2b6b..95d3be5cdf 100644 --- a/modules/audio_processing/test/echo_control_mock.h +++ b/modules/audio_processing/test/echo_control_mock.h @@ -24,6 +24,10 @@ class MockEchoControl : public EchoControl { MOCK_METHOD1(AnalyzeCapture, void(AudioBuffer* capture)); MOCK_METHOD2(ProcessCapture, void(AudioBuffer* capture, bool echo_path_change)); + MOCK_METHOD3(ProcessCapture, + void(AudioBuffer* capture, + AudioBuffer* linear_output, + bool echo_path_change)); MOCK_CONST_METHOD0(GetMetrics, EchoControl::Metrics()); MOCK_METHOD1(SetAudioBufferDelay, void(int delay_ms)); MOCK_CONST_METHOD0(ActiveProcessing, bool());