AEC3: Add transparency-related killswitches This CL adds a number of kill-switches to the AEC3 code to be used as safe fallbacks to increase AEC transparency. The changes have been shown to be bitexact for a test dataset. Bug: webrtc:11475,chromium:1066836 Change-Id: Ibebcbbfbbd958cb6fcc6993247e3030fa65b582c Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/172600 Reviewed-by: Sam Zackrisson <saza@webrtc.org> Commit-Queue: Per Åhgren <peah@webrtc.org> Cr-Commit-Position: refs/heads/master@{#30964}
diff --git a/modules/audio_processing/aec3/aec_state.cc b/modules/audio_processing/aec3/aec_state.cc index 3c2a403..e87f607 100644 --- a/modules/audio_processing/aec3/aec_state.cc +++ b/modules/audio_processing/aec3/aec_state.cc
@@ -22,6 +22,7 @@ #include "modules/audio_processing/logging/apm_data_dumper.h" #include "rtc_base/atomic_ops.h" #include "rtc_base/checks.h" +#include "system_wrappers/include/field_trial.h" namespace webrtc { namespace { @@ -29,6 +30,24 @@ constexpr size_t kBlocksSinceConvergencedFilterInit = 10000; constexpr size_t kBlocksSinceConsistentEstimateInit = 10000; +bool DeactivateTransparentMode() { + return field_trial::IsEnabled("WebRTC-Aec3TransparentModeKillSwitch"); +} + +bool DeactivateInitialStateResetAtEchoPathChange() { + return field_trial::IsEnabled( + "WebRTC-Aec3DeactivateInitialStateResetKillSwitch"); +} + +bool FullResetAtEchoPathChange() { + return !field_trial::IsEnabled("WebRTC-Aec3AecStateFullResetKillSwitch"); +} + +bool SubtractorAnalyzerResetAtEchoPathChange() { + return !field_trial::IsEnabled( + "WebRTC-Aec3AecStateSubtractorAnalyzerResetKillSwitch"); +} + void ComputeAvgRenderReverb( const SpectrumBuffer& spectrum_buffer, int delay_blocks, @@ -115,6 +134,12 @@ new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))), config_(config), num_capture_channels_(num_capture_channels), + transparent_mode_activated_(!DeactivateTransparentMode()), + deactivate_initial_state_reset_at_echo_path_change_( + DeactivateInitialStateResetAtEchoPathChange()), + full_reset_at_echo_path_change_(FullResetAtEchoPathChange()), + subtractor_analyzer_reset_at_echo_path_change_( + SubtractorAnalyzerResetAtEchoPathChange()), initial_state_(config_), delay_state_(config_, num_capture_channels_), transparent_state_(config_), @@ -136,7 +161,9 @@ capture_signal_saturation_ = false; strong_not_saturated_render_blocks_ = 0; blocks_with_active_render_ = 0; - initial_state_.Reset(); + if (!deactivate_initial_state_reset_at_echo_path_change_) { + initial_state_.Reset(); + } transparent_state_.Reset(); erle_estimator_.Reset(true); erl_estimator_.Reset(); @@ -146,13 +173,16 @@ // TODO(peah): Refine the reset scheme according to the type of gain and // delay adjustment. - if (echo_path_variability.delay_change != - EchoPathVariability::DelayAdjustment::kNone) { + if (full_reset_at_echo_path_change_ && + echo_path_variability.delay_change != + EchoPathVariability::DelayAdjustment::kNone) { full_reset(); } else if (echo_path_variability.gain_change) { erle_estimator_.Reset(false); } - subtractor_output_analyzer_.HandleEchoPathChange(); + if (subtractor_analyzer_reset_at_echo_path_change_) { + subtractor_output_analyzer_.HandleEchoPathChange(); + } } void AecState::Update( @@ -235,9 +265,13 @@ render_buffer.Spectrum(delay_state_.MinDirectPathFilterDelay()), Y2); // Detect and flag echo saturation. - saturation_detector_.Update(aligned_render_block, SaturatedCapture(), - UsableLinearEstimate(), subtractor_output, - max_echo_path_gain); + if (config_.ep_strength.echo_can_saturate) { + saturation_detector_.Update(aligned_render_block, SaturatedCapture(), + UsableLinearEstimate(), subtractor_output, + max_echo_path_gain); + } else { + RTC_DCHECK(!saturation_detector_.SaturatedEcho()); + } // Update the decision on whether to use the initial state parameter set. initial_state_.Update(active_render, SaturatedCapture());
diff --git a/modules/audio_processing/aec3/aec_state.h b/modules/audio_processing/aec3/aec_state.h index aadfde9..e79e64b 100644 --- a/modules/audio_processing/aec3/aec_state.h +++ b/modules/audio_processing/aec3/aec_state.h
@@ -107,7 +107,9 @@ } // Returns whether the transparent mode is active - bool TransparentMode() const { return transparent_state_.Active(); } + bool TransparentMode() const { + return transparent_mode_activated_ && transparent_state_.Active(); + } // Takes appropriate action at an echo path change. void HandleEchoPathChange(const EchoPathVariability& echo_path_variability); @@ -150,6 +152,10 @@ std::unique_ptr<ApmDataDumper> data_dumper_; const EchoCanceller3Config config_; const size_t num_capture_channels_; + const bool transparent_mode_activated_; + const bool deactivate_initial_state_reset_at_echo_path_change_; + const bool full_reset_at_echo_path_change_; + const bool subtractor_analyzer_reset_at_echo_path_change_; // Class for controlling the transition from the intial state, which in turn // controls when the filter parameters for the initial state should be used.
diff --git a/modules/audio_processing/aec3/echo_canceller3.cc b/modules/audio_processing/aec3/echo_canceller3.cc index bd1b82a..95cd22a 100644 --- a/modules/audio_processing/aec3/echo_canceller3.cc +++ b/modules/audio_processing/aec3/echo_canceller3.cc
@@ -47,6 +47,58 @@ adjusted_cfg.filter.enable_shadow_filter_output_usage; } + if (field_trial::IsEnabled("WebRTC-Aec3UseShortConfigChangeDuration")) { + adjusted_cfg.filter.config_change_duration_blocks = 10; + } + + if (field_trial::IsEnabled("WebRTC-Aec3UseZeroInitialStateDuration")) { + adjusted_cfg.filter.initial_state_seconds = 0.f; + } else if (field_trial::IsEnabled( + "WebRTC-Aec3UseDot1SecondsInitialStateDuration")) { + adjusted_cfg.filter.initial_state_seconds = .1f; + } else if (field_trial::IsEnabled( + "WebRTC-Aec3UseDot2SecondsInitialStateDuration")) { + adjusted_cfg.filter.initial_state_seconds = .2f; + } else if (field_trial::IsEnabled( + "WebRTC-Aec3UseDot3SecondsInitialStateDuration")) { + adjusted_cfg.filter.initial_state_seconds = .3f; + } else if (field_trial::IsEnabled( + "WebRTC-Aec3UseDot6SecondsInitialStateDuration")) { + adjusted_cfg.filter.initial_state_seconds = .6f; + } else if (field_trial::IsEnabled( + "WebRTC-Aec3UseDot9SecondsInitialStateDuration")) { + adjusted_cfg.filter.initial_state_seconds = .9f; + } else if (field_trial::IsEnabled( + "WebRTC-Aec3Use1Dot2SecondsInitialStateDuration")) { + adjusted_cfg.filter.initial_state_seconds = 1.2f; + } else if (field_trial::IsEnabled( + "WebRTC-Aec3Use1Dot6SecondsInitialStateDuration")) { + adjusted_cfg.filter.initial_state_seconds = 1.6f; + } else if (field_trial::IsEnabled( + "WebRTC-Aec3Use2Dot0SecondsInitialStateDuration")) { + adjusted_cfg.filter.initial_state_seconds = 2.0f; + } + + if (field_trial::IsEnabled("WebRTC-Aec3EchoSaturationDetectionKillSwitch")) { + adjusted_cfg.ep_strength.echo_can_saturate = false; + } + + if (field_trial::IsEnabled("WebRTC-Aec3UseDot2ReverbDefaultLen")) { + adjusted_cfg.ep_strength.default_len = 0.2f; + } else if (field_trial::IsEnabled("WebRTC-Aec3UseDot3ReverbDefaultLen")) { + adjusted_cfg.ep_strength.default_len = 0.3f; + } else if (field_trial::IsEnabled("WebRTC-Aec3UseDot4ReverbDefaultLen")) { + adjusted_cfg.ep_strength.default_len = 0.4f; + } else if (field_trial::IsEnabled("WebRTC-Aec3UseDot5ReverbDefaultLen")) { + adjusted_cfg.ep_strength.default_len = 0.5f; + } else if (field_trial::IsEnabled("WebRTC-Aec3UseDot6ReverbDefaultLen")) { + adjusted_cfg.ep_strength.default_len = 0.6f; + } else if (field_trial::IsEnabled("WebRTC-Aec3UseDot7ReverbDefaultLen")) { + adjusted_cfg.ep_strength.default_len = 0.7f; + } else if (field_trial::IsEnabled("WebRTC-Aec3UseDot8ReverbDefaultLen")) { + adjusted_cfg.ep_strength.default_len = 0.8f; + } + if (field_trial::IsEnabled("WebRTC-Aec3ShortHeadroomKillSwitch")) { // Two blocks headroom. adjusted_cfg.delay.delay_headroom_samples = kBlockSize * 2; @@ -60,6 +112,10 @@ adjusted_cfg.erle.clamp_quality_estimate_to_one = false; } + if (field_trial::IsEnabled("WebRTC-Aec3OnsetDetectionKillSwitch")) { + adjusted_cfg.erle.onset_detection = false; + } + if (field_trial::IsEnabled( "WebRTC-Aec3EnforceRenderDelayEstimationDownmixing")) { adjusted_cfg.delay.render_alignment_mixing.downmix = true; @@ -85,6 +141,65 @@ false; } + if (field_trial::IsEnabled("WebRTC-Aec3SensitiveDominantNearendActivation")) { + adjusted_cfg.suppressor.dominant_nearend_detection.enr_threshold = 0.5f; + } else if (field_trial::IsEnabled( + "WebRTC-Aec3VerySensitiveDominantNearendActivation")) { + adjusted_cfg.suppressor.dominant_nearend_detection.enr_threshold = 0.75f; + } + + if (field_trial::IsEnabled("WebRTC-Aec3TransparentAntiHowlingGain")) { + adjusted_cfg.suppressor.high_bands_suppression.anti_howling_gain = 1.f; + } + + if (field_trial::IsEnabled( + "WebRTC-Aec3EnforceMoreTransparentNormalSuppressorTuning")) { + adjusted_cfg.suppressor.normal_tuning.mask_lf.enr_transparent = 0.4f; + adjusted_cfg.suppressor.normal_tuning.mask_lf.enr_suppress = 0.5f; + } + + if (field_trial::IsEnabled( + "WebRTC-Aec3EnforceMoreTransparentNearendSuppressorTuning")) { + adjusted_cfg.suppressor.nearend_tuning.mask_lf.enr_transparent = 1.29f; + adjusted_cfg.suppressor.nearend_tuning.mask_lf.enr_suppress = 1.3f; + } + + if (field_trial::IsEnabled( + "WebRTC-Aec3EnforceRapidlyAdjustingNormalSuppressorTunings")) { + adjusted_cfg.suppressor.normal_tuning.max_inc_factor = 2.5f; + } + + if (field_trial::IsEnabled( + "WebRTC-Aec3EnforceRapidlyAdjustingNearendSuppressorTunings")) { + adjusted_cfg.suppressor.nearend_tuning.max_inc_factor = 2.5f; + } + + if (field_trial::IsEnabled( + "WebRTC-Aec3EnforceSlowlyAdjustingNormalSuppressorTunings")) { + adjusted_cfg.suppressor.normal_tuning.max_dec_factor_lf = .2f; + } + + if (field_trial::IsEnabled( + "WebRTC-Aec3EnforceSlowlyAdjustingNearendSuppressorTunings")) { + adjusted_cfg.suppressor.nearend_tuning.max_dec_factor_lf = .2f; + } + + if (field_trial::IsEnabled("WebRTC-Aec3EnforceStationarityProperties")) { + adjusted_cfg.echo_audibility.use_stationarity_properties = true; + } + + if (field_trial::IsEnabled( + "WebRTC-Aec3EnforceStationarityPropertiesAtInit")) { + adjusted_cfg.echo_audibility.use_stationarity_properties_at_init = true; + } + + if (field_trial::IsEnabled("WebRTC-Aec3EnforceLowActiveRenderLimit")) { + adjusted_cfg.render_levels.active_render_limit = 50.f; + } else if (field_trial::IsEnabled( + "WebRTC-Aec3EnforceVeryLowActiveRenderLimit")) { + adjusted_cfg.render_levels.active_render_limit = 30.f; + } + return adjusted_cfg; }
diff --git a/modules/audio_processing/aec3/residual_echo_estimator.cc b/modules/audio_processing/aec3/residual_echo_estimator.cc index 3846a79..5d31c66 100644 --- a/modules/audio_processing/aec3/residual_echo_estimator.cc +++ b/modules/audio_processing/aec3/residual_echo_estimator.cc
@@ -18,10 +18,67 @@ #include "api/array_view.h" #include "modules/audio_processing/aec3/reverb_model.h" #include "rtc_base/checks.h" +#include "system_wrappers/include/field_trial.h" namespace webrtc { namespace { +bool UseLowEarlyReflectionsTransparentModeGain() { + return field_trial::IsEnabled( + "WebRTC-Aec3UseLowEarlyReflectionsTransparentModeGain"); +} + +bool UseLowLateReflectionsTransparentModeGain() { + return field_trial::IsEnabled( + "WebRTC-Aec3UseLowLateReflectionsTransparentModeGain"); +} + +bool UseLowEarlyReflectionsDefaultGain() { + return field_trial::IsEnabled("WebRTC-Aec3UseLowEarlyReflectionsDefaultGain"); +} + +bool UseLowLateReflectionsDefaultGain() { + return field_trial::IsEnabled("WebRTC-Aec3UseLowLateReflectionsDefaultGain"); +} + +bool ModelReverbInNonlinearMode() { + return !field_trial::IsEnabled("WebRTC-Aec3rNonlinearModeReverbKillSwitch"); +} + +constexpr float kDefaultTransparentModeGain = 0.01f; + +float GetEarlyReflectionsTransparentModeGain() { + if (UseLowEarlyReflectionsTransparentModeGain()) { + return 0.001f; + } + return kDefaultTransparentModeGain; +} + +float GetLateReflectionsTransparentModeGain() { + if (UseLowLateReflectionsTransparentModeGain()) { + return 0.001f; + } + + return kDefaultTransparentModeGain; +} + +float GetEarlyReflectionsDefaultModeGain( + const EchoCanceller3Config::EpStrength& config) { + if (UseLowEarlyReflectionsDefaultGain()) { + return 0.1f; + } + + return config.default_gain; +} + +float GetLateReflectionsDefaultModeGain( + const EchoCanceller3Config::EpStrength& config) { + if (UseLowLateReflectionsDefaultGain()) { + return 0.1f; + } + return config.default_gain; +} + // Computes the indexes that will be used for computing spectral power over // the blocks surrounding the delay. void GetRenderIndexesToAnalyze( @@ -138,19 +195,21 @@ } } -// Chooses the echo path gain to use. -float GetEchoPathGain(const AecState& aec_state, - const EchoCanceller3Config::EpStrength& config) { - float gain_amplitude = - aec_state.TransparentMode() ? 0.01f : config.default_gain; - return gain_amplitude * gain_amplitude; -} - } // namespace ResidualEchoEstimator::ResidualEchoEstimator(const EchoCanceller3Config& config, size_t num_render_channels) - : config_(config), num_render_channels_(num_render_channels) { + : config_(config), + num_render_channels_(num_render_channels), + early_reflections_transparent_mode_gain_( + GetEarlyReflectionsTransparentModeGain()), + late_reflections_transparent_mode_gain_( + GetLateReflectionsTransparentModeGain()), + early_reflections_general_gain_( + GetEarlyReflectionsDefaultModeGain(config_.ep_strength)), + late_reflections_general_gain_( + GetLateReflectionsDefaultModeGain(config_.ep_strength)), + model_reverb_in_nonlinear_mode_(ModelReverbInNonlinearMode()) { Reset(); } @@ -190,7 +249,7 @@ AddReverb(ReverbType::kLinear, aec_state, render_buffer, R2); } else { const float echo_path_gain = - GetEchoPathGain(aec_state, config_.ep_strength); + GetEchoPathGain(aec_state, /*gain_for_early_reflections=*/true); // When there is saturated echo, assume the same spectral content as is // present in the microphone signal. @@ -218,7 +277,7 @@ NonLinearEstimate(echo_path_gain, X2, R2); } - if (!aec_state.TransparentMode()) { + if (model_reverb_in_nonlinear_mode_ && !aec_state.TransparentMode()) { AddReverb(ReverbType::kNonLinear, aec_state, render_buffer, R2); } } @@ -316,7 +375,7 @@ aec_state.ReverbDecay()); } else { const float echo_path_gain = - GetEchoPathGain(aec_state, config_.ep_strength); + GetEchoPathGain(aec_state, /*gain_for_early_reflections=*/false); echo_reverb_.UpdateReverbNoFreqShaping(render_power, echo_path_gain, aec_state.ReverbDecay()); } @@ -331,4 +390,21 @@ } } +// Chooses the echo path gain to use. +float ResidualEchoEstimator::GetEchoPathGain( + const AecState& aec_state, + bool gain_for_early_reflections) const { + float gain_amplitude; + if (aec_state.TransparentMode()) { + gain_amplitude = gain_for_early_reflections + ? early_reflections_transparent_mode_gain_ + : late_reflections_transparent_mode_gain_; + } else { + gain_amplitude = gain_for_early_reflections + ? early_reflections_general_gain_ + : late_reflections_general_gain_; + } + return gain_amplitude * gain_amplitude; +} + } // namespace webrtc
diff --git a/modules/audio_processing/aec3/residual_echo_estimator.h b/modules/audio_processing/aec3/residual_echo_estimator.h index 5c14bdb..081cc06 100644 --- a/modules/audio_processing/aec3/residual_echo_estimator.h +++ b/modules/audio_processing/aec3/residual_echo_estimator.h
@@ -58,8 +58,17 @@ const RenderBuffer& render_buffer, rtc::ArrayView<std::array<float, kFftLengthBy2Plus1>> R2); + // Gets the echo path gain to apply. + float GetEchoPathGain(const AecState& aec_state, + bool gain_for_early_reflections) const; + const EchoCanceller3Config config_; const size_t num_render_channels_; + const float early_reflections_transparent_mode_gain_; + const float late_reflections_transparent_mode_gain_; + const float early_reflections_general_gain_; + const float late_reflections_general_gain_; + const bool model_reverb_in_nonlinear_mode_; std::array<float, kFftLengthBy2Plus1> X2_noise_floor_; std::array<int, kFftLengthBy2Plus1> X2_noise_floor_counter_; ReverbModel echo_reverb_;