AEC3: Analyze multi-channel SubtractorOutput in AecState Updates SubtractorOutputAnalyzer and AecState::SaturationDetector to multi-channel. Bug: webrtc:10913 Change-Id: I39edafdc5d5a4db5cc853cf116d60af0f506b3bf Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/154342 Commit-Queue: Sam Zackrisson <saza@webrtc.org> Reviewed-by: Gustaf Ullberg <gustaf@webrtc.org> Reviewed-by: Per Ã…hgren <peah@webrtc.org> Cr-Commit-Position: refs/heads/master@{#29355}
diff --git a/modules/audio_processing/aec3/adaptive_fir_filter_unittest.cc b/modules/audio_processing/aec3/adaptive_fir_filter_unittest.cc index 9318c21..36e31eb 100644 --- a/modules/audio_processing/aec3/adaptive_fir_filter_unittest.cc +++ b/modules/audio_processing/aec3/adaptive_fir_filter_unittest.cc
@@ -298,6 +298,7 @@ // adapt its coefficients. TEST(AdaptiveFirFilter, FilterAndAdapt) { constexpr size_t kNumRenderChannels = 1; + constexpr size_t kNumCaptureChannels = 1; constexpr int kSampleRateHz = 48000; constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz); @@ -325,12 +326,12 @@ kNumRenderChannels, std::vector<float>(kBlockSize, 0.f))); std::vector<float> n(kBlockSize, 0.f); std::vector<float> y(kBlockSize, 0.f); - AecState aec_state(EchoCanceller3Config{}); + AecState aec_state(EchoCanceller3Config{}, kNumCaptureChannels); RenderSignalAnalyzer render_signal_analyzer(config); absl::optional<DelayEstimate> delay_estimate; std::vector<float> e(kBlockSize, 0.f); std::array<float, kFftLength> s_scratch; - SubtractorOutput output; + std::vector<SubtractorOutput> output(kNumCaptureChannels); FftData S; FftData G; FftData E; @@ -344,7 +345,9 @@ Y2.fill(0.f); E2_main.fill(0.f); E2_shadow.fill(0.f); - output.Reset(); + for (auto& subtractor_output : output) { + subtractor_output.Reset(); + } constexpr float kScale = 1.0f / kFftLengthBy2; @@ -385,7 +388,7 @@ [](float& a) { a = rtc::SafeClamp(a, -32768.f, 32767.f); }); fft.ZeroPaddedFft(e, Aec3Fft::Window::kRectangular, &E); for (size_t k = 0; k < kBlockSize; ++k) { - output.s_main[k] = kScale * s_scratch[k + kFftLengthBy2]; + output[0].s_main[k] = kScale * s_scratch[k + kFftLengthBy2]; } std::array<float, kFftLengthBy2Plus1> render_power; @@ -398,7 +401,7 @@ filter.ComputeFrequencyResponse(&H2); aec_state.Update(delay_estimate, H2, h, *render_buffer, E2_main, Y2, - output, y); + output); } // Verify that the filter is able to perform well. EXPECT_LT(1000 * std::inner_product(e.begin(), e.end(), e.begin(), 0.f),
diff --git a/modules/audio_processing/aec3/aec_state.cc b/modules/audio_processing/aec3/aec_state.cc index 8ff2930..97c27d5 100644 --- a/modules/audio_processing/aec3/aec_state.cc +++ b/modules/audio_processing/aec3/aec_state.cc
@@ -55,7 +55,8 @@ return absl::nullopt; } -AecState::AecState(const EchoCanceller3Config& config) +AecState::AecState(const EchoCanceller3Config& config, + size_t num_capture_channels) : data_dumper_( new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))), config_(config), @@ -68,7 +69,8 @@ filter_analyzer_(config_), echo_audibility_( config_.echo_audibility.use_stationarity_properties_at_init), - reverb_model_estimator_(config_) {} + reverb_model_estimator_(config_), + subtractor_output_analyzers_(num_capture_channels) {} AecState::~AecState() = default; @@ -95,7 +97,9 @@ } else if (echo_path_variability.gain_change) { erle_estimator_.Reset(false); } - subtractor_output_analyzer_.HandleEchoPathChange(); + for (auto& analyzer : subtractor_output_analyzers_) { + analyzer.HandleEchoPathChange(); + } } void AecState::Update( @@ -106,10 +110,13 @@ const RenderBuffer& render_buffer, const std::array<float, kFftLengthBy2Plus1>& E2_main, const std::array<float, kFftLengthBy2Plus1>& Y2, - const SubtractorOutput& subtractor_output, - rtc::ArrayView<const float> y) { + rtc::ArrayView<const SubtractorOutput> subtractor_output) { + RTC_DCHECK_EQ(subtractor_output.size(), subtractor_output_analyzers_.size()); + // Analyze the filter output. - subtractor_output_analyzer_.Update(subtractor_output); + for (size_t ch = 0; ch < subtractor_output.size(); ++ch) { + subtractor_output_analyzers_[ch].Update(subtractor_output[ch]); + } // Analyze the properties of the filter. filter_analyzer_.Update(adaptive_filter_impulse_response, render_buffer); @@ -120,17 +127,22 @@ strong_not_saturated_render_blocks_); } - const std::vector<float>& aligned_render_block = - render_buffer.Block(-delay_state_.DirectPathFilterDelay())[0][0]; + const std::vector<std::vector<float>>& aligned_render_block = + render_buffer.Block(-delay_state_.DirectPathFilterDelay())[0]; // Update render counters. - const float render_energy = std::inner_product( - aligned_render_block.begin(), aligned_render_block.end(), - aligned_render_block.begin(), 0.f); - const bool active_render = - render_energy > (config_.render_levels.active_render_limit * - config_.render_levels.active_render_limit) * - kFftLengthBy2; + bool active_render = false; + for (size_t ch = 0; ch < aligned_render_block.size(); ++ch) { + const float render_energy = std::inner_product( + aligned_render_block[ch].begin(), aligned_render_block[ch].end(), + aligned_render_block[ch].begin(), 0.f); + if (render_energy > (config_.render_levels.active_render_limit * + config_.render_levels.active_render_limit) * + kFftLengthBy2) { + active_render = true; + break; + } + } blocks_with_active_render_ += active_render ? 1 : 0; strong_not_saturated_render_blocks_ += active_render && !SaturatedCapture() ? 1 : 0; @@ -153,16 +165,18 @@ erle_estimator_.Reset(false); } + // TODO(bugs.webrtc.org/10913): Take all channels into account. const auto& X2 = render_buffer.Spectrum(delay_state_.DirectPathFilterDelay(), /*channel=*/0); const auto& X2_input_erle = X2_reverb; erle_estimator_.Update(render_buffer, adaptive_filter_frequency_response, X2_input_erle, Y2, E2_main, - subtractor_output_analyzer_.ConvergedFilter(), + subtractor_output_analyzers_[0].ConvergedFilter(), config_.erle.onset_detection); - erl_estimator_.Update(subtractor_output_analyzer_.ConvergedFilter(), X2, Y2); + erl_estimator_.Update(subtractor_output_analyzers_[0].ConvergedFilter(), X2, + Y2); // Detect and flag echo saturation. saturation_detector_.Update(aligned_render_block, SaturatedCapture(), @@ -175,15 +189,15 @@ // Detect whether the transparent mode should be activated. transparent_state_.Update(delay_state_.DirectPathFilterDelay(), filter_analyzer_.Consistent(), - subtractor_output_analyzer_.ConvergedFilter(), - subtractor_output_analyzer_.DivergedFilter(), + subtractor_output_analyzers_[0].ConvergedFilter(), + subtractor_output_analyzers_[0].DivergedFilter(), active_render, SaturatedCapture()); // Analyze the quality of the filter. - filter_quality_state_.Update(active_render, TransparentMode(), - SaturatedCapture(), - filter_analyzer_.Consistent(), external_delay, - subtractor_output_analyzer_.ConvergedFilter()); + filter_quality_state_.Update( + active_render, TransparentMode(), SaturatedCapture(), + filter_analyzer_.Consistent(), external_delay, + subtractor_output_analyzers_[0].ConvergedFilter()); // Update the reverb estimate. const bool stationary_block = @@ -212,9 +226,9 @@ data_dumper_->DumpRaw("aec3_capture_saturation", SaturatedCapture()); data_dumper_->DumpRaw("aec3_echo_saturation", SaturatedEcho()); data_dumper_->DumpRaw("aec3_converged_filter", - subtractor_output_analyzer_.ConvergedFilter()); + subtractor_output_analyzers_[0].ConvergedFilter()); data_dumper_->DumpRaw("aec3_diverged_filter", - subtractor_output_analyzer_.DivergedFilter()); + subtractor_output_analyzers_[0].DivergedFilter()); data_dumper_->DumpRaw("aec3_external_delay_avaliable", external_delay ? 1 : 0); @@ -406,27 +420,36 @@ usable_linear_estimate_ = usable_linear_estimate_ && !transparent_mode; } - void AecState::SaturationDetector::Update( - rtc::ArrayView<const float> x, + rtc::ArrayView<const std::vector<float>> x, bool saturated_capture, bool usable_linear_estimate, - const SubtractorOutput& subtractor_output, + rtc::ArrayView<const SubtractorOutput> subtractor_output, float echo_path_gain) { - saturated_echo_ = saturated_capture; + saturated_echo_ = false; + if (!saturated_capture) { + return; + } + if (usable_linear_estimate) { constexpr float kSaturationThreshold = 20000.f; - saturated_echo_ = - saturated_echo_ && - (subtractor_output.s_main_max_abs > kSaturationThreshold || - subtractor_output.s_shadow_max_abs > kSaturationThreshold); + for (size_t ch = 0; ch < subtractor_output.size(); ++ch) { + saturated_echo_ = + saturated_echo_ || + (subtractor_output[ch].s_main_max_abs > kSaturationThreshold || + subtractor_output[ch].s_shadow_max_abs > kSaturationThreshold); + } } else { - const float max_sample = fabs(*std::max_element( - x.begin(), x.end(), [](float a, float b) { return a * a < b * b; })); + float max_sample = 0.f; + for (auto& channel : x) { + for (float sample : channel) { + max_sample = std::max(max_sample, fabsf(sample)); + } + } const float kMargin = 10.f; float peak_echo_amplitude = max_sample * echo_path_gain * kMargin; - saturated_echo_ = saturated_echo_ && peak_echo_amplitude > 32000; + saturated_echo_ = saturated_echo_ || peak_echo_amplitude > 32000; } }
diff --git a/modules/audio_processing/aec3/aec_state.h b/modules/audio_processing/aec3/aec_state.h index 43cdb0b..1229732 100644 --- a/modules/audio_processing/aec3/aec_state.h +++ b/modules/audio_processing/aec3/aec_state.h
@@ -40,7 +40,7 @@ // Handles the state and the conditions for the echo removal functionality. class AecState { public: - explicit AecState(const EchoCanceller3Config& config); + AecState(const EchoCanceller3Config& config, size_t num_capture_channels); ~AecState(); // Returns whether the echo subtractor can be used to determine the residual @@ -129,6 +129,8 @@ } // Updates the aec state. + // TODO(bugs.webrtc.org/10913): Handle multi-channel adaptive filter response. + // TODO(bugs.webrtc.org/10913): Compute multi-channel ERL, ERLE, and reverb. void Update(const absl::optional<DelayEstimate>& external_delay, const std::vector<std::array<float, kFftLengthBy2Plus1>>& adaptive_filter_frequency_response, @@ -136,8 +138,7 @@ const RenderBuffer& render_buffer, const std::array<float, kFftLengthBy2Plus1>& E2_main, const std::array<float, kFftLengthBy2Plus1>& Y2, - const SubtractorOutput& subtractor_output, - rtc::ArrayView<const float> y); + rtc::ArrayView<const SubtractorOutput> subtractor_output); // Returns filter length in blocks. int FilterLengthBlocks() const { @@ -275,10 +276,10 @@ bool SaturatedEcho() const { return saturated_echo_; } // Updates the detection decision based on new data. - void Update(rtc::ArrayView<const float> x, + void Update(rtc::ArrayView<const std::vector<float>> x, bool saturated_capture, bool usable_linear_estimate, - const SubtractorOutput& subtractor_output, + rtc::ArrayView<const SubtractorOutput> subtractor_output, float echo_path_gain); private: @@ -295,7 +296,7 @@ EchoAudibility echo_audibility_; ReverbModelEstimator reverb_model_estimator_; RenderReverbModel render_reverb_; - SubtractorOutputAnalyzer subtractor_output_analyzer_; + std::vector<SubtractorOutputAnalyzer> subtractor_output_analyzers_; }; } // namespace webrtc
diff --git a/modules/audio_processing/aec3/aec_state_unittest.cc b/modules/audio_processing/aec3/aec_state_unittest.cc index 4631eac..ccf953a 100644 --- a/modules/audio_processing/aec3/aec_state_unittest.cc +++ b/modules/audio_processing/aec3/aec_state_unittest.cc
@@ -13,37 +13,48 @@ #include "modules/audio_processing/aec3/aec3_fft.h" #include "modules/audio_processing/aec3/render_delay_buffer.h" #include "modules/audio_processing/logging/apm_data_dumper.h" +#include "rtc_base/strings/string_builder.h" #include "test/gtest.h" namespace webrtc { +namespace { +std::string ProduceDebugText(size_t num_render_channels, + size_t num_capture_channels) { + rtc::StringBuilder ss; + ss << "Render channels: " << num_render_channels; + ss << ", Capture channels: " << num_capture_channels; + return ss.Release(); +} -// Verify the general functionality of AecState -TEST(AecState, NormalUsage) { - constexpr size_t kNumChannels = 1; +void RunNormalUsageTest(size_t num_render_channels, + size_t num_capture_channels) { + // TODO(bugs.webrtc.org/10913): Test with different content in different + // channels. constexpr int kSampleRateHz = 48000; constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz); ApmDataDumper data_dumper(42); EchoCanceller3Config config; - AecState state(config); + AecState state(config, num_capture_channels); absl::optional<DelayEstimate> delay_estimate = DelayEstimate(DelayEstimate::Quality::kRefined, 10); std::unique_ptr<RenderDelayBuffer> render_delay_buffer( - RenderDelayBuffer::Create(config, kSampleRateHz, kNumChannels)); + RenderDelayBuffer::Create(config, kSampleRateHz, num_render_channels)); std::array<float, kFftLengthBy2Plus1> E2_main = {}; std::array<float, kFftLengthBy2Plus1> Y2 = {}; std::vector<std::vector<std::vector<float>>> x( kNumBands, std::vector<std::vector<float>>( - kNumChannels, std::vector<float>(kBlockSize, 0.f))); + num_render_channels, std::vector<float>(kBlockSize, 0.f))); EchoPathVariability echo_path_variability( false, EchoPathVariability::DelayAdjustment::kNone, false); - SubtractorOutput output; - output.Reset(); - std::array<float, kBlockSize> y; + std::vector<std::array<float, kBlockSize>> y(num_capture_channels); + std::vector<SubtractorOutput> subtractor_output(num_capture_channels); + for (size_t ch = 0; ch < num_capture_channels; ++ch) { + subtractor_output[ch].Reset(); + subtractor_output[ch].s_main.fill(100.f); + subtractor_output[ch].e_main.fill(100.f); + y[ch].fill(1000.f); + } Aec3Fft fft; - output.s_main.fill(100.f); - output.e_main.fill(100.f); - y.fill(1000.f); - std::vector<std::array<float, kFftLengthBy2Plus1>> converged_filter_frequency_response(10); for (auto& v : converged_filter_frequency_response) { @@ -53,52 +64,59 @@ diverged_filter_frequency_response = converged_filter_frequency_response; converged_filter_frequency_response[2].fill(100.f); converged_filter_frequency_response[2][0] = 1.f; - std::vector<float> impulse_response( GetTimeDomainLength(config.filter.main.length_blocks), 0.f); // Verify that linear AEC usability is true when the filter is converged for (size_t band = 0; band < kNumBands; ++band) { - for (size_t channel = 0; channel < kNumChannels; ++channel) { - std::fill(x[band][channel].begin(), x[band][channel].end(), 101.f); + for (size_t ch = 0; ch < num_render_channels; ++ch) { + std::fill(x[band][ch].begin(), x[band][ch].end(), 101.f); } } for (int k = 0; k < 3000; ++k) { render_delay_buffer->Insert(x); - output.ComputeMetrics(y); + for (size_t ch = 0; ch < num_capture_channels; ++ch) { + subtractor_output[ch].ComputeMetrics(y[ch]); + } state.Update(delay_estimate, converged_filter_frequency_response, impulse_response, *render_delay_buffer->GetRenderBuffer(), - E2_main, Y2, output, y); + E2_main, Y2, subtractor_output); } EXPECT_TRUE(state.UsableLinearEstimate()); - // Verify that linear AEC usability becomes false after an echo path change is - // reported - output.ComputeMetrics(y); + // Verify that linear AEC usability becomes false after an echo path + // change is reported + for (size_t ch = 0; ch < num_capture_channels; ++ch) { + subtractor_output[ch].ComputeMetrics(y[ch]); + } state.HandleEchoPathChange(EchoPathVariability( false, EchoPathVariability::DelayAdjustment::kBufferReadjustment, false)); state.Update(delay_estimate, converged_filter_frequency_response, impulse_response, *render_delay_buffer->GetRenderBuffer(), - E2_main, Y2, output, y); + E2_main, Y2, subtractor_output); EXPECT_FALSE(state.UsableLinearEstimate()); // Verify that the active render detection works as intended. std::fill(x[0][0].begin(), x[0][0].end(), 101.f); render_delay_buffer->Insert(x); - output.ComputeMetrics(y); + for (size_t ch = 0; ch < num_capture_channels; ++ch) { + subtractor_output[ch].ComputeMetrics(y[ch]); + } state.HandleEchoPathChange(EchoPathVariability( true, EchoPathVariability::DelayAdjustment::kNewDetectedDelay, false)); state.Update(delay_estimate, converged_filter_frequency_response, impulse_response, *render_delay_buffer->GetRenderBuffer(), - E2_main, Y2, output, y); + E2_main, Y2, subtractor_output); EXPECT_FALSE(state.ActiveRender()); for (int k = 0; k < 1000; ++k) { render_delay_buffer->Insert(x); - output.ComputeMetrics(y); + for (size_t ch = 0; ch < num_capture_channels; ++ch) { + subtractor_output[ch].ComputeMetrics(y[ch]); + } state.Update(delay_estimate, converged_filter_frequency_response, impulse_response, *render_delay_buffer->GetRenderBuffer(), - E2_main, Y2, output, y); + E2_main, Y2, subtractor_output); } EXPECT_TRUE(state.ActiveRender()); @@ -121,10 +139,12 @@ Y2.fill(10.f * 10000.f * 10000.f); for (size_t k = 0; k < 1000; ++k) { - output.ComputeMetrics(y); + for (size_t ch = 0; ch < num_capture_channels; ++ch) { + subtractor_output[ch].ComputeMetrics(y[ch]); + } state.Update(delay_estimate, converged_filter_frequency_response, impulse_response, *render_delay_buffer->GetRenderBuffer(), - E2_main, Y2, output, y); + E2_main, Y2, subtractor_output); } ASSERT_TRUE(state.UsableLinearEstimate()); @@ -139,15 +159,17 @@ E2_main.fill(1.f * 10000.f * 10000.f); Y2.fill(10.f * E2_main[0]); for (size_t k = 0; k < 1000; ++k) { - output.ComputeMetrics(y); + for (size_t ch = 0; ch < num_capture_channels; ++ch) { + subtractor_output[ch].ComputeMetrics(y[ch]); + } state.Update(delay_estimate, converged_filter_frequency_response, impulse_response, *render_delay_buffer->GetRenderBuffer(), - E2_main, Y2, output, y); + E2_main, Y2, subtractor_output); } ASSERT_TRUE(state.UsableLinearEstimate()); { - // Note that the render spectrum is built so it does not have energy in the - // odd bands but just in the even bands. + // Note that the render spectrum is built so it does not have energy in + // the odd bands but just in the even bands. const auto& erle = state.Erle(); EXPECT_EQ(erle[0], erle[1]); constexpr size_t kLowFrequencyLimit = 32; @@ -163,10 +185,12 @@ E2_main.fill(1.f * 10000.f * 10000.f); Y2.fill(5.f * E2_main[0]); for (size_t k = 0; k < 1000; ++k) { - output.ComputeMetrics(y); + for (size_t ch = 0; ch < num_capture_channels; ++ch) { + subtractor_output[ch].ComputeMetrics(y[ch]); + } state.Update(delay_estimate, converged_filter_frequency_response, impulse_response, *render_delay_buffer->GetRenderBuffer(), - E2_main, Y2, output, y); + E2_main, Y2, subtractor_output); } ASSERT_TRUE(state.UsableLinearEstimate()); @@ -184,11 +208,24 @@ } } +} // namespace + +// Verify the general functionality of AecState +TEST(AecState, NormalUsage) { + for (size_t num_render_channels : {1, 2, 8}) { + for (size_t num_capture_channels : {1, 2, 8}) { + SCOPED_TRACE(ProduceDebugText(num_render_channels, num_capture_channels)); + RunNormalUsageTest(num_render_channels, num_capture_channels); + } + } +} + // Verifies the delay for a converged filter is correctly identified. TEST(AecState, ConvergedFilterDelay) { constexpr int kFilterLengthBlocks = 10; + constexpr size_t kNumCaptureChannels = 1; EchoCanceller3Config config; - AecState state(config); + AecState state(config, kNumCaptureChannels); std::unique_ptr<RenderDelayBuffer> render_delay_buffer( RenderDelayBuffer::Create(config, 48000, 1)); absl::optional<DelayEstimate> delay_estimate; @@ -197,10 +234,12 @@ std::array<float, kBlockSize> x; EchoPathVariability echo_path_variability( false, EchoPathVariability::DelayAdjustment::kNone, false); - SubtractorOutput output; - output.Reset(); + std::vector<SubtractorOutput> subtractor_output(kNumCaptureChannels); + for (auto& output : subtractor_output) { + output.Reset(); + output.s_main.fill(100.f); + } std::array<float, kBlockSize> y; - output.s_main.fill(100.f); x.fill(0.f); y.fill(0.f); @@ -213,16 +252,17 @@ std::vector<float> impulse_response( GetTimeDomainLength(config.filter.main.length_blocks), 0.f); - // Verify that the filter delay for a converged filter is properly identified. + // Verify that the filter delay for a converged filter is properly + // identified. for (int k = 0; k < kFilterLengthBlocks; ++k) { std::fill(impulse_response.begin(), impulse_response.end(), 0.f); impulse_response[k * kBlockSize + 1] = 1.f; state.HandleEchoPathChange(echo_path_variability); - output.ComputeMetrics(y); + subtractor_output[0].ComputeMetrics(y); state.Update(delay_estimate, frequency_response, impulse_response, - *render_delay_buffer->GetRenderBuffer(), E2_main, Y2, output, - y); + *render_delay_buffer->GetRenderBuffer(), E2_main, Y2, + subtractor_output); } }
diff --git a/modules/audio_processing/aec3/comfort_noise_generator_unittest.cc b/modules/audio_processing/aec3/comfort_noise_generator_unittest.cc index bac30b4..94aa039 100644 --- a/modules/audio_processing/aec3/comfort_noise_generator_unittest.cc +++ b/modules/audio_processing/aec3/comfort_noise_generator_unittest.cc
@@ -37,7 +37,7 @@ FftData noise; EXPECT_DEATH( ComfortNoiseGenerator(DetectOptimization(), 42) - .Compute(AecState(EchoCanceller3Config{}), N2, nullptr, &noise), + .Compute(AecState(EchoCanceller3Config{}, 1), N2, nullptr, &noise), ""); } @@ -46,7 +46,7 @@ FftData noise; EXPECT_DEATH( ComfortNoiseGenerator(DetectOptimization(), 42) - .Compute(AecState(EchoCanceller3Config{}), N2, &noise, nullptr), + .Compute(AecState(EchoCanceller3Config{}, 1), N2, &noise, nullptr), ""); } @@ -54,7 +54,7 @@ TEST(ComfortNoiseGenerator, CorrectLevel) { ComfortNoiseGenerator cng(DetectOptimization(), 42); - AecState aec_state(EchoCanceller3Config{}); + AecState aec_state(EchoCanceller3Config{}, 1); std::array<float, kFftLengthBy2Plus1> N2; N2.fill(1000.f * 1000.f);
diff --git a/modules/audio_processing/aec3/echo_remover.cc b/modules/audio_processing/aec3/echo_remover.cc index c9a58ec..2df9cfd 100644 --- a/modules/audio_processing/aec3/echo_remover.cc +++ b/modules/audio_processing/aec3/echo_remover.cc
@@ -202,7 +202,7 @@ num_capture_channels_), render_signal_analyzer_(config_), residual_echo_estimators_(num_capture_channels_), - aec_state_(config_), + aec_state_(config_, num_capture_channels_), e_old_(num_capture_channels_), y_old_(num_capture_channels_), e_heap_(NumChannelsOnHeap(num_capture_channels_)), @@ -388,7 +388,7 @@ // TODO(bugs.webrtc.org/10913): Take all subtractors into account. aec_state_.Update(external_delay, subtractor_.FilterFrequencyResponse(), subtractor_.FilterImpulseResponse(), *render_buffer, E2[0], - Y2[0], subtractor_output[0], y0); + Y2[0], subtractor_output); // Choose the linear output. const auto& Y_fft = aec_state_.UseLinearFilterOutput() ? E : Y;
diff --git a/modules/audio_processing/aec3/echo_remover_metrics_unittest.cc b/modules/audio_processing/aec3/echo_remover_metrics_unittest.cc index c16c7ea..30c6611 100644 --- a/modules/audio_processing/aec3/echo_remover_metrics_unittest.cc +++ b/modules/audio_processing/aec3/echo_remover_metrics_unittest.cc
@@ -138,7 +138,7 @@ // Verify the general functionality of EchoRemoverMetrics. TEST(EchoRemoverMetrics, NormalUsage) { EchoRemoverMetrics metrics; - AecState aec_state(EchoCanceller3Config{}); + AecState aec_state(EchoCanceller3Config{}, 1); std::array<float, kFftLengthBy2Plus1> comfort_noise_spectrum; std::array<float, kFftLengthBy2Plus1> suppressor_gain; comfort_noise_spectrum.fill(10.f);
diff --git a/modules/audio_processing/aec3/main_filter_update_gain_unittest.cc b/modules/audio_processing/aec3/main_filter_update_gain_unittest.cc index e78f1cd..20714ce 100644 --- a/modules/audio_processing/aec3/main_filter_update_gain_unittest.cc +++ b/modules/audio_processing/aec3/main_filter_update_gain_unittest.cc
@@ -83,21 +83,23 @@ config.delay.default_delay = 1; std::unique_ptr<RenderDelayBuffer> render_delay_buffer( RenderDelayBuffer::Create(config, kSampleRateHz, kNumChannels)); - AecState aec_state(config); + AecState aec_state(config, kNumChannels); RenderSignalAnalyzer render_signal_analyzer(config); absl::optional<DelayEstimate> delay_estimate; std::array<float, kFftLength> s_scratch; std::array<float, kBlockSize> s; FftData S; FftData G; - SubtractorOutput output; - output.Reset(); - FftData& E_main = output.E_main; + std::vector<SubtractorOutput> output(kNumChannels); + for (auto& subtractor_output : output) { + subtractor_output.Reset(); + } + FftData& E_main = output[0].E_main; FftData E_shadow; std::array<float, kFftLengthBy2Plus1> Y2; - std::array<float, kFftLengthBy2Plus1>& E2_main = output.E2_main; - std::array<float, kBlockSize>& e_main = output.e_main; - std::array<float, kBlockSize>& e_shadow = output.e_shadow; + std::array<float, kFftLengthBy2Plus1>& E2_main = output[0].E2_main; + std::array<float, kBlockSize>& e_main = output[0].e_main; + std::array<float, kBlockSize>& e_shadow = output[0].e_shadow; Y2.fill(0.f); constexpr float kScale = 1.0f / kFftLengthBy2; @@ -165,8 +167,8 @@ fft.ZeroPaddedFft(e_shadow, Aec3Fft::Window::kRectangular, &E_shadow); // Compute spectra for future use. - E_main.Spectrum(Aec3Optimization::kNone, output.E2_main); - E_shadow.Spectrum(Aec3Optimization::kNone, output.E2_shadow); + E_main.Spectrum(Aec3Optimization::kNone, output[0].E2_main); + E_shadow.Spectrum(Aec3Optimization::kNone, output[0].E2_shadow); // Adapt the shadow filter. std::array<float, kFftLengthBy2Plus1> render_power; @@ -182,7 +184,7 @@ std::array<float, kFftLengthBy2Plus1> erl; ComputeErl(optimization, H2, erl); - main_gain.Compute(render_power, render_signal_analyzer, output, erl, + main_gain.Compute(render_power, render_signal_analyzer, output[0], erl, main_filter.SizePartitions(), saturation, &G); main_filter.Adapt(*render_delay_buffer->GetRenderBuffer(), G, &h); @@ -192,7 +194,7 @@ main_filter.ComputeFrequencyResponse(&H2); aec_state.Update(delay_estimate, H2, h, *render_delay_buffer->GetRenderBuffer(), E2_main, Y2, - output, y); + output); } std::copy(e_main.begin(), e_main.end(), e_last_block->begin());
diff --git a/modules/audio_processing/aec3/residual_echo_estimator_unittest.cc b/modules/audio_processing/aec3/residual_echo_estimator_unittest.cc index 863f8f8..2823cae 100644 --- a/modules/audio_processing/aec3/residual_echo_estimator_unittest.cc +++ b/modules/audio_processing/aec3/residual_echo_estimator_unittest.cc
@@ -25,7 +25,7 @@ // Verifies that the check for non-null output residual echo power works. TEST(ResidualEchoEstimator, NullResidualEchoPowerOutput) { EchoCanceller3Config config; - AecState aec_state(config); + AecState aec_state(config, 1); std::unique_ptr<RenderDelayBuffer> render_delay_buffer( RenderDelayBuffer::Create(config, 48000, 1)); std::vector<std::array<float, kFftLengthBy2Plus1>> H2; @@ -49,7 +49,7 @@ EchoCanceller3Config config; config.ep_strength.default_len = 0.f; ResidualEchoEstimator estimator(config); - AecState aec_state(config); + AecState aec_state(config, kNumChannels); std::unique_ptr<RenderDelayBuffer> render_delay_buffer( RenderDelayBuffer::Create(config, kSampleRateHz, kNumChannels)); @@ -66,7 +66,7 @@ kNumChannels, std::vector<float>(kBlockSize, 0.f))); std::vector<std::array<float, kFftLengthBy2Plus1>> H2(10); Random random_generator(42U); - SubtractorOutput output; + std::vector<SubtractorOutput> output(kNumChannels); std::array<float, kBlockSize> y; Aec3Fft fft; absl::optional<DelayEstimate> delay_estimate; @@ -80,8 +80,10 @@ std::vector<float> h(GetTimeDomainLength(config.filter.main.length_blocks), 0.f); - output.Reset(); - output.s_main.fill(100.f); + for (auto& subtractor_output : output) { + subtractor_output.Reset(); + subtractor_output.s_main.fill(100.f); + } y.fill(0.f); constexpr float kLevel = 10.f; @@ -103,7 +105,7 @@ aec_state.HandleEchoPathChange(echo_path_variability); aec_state.Update(delay_estimate, H2, h, *render_delay_buffer->GetRenderBuffer(), E2_main, Y2, - output, y); + output); estimator.Estimate(aec_state, *render_delay_buffer->GetRenderBuffer(), S2_linear, Y2, &R2);
diff --git a/modules/audio_processing/aec3/shadow_filter_update_gain_unittest.cc b/modules/audio_processing/aec3/shadow_filter_update_gain_unittest.cc index 300f6b1..605f570 100644 --- a/modules/audio_processing/aec3/shadow_filter_update_gain_unittest.cc +++ b/modules/audio_processing/aec3/shadow_filter_update_gain_unittest.cc
@@ -64,7 +64,6 @@ std::vector<std::vector<float>>(num_render_channels, std::vector<float>(kBlockSize, 0.f))); std::array<float, kBlockSize> y; - AecState aec_state(config); RenderSignalAnalyzer render_signal_analyzer(config); std::array<float, kFftLength> s; FftData S;
diff --git a/modules/audio_processing/aec3/subtractor_output.h b/modules/audio_processing/aec3/subtractor_output.h index 5f6fd3e..2822b08 100644 --- a/modules/audio_processing/aec3/subtractor_output.h +++ b/modules/audio_processing/aec3/subtractor_output.h
@@ -19,7 +19,8 @@ namespace webrtc { -// Stores the values being returned from the echo subtractor. +// Stores the values being returned from the echo subtractor for a single +// capture channel. struct SubtractorOutput { SubtractorOutput(); ~SubtractorOutput();
diff --git a/modules/audio_processing/aec3/subtractor_unittest.cc b/modules/audio_processing/aec3/subtractor_unittest.cc index daacbd3..b5635f4 100644 --- a/modules/audio_processing/aec3/subtractor_unittest.cc +++ b/modules/audio_processing/aec3/subtractor_unittest.cc
@@ -55,7 +55,7 @@ std::array<float, kFftLengthBy2Plus1> Y2; std::array<float, kFftLengthBy2Plus1> E2_main; std::array<float, kFftLengthBy2Plus1> E2_shadow; - AecState aec_state(config); + AecState aec_state(config, kNumChannels); x_old.fill(0.f); Y2.fill(0.f); E2_main.fill(0.f); @@ -93,7 +93,7 @@ aec_state.Update(delay_estimate, subtractor.FilterFrequencyResponse(), subtractor.FilterImpulseResponse(), *render_delay_buffer->GetRenderBuffer(), E2_main, Y2, - output[0], y[0]); + output); } const float output_power = @@ -139,7 +139,7 @@ EXPECT_DEATH( subtractor.Process(*render_delay_buffer->GetRenderBuffer(), y, - render_signal_analyzer, AecState(config), output), + render_signal_analyzer, AecState(config, 1), output), ""); }
diff --git a/modules/audio_processing/aec3/suppression_gain_unittest.cc b/modules/audio_processing/aec3/suppression_gain_unittest.cc index cfd92be..490c7ec 100644 --- a/modules/audio_processing/aec3/suppression_gain_unittest.cc +++ b/modules/audio_processing/aec3/suppression_gain_unittest.cc
@@ -42,7 +42,7 @@ Y.im.fill(0.f); float high_bands_gain; - AecState aec_state(EchoCanceller3Config{}); + AecState aec_state(EchoCanceller3Config{}, 1); EXPECT_DEATH( SuppressionGain(EchoCanceller3Config{}, DetectOptimization(), 16000) .GetGain(E2, S2, R2, N2, @@ -71,13 +71,13 @@ std::array<float, kFftLengthBy2Plus1> R2; std::array<float, kFftLengthBy2Plus1> N2; std::array<float, kFftLengthBy2Plus1> g; - SubtractorOutput output; + std::vector<SubtractorOutput> output(kNumChannels); std::array<float, kBlockSize> y; std::vector<std::vector<std::vector<float>>> x( kNumBands, std::vector<std::vector<float>>( kNumChannels, std::vector<float>(kBlockSize, 0.f))); EchoCanceller3Config config; - AecState aec_state(config); + AecState aec_state(config, kNumChannels); ApmDataDumper data_dumper(42); Subtractor subtractor(config, 1, 1, &data_dumper, DetectOptimization()); std::unique_ptr<RenderDelayBuffer> render_delay_buffer( @@ -90,22 +90,22 @@ R2.fill(0.1f); S2.fill(0.1f); N2.fill(100.f); - output.Reset(); + for (auto& subtractor_output : output) { + subtractor_output.Reset(); + } y.fill(0.f); // Ensure that the gain is no longer forced to zero. for (int k = 0; k <= kNumBlocksPerSecond / 5 + 1; ++k) { aec_state.Update(delay_estimate, subtractor.FilterFrequencyResponse(), subtractor.FilterImpulseResponse(), - *render_delay_buffer->GetRenderBuffer(), E2, Y2, output, - y); + *render_delay_buffer->GetRenderBuffer(), E2, Y2, output); } for (int k = 0; k < 100; ++k) { aec_state.Update(delay_estimate, subtractor.FilterFrequencyResponse(), subtractor.FilterImpulseResponse(), - *render_delay_buffer->GetRenderBuffer(), E2, Y2, output, - y); + *render_delay_buffer->GetRenderBuffer(), E2, Y2, output); suppression_gain.GetGain(E2, S2, R2, N2, analyzer, aec_state, x, &high_bands_gain, &g); } @@ -122,8 +122,7 @@ for (int k = 0; k < 100; ++k) { aec_state.Update(delay_estimate, subtractor.FilterFrequencyResponse(), subtractor.FilterImpulseResponse(), - *render_delay_buffer->GetRenderBuffer(), E2, Y2, output, - y); + *render_delay_buffer->GetRenderBuffer(), E2, Y2, output); suppression_gain.GetGain(E2, S2, R2, N2, analyzer, aec_state, x, &high_bands_gain, &g); }