Audio: Enable dynamic on-the-fly AEC3 configuration updates for the suppressor gain computation

This CL refactors suppression gain to allow dynamic configuration changes without requiring reinitialization. This allows switching to a specific ML-REE configuration once the linear mode is used.

These changes are bitexact when not running the neural residual echo estimator. The refinement in MovingAverageSpectrum does not introduce  any change when the residual residual echo estimator is disabled. When running neural residual echo estimator, there is a reduction of initial echo blips for recordings that manifest such issue.

Bug: webrtc:442444736
No-Iwyu: Not changing the tflite includes.
Change-Id: If7c7bdb55ab5a95bb846ea75d45fe5b6387a2fea
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/456140
Reviewed-by: Per Åhgren <peah@webrtc.org>
Commit-Queue: Jesus de Vicente Pena <devicentepena@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#47303}
diff --git a/api/audio/neural_residual_echo_estimator.h b/api/audio/neural_residual_echo_estimator.h
index 93ea2fa..87e8149 100644
--- a/api/audio/neural_residual_echo_estimator.h
+++ b/api/audio/neural_residual_echo_estimator.h
@@ -57,6 +57,11 @@
   // Returns a recommended AEC3 configuration for this estimator.
   virtual EchoCanceller3Config GetConfiguration(bool multi_channel) const = 0;
 
+  // Adjusts the provided AEC3 suppressor configuration based on the estimator's
+  // requirements.
+  virtual EchoCanceller3Config::Suppressor AdjustConfig(
+      const EchoCanceller3Config::Suppressor& config) const = 0;
+
   // Resets the internal state of the estimator.
   virtual void Reset() = 0;
 };
diff --git a/modules/audio_processing/aec3/dominant_nearend_detector.cc b/modules/audio_processing/aec3/dominant_nearend_detector.cc
index 35aff93..d7e01fb 100644
--- a/modules/audio_processing/aec3/dominant_nearend_detector.cc
+++ b/modules/audio_processing/aec3/dominant_nearend_detector.cc
@@ -24,12 +24,7 @@
 DominantNearendDetector::DominantNearendDetector(
     const EchoCanceller3Config::Suppressor::DominantNearendDetection& config,
     size_t num_capture_channels)
-    : enr_threshold_(config.enr_threshold),
-      enr_exit_threshold_(config.enr_exit_threshold),
-      snr_threshold_(config.snr_threshold),
-      hold_duration_(config.hold_duration),
-      trigger_threshold_(config.trigger_threshold),
-      use_during_initial_phase_(config.use_during_initial_phase),
+    : config_(config),
       num_capture_channels_(num_capture_channels),
       trigger_counters_(num_capture_channels_),
       hold_counters_(num_capture_channels_) {}
@@ -55,13 +50,13 @@
 
     // Detect strong active nearend if the nearend is sufficiently stronger than
     // the echo and the nearend noise.
-    if ((!initial_state || use_during_initial_phase_) &&
-        echo_sum < enr_threshold_ * ne_sum &&
-        ne_sum > snr_threshold_ * noise_sum) {
-      if (++trigger_counters_[ch] >= trigger_threshold_) {
+    if ((!initial_state || config_.use_during_initial_phase) &&
+        echo_sum < config_.enr_threshold * ne_sum &&
+        ne_sum > config_.snr_threshold * noise_sum) {
+      if (++trigger_counters_[ch] >= config_.trigger_threshold) {
         // After a period of strong active nearend activity, flag nearend mode.
-        hold_counters_[ch] = hold_duration_;
-        trigger_counters_[ch] = trigger_threshold_;
+        hold_counters_[ch] = config_.hold_duration;
+        trigger_counters_[ch] = config_.trigger_threshold;
       }
     } else {
       // Forget previously detected strong active nearend activity.
@@ -69,8 +64,8 @@
     }
 
     // Exit nearend-state early at strong echo.
-    if (echo_sum > enr_exit_threshold_ * ne_sum &&
-        echo_sum > snr_threshold_ * noise_sum) {
+    if (echo_sum > config_.enr_exit_threshold * ne_sum &&
+        echo_sum > config_.snr_threshold * noise_sum) {
       hold_counters_[ch] = 0;
     }
 
@@ -79,4 +74,10 @@
     nearend_state_ = nearend_state_ || hold_counters_[ch] > 0;
   }
 }
+
+void DominantNearendDetector::SetConfig(
+    const EchoCanceller3Config::Suppressor& config) {
+  config_ = config.dominant_nearend_detection;
+}
+
 }  // namespace webrtc
diff --git a/modules/audio_processing/aec3/dominant_nearend_detector.h b/modules/audio_processing/aec3/dominant_nearend_detector.h
index cbab36e..877697c 100644
--- a/modules/audio_processing/aec3/dominant_nearend_detector.h
+++ b/modules/audio_processing/aec3/dominant_nearend_detector.h
@@ -40,13 +40,11 @@
           comfort_noise_spectrum,
       bool initial_state) override;
 
+  // Sets the configuration.
+  void SetConfig(const EchoCanceller3Config::Suppressor& config) override;
+
  private:
-  const float enr_threshold_;
-  const float enr_exit_threshold_;
-  const float snr_threshold_;
-  const int hold_duration_;
-  const int trigger_threshold_;
-  const bool use_during_initial_phase_;
+  EchoCanceller3Config::Suppressor::DominantNearendDetection config_;
   const size_t num_capture_channels_;
 
   bool nearend_state_ = false;
diff --git a/modules/audio_processing/aec3/echo_canceller3_unittest.cc b/modules/audio_processing/aec3/echo_canceller3_unittest.cc
index 69d71ad..a27b3e8 100644
--- a/modules/audio_processing/aec3/echo_canceller3_unittest.cc
+++ b/modules/audio_processing/aec3/echo_canceller3_unittest.cc
@@ -1195,6 +1195,11 @@
       return EchoCanceller3Config();
     }
 
+    EchoCanceller3Config::Suppressor AdjustConfig(
+        const EchoCanceller3Config::Suppressor& config) const override {
+      return config;
+    }
+
     MOCK_METHOD(void, Reset, (), (override));
 
    private:
diff --git a/modules/audio_processing/aec3/echo_remover.cc b/modules/audio_processing/aec3/echo_remover.cc
index 0d3e1d1..9919b6f 100644
--- a/modules/audio_processing/aec3/echo_remover.cc
+++ b/modules/audio_processing/aec3/echo_remover.cc
@@ -184,6 +184,8 @@
  private:
   static std::atomic<int> instance_count_;
   const EchoCanceller3Config config_;
+  const std::optional<EchoCanceller3Config::Suppressor>
+      ml_ree_suppressor_config_;
   const Aec3Fft fft_;
   std::unique_ptr<ApmDataDumper> data_dumper_;
   const Aec3Optimization optimization_;
@@ -206,6 +208,7 @@
   size_t block_counter_ = 0;
   int gain_change_hangover_ = 0;
   bool refined_filter_output_last_selected_ = true;
+  bool ml_ree_was_active_ = false;
 
   std::vector<std::array<float, kFftLengthBy2>> e_heap_;
   std::vector<std::array<float, kFftLengthBy2Plus1>> Y2_heap_;
@@ -230,6 +233,11 @@
     size_t num_capture_channels,
     NeuralResidualEchoEstimator* neural_residual_echo_estimator)
     : config_(config),
+      ml_ree_suppressor_config_(
+          neural_residual_echo_estimator
+              ? std::make_optional(neural_residual_echo_estimator->AdjustConfig(
+                    config.suppressor))
+              : std::nullopt),
       fft_(),
       data_dumper_(new ApmDataDumper(instance_count_.fetch_add(1) + 1)),
       optimization_(DetectOptimization()),
@@ -416,9 +424,11 @@
   subtractor_.Process(*render_buffer, *y, render_signal_analyzer_, aec_state_,
                       subtractor_output);
 
+  const bool ml_ree_is_active = residual_echo_estimator_.IsMlReeActive();
+
   // Compute spectra.
   bool use_refined_output;
-  if (use_coarse_filter_output_) {
+  if (use_coarse_filter_output_ && !ml_ree_is_active) {
     use_refined_output = false;
     for (size_t ch = 0; ch < num_capture_channels_; ++ch) {
       if (UseRefinedOutput(subtractor_output[ch])) {
@@ -499,9 +509,20 @@
     const bool clock_drift = config_.echo_removal_control.has_clock_drift ||
                              echo_path_variability.clock_drift;
 
+    // Select the active suppressor configuration.
+    const EchoCanceller3Config::Suppressor& active_suppressor_config =
+        (ml_ree_is_active && ml_ree_suppressor_config_.has_value())
+            ? *ml_ree_suppressor_config_
+            : config_.suppressor;
+
+    // Determine if the configuration affecting the suppressor has changed.
+    const bool config_changed = (ml_ree_is_active != ml_ree_was_active_);
+    ml_ree_was_active_ = ml_ree_is_active;
+
     // Compute preferred gains.
     float high_bands_gain;
-    suppression_gain_.GetGain(nearend_spectrum, echo_spectrum, R2, R2_unbounded,
+    suppression_gain_.GetGain(active_suppressor_config, config_changed,
+                              nearend_spectrum, echo_spectrum, R2, R2_unbounded,
                               cng_.NoiseSpectrum(), render_signal_analyzer_,
                               aec_state_, x, clock_drift, &high_bands_gain, &G);
 
diff --git a/modules/audio_processing/aec3/moving_average_spectrum.cc b/modules/audio_processing/aec3/moving_average_spectrum.cc
index 3884c4a..fb46918 100644
--- a/modules/audio_processing/aec3/moving_average_spectrum.cc
+++ b/modules/audio_processing/aec3/moving_average_spectrum.cc
@@ -23,9 +23,9 @@
 MovingAverageSpectrum::MovingAverageSpectrum(size_t num_elem, size_t mem_len)
     : num_elem_(num_elem),
       mem_len_(mem_len - 1),
-      scaling_(1.0f / static_cast<float>(mem_len)),
       memory_(num_elem * mem_len_, 0.f),
-      mem_index_(0) {
+      mem_index_(0),
+      number_updates_(0) {
   RTC_DCHECK(num_elem_ > 0);
   RTC_DCHECK(mem_len > 0);
 }
@@ -44,9 +44,10 @@
                    std::plus<float>());
   }
 
-  // Divide by mem_len_.
+  // Divide by the number of points used to compute the average.
+  const float scaling = 1.0f / static_cast<float>(number_updates_ + 1);
   for (float& o : output) {
-    o *= scaling_;
+    o *= scaling;
   }
 
   // Update memory.
@@ -55,6 +56,19 @@
               memory_.begin() + mem_index_ * num_elem_);
     mem_index_ = (mem_index_ + 1) % mem_len_;
   }
+  number_updates_ = std::min(static_cast<int>(mem_len_), number_updates_ + 1);
+}
+
+void MovingAverageSpectrum::UpdateMemoryLength(size_t mem_len) {
+  if (mem_len_ + 1 == mem_len) {
+    return;
+  }
+  RTC_DCHECK(mem_len > 0);
+  mem_len_ = mem_len - 1;
+  memory_.resize(num_elem_ * mem_len_);
+  std::fill(memory_.begin(), memory_.end(), 0.0f);
+  mem_index_ = 0;
+  number_updates_ = 0;
 }
 
 }  // namespace webrtc
diff --git a/modules/audio_processing/aec3/moving_average_spectrum.h b/modules/audio_processing/aec3/moving_average_spectrum.h
index b4a3763..e8a2909 100644
--- a/modules/audio_processing/aec3/moving_average_spectrum.h
+++ b/modules/audio_processing/aec3/moving_average_spectrum.h
@@ -25,16 +25,20 @@
   MovingAverageSpectrum(size_t num_elem, size_t mem_len);
   ~MovingAverageSpectrum();
 
-  // Computes the average of input and mem_len-1 previous inputs and stores the
-  // result in output.
+  // Computes the average of the current input and up to mem_len-1 previous
+  // inputs and stores the result in output.
   void Average(std::span<const float> input, std::span<float> output);
 
+  // If a new memory length is provided, resets the state and clears the
+  // average memory to use the new window size.
+  void UpdateMemoryLength(size_t mem_len);
+
  private:
   const size_t num_elem_;
-  const size_t mem_len_;
-  const float scaling_;
+  size_t mem_len_;
   std::vector<float> memory_;
   size_t mem_index_;
+  int number_updates_;
 };
 
 }  // namespace webrtc
diff --git a/modules/audio_processing/aec3/moving_average_spectrum_unittest.cc b/modules/audio_processing/aec3/moving_average_spectrum_unittest.cc
index 718069b..5dbfca2 100644
--- a/modules/audio_processing/aec3/moving_average_spectrum_unittest.cc
+++ b/modules/audio_processing/aec3/moving_average_spectrum_unittest.cc
@@ -29,16 +29,16 @@
   std::array<float, num_elem> output;
 
   ma.Average(data1, output);
-  EXPECT_NEAR(output[0], data1[0] / 3.0f, e);
-  EXPECT_NEAR(output[1], data1[1] / 3.0f, e);
-  EXPECT_NEAR(output[2], data1[2] / 3.0f, e);
-  EXPECT_NEAR(output[3], data1[3] / 3.0f, e);
+  EXPECT_NEAR(output[0], data1[0] / 1.0f, e);
+  EXPECT_NEAR(output[1], data1[1] / 1.0f, e);
+  EXPECT_NEAR(output[2], data1[2] / 1.0f, e);
+  EXPECT_NEAR(output[3], data1[3] / 1.0f, e);
 
   ma.Average(data2, output);
-  EXPECT_NEAR(output[0], (data1[0] + data2[0]) / 3.0f, e);
-  EXPECT_NEAR(output[1], (data1[1] + data2[1]) / 3.0f, e);
-  EXPECT_NEAR(output[2], (data1[2] + data2[2]) / 3.0f, e);
-  EXPECT_NEAR(output[3], (data1[3] + data2[3]) / 3.0f, e);
+  EXPECT_NEAR(output[0], (data1[0] + data2[0]) / 2.0f, e);
+  EXPECT_NEAR(output[1], (data1[1] + data2[1]) / 2.0f, e);
+  EXPECT_NEAR(output[2], (data1[2] + data2[2]) / 2.0f, e);
+  EXPECT_NEAR(output[3], (data1[3] + data2[3]) / 2.0f, e);
 
   ma.Average(data3, output);
   EXPECT_NEAR(output[0], (data1[0] + data2[0] + data3[0]) / 3.0f, e);
@@ -53,6 +53,26 @@
   EXPECT_NEAR(output[3], (data2[3] + data3[3] + data4[3]) / 3.0f, e);
 }
 
+TEST(MovingAverageSpectrum, UpdateMemoryLength) {
+  constexpr size_t num_elem = 4;
+  constexpr float e = 1e-6f;
+  MovingAverageSpectrum ma(num_elem, 3);
+  std::array<float, num_elem> data1 = {1, 2, 3, 4};
+  std::array<float, num_elem> data2 = {5, 1, 9, 7};
+  std::array<float, num_elem> output;
+
+  ma.Average(data1, output);
+  EXPECT_NEAR(output[0], data1[0], e);
+
+  ma.UpdateMemoryLength(1);
+  ma.Average(data2, output);
+  // After update, it should behave as if it was just created with mem_len = 1.
+  EXPECT_NEAR(output[0], data2[0], e);
+  EXPECT_NEAR(output[1], data2[1], e);
+  EXPECT_NEAR(output[2], data2[2], e);
+  EXPECT_NEAR(output[3], data2[3], e);
+}
+
 TEST(MovingAverageSpectrum, PassThrough) {
   constexpr size_t num_elem = 4;
   constexpr size_t mem_len = 1;
diff --git a/modules/audio_processing/aec3/nearend_detector.h b/modules/audio_processing/aec3/nearend_detector.h
index c95c19e..ff30f42 100644
--- a/modules/audio_processing/aec3/nearend_detector.h
+++ b/modules/audio_processing/aec3/nearend_detector.h
@@ -14,6 +14,7 @@
 #include <array>
 #include <span>
 
+#include "api/audio/echo_canceller3_config.h"
 #include "modules/audio_processing/aec3/aec3_common.h"
 
 namespace webrtc {
@@ -33,6 +34,9 @@
       std::span<const std::array<float, kFftLengthBy2Plus1>>
           comfort_noise_spectrum,
       bool initial_state) = 0;
+
+  // Sets the configuration.
+  virtual void SetConfig(const EchoCanceller3Config::Suppressor& config) = 0;
 };
 
 }  // namespace webrtc
diff --git a/modules/audio_processing/aec3/neural_residual_echo_estimator/neural_residual_echo_estimator_impl.cc b/modules/audio_processing/aec3/neural_residual_echo_estimator/neural_residual_echo_estimator_impl.cc
index 0c80ef1..a18f1e5 100644
--- a/modules/audio_processing/aec3/neural_residual_echo_estimator/neural_residual_echo_estimator_impl.cc
+++ b/modules/audio_processing/aec3/neural_residual_echo_estimator/neural_residual_echo_estimator_impl.cc
@@ -569,6 +569,15 @@
 EchoCanceller3Config NeuralResidualEchoEstimatorImpl::GetConfiguration(
     bool multi_channel) const {
   EchoCanceller3Config config;
+  config.suppressor = AdjustConfig(config.suppressor);
+  config.filter.enable_coarse_filter_output_usage = false;
+  return config;
+}
+
+EchoCanceller3Config::Suppressor NeuralResidualEchoEstimatorImpl::AdjustConfig(
+    const EchoCanceller3Config::Suppressor& suppressor_config) const {
+  EchoCanceller3Config::Suppressor adjusted_suppressor_config =
+      suppressor_config;
   EchoCanceller3Config::Suppressor::MaskingThresholds tuning_masking_thresholds(
       /*enr_transparent=*/0.0f, /*enr_suppress=*/1.0f,
       /*emr_transparent=*/0.3f);
@@ -576,15 +585,15 @@
       /*mask_lf=*/tuning_masking_thresholds,
       /*mask_hf=*/tuning_masking_thresholds, /*max_inc_factor=*/100.0f,
       /*max_dec_factor_lf=*/0.0f);
-  config.filter.enable_coarse_filter_output_usage = false;
-  config.suppressor.nearend_average_blocks = 1;
-  config.suppressor.normal_tuning = tuning;
-  config.suppressor.nearend_tuning = tuning;
-  config.suppressor.dominant_nearend_detection.enr_threshold = 0.5f;
-  config.suppressor.dominant_nearend_detection.trigger_threshold = 2;
-  config.suppressor.high_frequency_suppression.limiting_gain_band = 24;
-  config.suppressor.high_frequency_suppression.bands_in_limiting_gain = 3;
-  return config;
+  adjusted_suppressor_config.nearend_average_blocks = 1;
+  adjusted_suppressor_config.normal_tuning = tuning;
+  adjusted_suppressor_config.nearend_tuning = tuning;
+  adjusted_suppressor_config.dominant_nearend_detection.enr_threshold = 0.5f;
+  adjusted_suppressor_config.dominant_nearend_detection.trigger_threshold = 2;
+  adjusted_suppressor_config.high_frequency_suppression.limiting_gain_band = 24;
+  adjusted_suppressor_config.high_frequency_suppression.bands_in_limiting_gain =
+      3;
+  return adjusted_suppressor_config;
 }
 
 void NeuralResidualEchoEstimatorImpl::DumpInputs(
diff --git a/modules/audio_processing/aec3/neural_residual_echo_estimator/neural_residual_echo_estimator_impl.h b/modules/audio_processing/aec3/neural_residual_echo_estimator/neural_residual_echo_estimator_impl.h
index 2c2f091..f92358e 100644
--- a/modules/audio_processing/aec3/neural_residual_echo_estimator/neural_residual_echo_estimator_impl.h
+++ b/modules/audio_processing/aec3/neural_residual_echo_estimator/neural_residual_echo_estimator_impl.h
@@ -81,6 +81,8 @@
       std::span<std::array<float, kFftLengthBy2Plus1>> R2_unbounded) override;
 
   EchoCanceller3Config GetConfiguration(bool multi_channel) const override;
+  EchoCanceller3Config::Suppressor AdjustConfig(
+      const EchoCanceller3Config::Suppressor& config) const override;
 
   void Reset() override;
 
diff --git a/modules/audio_processing/aec3/residual_echo_estimator.cc b/modules/audio_processing/aec3/residual_echo_estimator.cc
index 65793d4..fd821cc 100644
--- a/modules/audio_processing/aec3/residual_echo_estimator.cc
+++ b/modules/audio_processing/aec3/residual_echo_estimator.cc
@@ -206,6 +206,8 @@
 
   const size_t num_capture_channels = R2.size();
 
+  is_ml_ree_active_ = neural_residual_echo_estimator_ != nullptr &&
+                      aec_state.UsableLinearEstimate();
   // Estimate the power of the stationary noise in the render signal.
   UpdateRenderNoisePower(render_buffer);
 
@@ -235,9 +237,9 @@
         R2, R2_unbounded);
   }
 
-  // Estimate the residual echo power.
-  if (aec_state.UsableLinearEstimate()) {
-    if (neural_residual_echo_estimator_ == nullptr) {
+  // Estimate the residual echo power, used when ml_ree is not active.
+  if (!is_ml_ree_active_) {
+    if (aec_state.UsableLinearEstimate()) {
       // When there is saturated echo, assume the same spectral content as is
       // present in the microphone signal.
       if (aec_state.SaturatedEcho()) {
@@ -256,48 +258,48 @@
                    dominant_nearend);
       AddReverb(R2);
       AddReverb(R2_unbounded);
-    }
-  } else {
-    const float echo_path_gain =
-        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.
-    if (aec_state.SaturatedEcho()) {
-      for (size_t ch = 0; ch < num_capture_channels; ++ch) {
-        std::copy(Y2[ch].begin(), Y2[ch].end(), R2[ch].begin());
-        std::copy(Y2[ch].begin(), Y2[ch].end(), R2_unbounded[ch].begin());
-      }
     } else {
-      // Estimate the echo generating signal power.
-      std::array<float, kFftLengthBy2Plus1> X2;
-      EchoGeneratingPower(num_render_channels_,
-                          render_buffer.GetSpectrumBuffer(), config_.echo_model,
-                          aec_state.MinDirectPathFilterDelay(), X2);
-      if (!aec_state.UseStationarityProperties()) {
-        ApplyNoiseGate(config_.echo_model, X2);
+      const float echo_path_gain =
+          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.
+      if (aec_state.SaturatedEcho()) {
+        for (size_t ch = 0; ch < num_capture_channels; ++ch) {
+          std::copy(Y2[ch].begin(), Y2[ch].end(), R2[ch].begin());
+          std::copy(Y2[ch].begin(), Y2[ch].end(), R2_unbounded[ch].begin());
+        }
+      } else {
+        // Estimate the echo generating signal power.
+        std::array<float, kFftLengthBy2Plus1> X2;
+        EchoGeneratingPower(
+            num_render_channels_, render_buffer.GetSpectrumBuffer(),
+            config_.echo_model, aec_state.MinDirectPathFilterDelay(), X2);
+        if (!aec_state.UseStationarityProperties()) {
+          ApplyNoiseGate(config_.echo_model, X2);
+        }
+
+        // Subtract the stationary noise power to avoid stationary noise causing
+        // excessive echo suppression.
+        for (size_t k = 0; k < kFftLengthBy2Plus1; ++k) {
+          X2[k] -=
+              config_.echo_model.stationary_gate_slope * X2_noise_floor_[k];
+          X2[k] = std::max(0.f, X2[k]);
+        }
+
+        NonLinearEstimate(echo_path_gain, X2, R2);
+        NonLinearEstimate(echo_path_gain, X2, R2_unbounded);
       }
 
-      // Subtract the stationary noise power to avoid stationary noise causing
-      // excessive echo suppression.
-      for (size_t k = 0; k < kFftLengthBy2Plus1; ++k) {
-        X2[k] -= config_.echo_model.stationary_gate_slope * X2_noise_floor_[k];
-        X2[k] = std::max(0.f, X2[k]);
+      if (config_.echo_model.model_reverb_in_nonlinear_mode &&
+          !aec_state.TransparentModeActive()) {
+        UpdateReverb(ReverbType::kNonLinear, aec_state, render_buffer,
+                     dominant_nearend);
+        AddReverb(R2);
+        AddReverb(R2_unbounded);
       }
-
-      NonLinearEstimate(echo_path_gain, X2, R2);
-      NonLinearEstimate(echo_path_gain, X2, R2_unbounded);
-    }
-
-    if (config_.echo_model.model_reverb_in_nonlinear_mode &&
-        !aec_state.TransparentModeActive()) {
-      UpdateReverb(ReverbType::kNonLinear, aec_state, render_buffer,
-                   dominant_nearend);
-      AddReverb(R2);
-      AddReverb(R2_unbounded);
     }
   }
-
   if (aec_state.UseStationarityProperties()) {
     // Scale the echo according to echo audibility.
     std::array<float, kFftLengthBy2Plus1> residual_scaling;
diff --git a/modules/audio_processing/aec3/residual_echo_estimator.h b/modules/audio_processing/aec3/residual_echo_estimator.h
index fe1a4f8..0a53e0d 100644
--- a/modules/audio_processing/aec3/residual_echo_estimator.h
+++ b/modules/audio_processing/aec3/residual_echo_estimator.h
@@ -49,6 +49,9 @@
       std::span<std::array<float, kFftLengthBy2Plus1>> R2,
       std::span<std::array<float, kFftLengthBy2Plus1>> R2_unbounded);
 
+  // Returns true if ML-REE is active.
+  bool IsMlReeActive() const { return is_ml_ree_active_; }
+
  private:
   enum class ReverbType { kLinear, kNonLinear };
 
@@ -84,6 +87,7 @@
   std::array<int, kFftLengthBy2Plus1> X2_noise_floor_counter_;
   ReverbModel echo_reverb_;
   NeuralResidualEchoEstimator* neural_residual_echo_estimator_;
+  bool is_ml_ree_active_ = false;
 };
 
 }  // namespace webrtc
diff --git a/modules/audio_processing/aec3/subband_nearend_detector.cc b/modules/audio_processing/aec3/subband_nearend_detector.cc
index 39908ac..8cd85e4 100644
--- a/modules/audio_processing/aec3/subband_nearend_detector.cc
+++ b/modules/audio_processing/aec3/subband_nearend_detector.cc
@@ -73,4 +73,17 @@
          (nearend_power_subband1 > config_.snr_threshold * noise_power));
   }
 }
+
+void SubbandNearendDetector::SetConfig(
+    const EchoCanceller3Config::Suppressor& config) {
+  config_ = config.subband_nearend_detection;
+  one_over_subband_length1_ =
+      1.f / (config_.subband1.high - config_.subband1.low + 1);
+  one_over_subband_length2_ =
+      1.f / (config_.subband2.high - config_.subband2.low + 1);
+  for (auto& smoother : nearend_smoothers_) {
+    smoother.UpdateMemoryLength(config_.nearend_average_blocks);
+  }
+}
+
 }  // namespace webrtc
diff --git a/modules/audio_processing/aec3/subband_nearend_detector.h b/modules/audio_processing/aec3/subband_nearend_detector.h
index 5b43331..e28391f 100644
--- a/modules/audio_processing/aec3/subband_nearend_detector.h
+++ b/modules/audio_processing/aec3/subband_nearend_detector.h
@@ -41,12 +41,15 @@
           comfort_noise_spectrum,
       bool initial_state) override;
 
+  // Sets the configuration.
+  void SetConfig(const EchoCanceller3Config::Suppressor& config) override;
+
  private:
-  const EchoCanceller3Config::Suppressor::SubbandNearendDetection config_;
+  EchoCanceller3Config::Suppressor::SubbandNearendDetection config_;
   const size_t num_capture_channels_;
   std::vector<MovingAverageSpectrum> nearend_smoothers_;
-  const float one_over_subband_length1_;
-  const float one_over_subband_length2_;
+  float one_over_subband_length1_;
+  float one_over_subband_length2_;
   bool nearend_state_ = false;
 };
 
diff --git a/modules/audio_processing/aec3/suppression_gain.cc b/modules/audio_processing/aec3/suppression_gain.cc
index aab8a32..c5ca8ff 100644
--- a/modules/audio_processing/aec3/suppression_gain.cc
+++ b/modules/audio_processing/aec3/suppression_gain.cc
@@ -41,14 +41,16 @@
   (*gain)[0] = (*gain)[1] = std::min((*gain)[1], (*gain)[2]);
 }
 
-void LimitHighFrequencyGains(const EchoCanceller3Config::Suppressor& config,
-                             std::array<float, kFftLengthBy2Plus1>* gain) {
+void LimitHighFrequencyGains(
+    const EchoCanceller3Config::Suppressor::HighFrequencySuppression&
+        high_frequency_suppression,
+    bool conservative_hf_suppression,
+    std::array<float, kFftLengthBy2Plus1>* gain) {
   // Limit the high frequency gains to avoid echo leakage due to an imperfect
   // filter.
-  const int limiting_gain_band =
-      config.high_frequency_suppression.limiting_gain_band;
+  const int limiting_gain_band = high_frequency_suppression.limiting_gain_band;
   const int bands_in_limiting_gain =
-      config.high_frequency_suppression.bands_in_limiting_gain;
+      high_frequency_suppression.bands_in_limiting_gain;
   if (bands_in_limiting_gain > 0) {
     RTC_DCHECK_GE(limiting_gain_band, 0);
     RTC_DCHECK_LE(limiting_gain_band + bands_in_limiting_gain, gain->size());
@@ -63,7 +65,7 @@
   }
   (*gain)[kFftLengthBy2] = (*gain)[kFftLengthBy2Minus1];
 
-  if (config.conservative_hf_suppression) {
+  if (conservative_hf_suppression) {
     // Limits the gain in the frequencies for which the adaptive filter has not
     // converged.
     // TODO(peah): Make adaptive to take the actual filter error into account.
@@ -83,9 +85,10 @@
 }
 
 // Scales the echo according to assessed audibility at the other end.
-void WeightEchoForAudibility(const EchoCanceller3Config& config,
-                             std::span<const float> echo,
-                             std::span<float> weighted_echo) {
+void WeightEchoForAudibility(
+    const EchoCanceller3Config::EchoAudibility& echo_audibility,
+    std::span<const float> echo,
+    std::span<float> weighted_echo) {
   RTC_DCHECK_EQ(kFftLengthBy2Plus1, echo.size());
   RTC_DCHECK_EQ(kFftLengthBy2Plus1, weighted_echo.size());
 
@@ -101,19 +104,19 @@
     }
   };
 
-  float threshold = config.echo_audibility.floor_power *
-                    config.echo_audibility.audibility_threshold_lf;
-  float normalizer = 1.f / (threshold - config.echo_audibility.floor_power);
+  float threshold =
+      echo_audibility.floor_power * echo_audibility.audibility_threshold_lf;
+  float normalizer = 1.f / (threshold - echo_audibility.floor_power);
   weigh(threshold, normalizer, 0, 3, echo, weighted_echo);
 
-  threshold = config.echo_audibility.floor_power *
-              config.echo_audibility.audibility_threshold_mf;
-  normalizer = 1.f / (threshold - config.echo_audibility.floor_power);
+  threshold =
+      echo_audibility.floor_power * echo_audibility.audibility_threshold_mf;
+  normalizer = 1.f / (threshold - echo_audibility.floor_power);
   weigh(threshold, normalizer, 3, 7, echo, weighted_echo);
 
-  threshold = config.echo_audibility.floor_power *
-              config.echo_audibility.audibility_threshold_hf;
-  normalizer = 1.f / (threshold - config.echo_audibility.floor_power);
+  threshold =
+      echo_audibility.floor_power * echo_audibility.audibility_threshold_hf;
+  normalizer = 1.f / (threshold - echo_audibility.floor_power);
   weigh(threshold, normalizer, 7, kFftLengthBy2Plus1, echo, weighted_echo);
 }
 
@@ -122,6 +125,7 @@
 std::atomic<int> SuppressionGain::instance_count_(0);
 
 float SuppressionGain::UpperBandsGain(
+    const EchoCanceller3Config::Suppressor& suppressor_config,
     std::span<const std::array<float, kFftLengthBy2Plus1>> echo_spectrum,
     std::span<const std::array<float, kFftLengthBy2Plus1>>
         comfort_noise_spectrum,
@@ -172,7 +176,7 @@
   // upper bands.
   float anti_howling_gain;
   const float activation_threshold =
-      kBlockSize * config_.suppressor.high_bands_suppression
+      kBlockSize * suppressor_config.high_bands_suppression
                        .anti_howling_activation_threshold;
   if (high_band_energy < std::max(low_band_energy, activation_threshold)) {
     anti_howling_gain = 1.f;
@@ -181,14 +185,14 @@
     RTC_DCHECK_LE(low_band_energy, high_band_energy);
     RTC_DCHECK_NE(0.f, high_band_energy);
     anti_howling_gain =
-        config_.suppressor.high_bands_suppression.anti_howling_gain *
+        suppressor_config.high_bands_suppression.anti_howling_gain *
         sqrtf(low_band_energy / high_band_energy);
   }
 
   float gain_bound = 1.f;
   if (!dominant_nearend_detector_->IsNearendState()) {
     // Bound the upper gain during significant echo activity.
-    const auto& cfg = config_.suppressor.high_bands_suppression;
+    const auto& cfg = suppressor_config.high_bands_suppression;
     auto low_frequency_energy = [](std::span<const float> spectrum) {
       RTC_DCHECK_LE(16, spectrum.size());
       return std::accumulate(spectrum.begin() + 1, spectrum.begin() + 16, 0.f);
@@ -230,16 +234,18 @@
 
 // Compute the minimum gain as the attenuating gain to put the signal just
 // above the zero sample values.
-void SuppressionGain::GetMinGain(std::span<const float> weighted_residual_echo,
-                                 std::span<const float> last_nearend,
-                                 std::span<const float> last_echo,
-                                 bool low_noise_render,
-                                 bool saturated_echo,
-                                 std::span<float> min_gain) const {
+void SuppressionGain::GetMinGain(
+    const EchoCanceller3Config::Suppressor& suppressor_config,
+    std::span<const float> weighted_residual_echo,
+    std::span<const float> last_nearend,
+    std::span<const float> last_echo,
+    bool low_noise_render,
+    bool saturated_echo,
+    std::span<float> min_gain) const {
   if (!saturated_echo) {
     const float min_echo_power =
-        low_noise_render ? config_.echo_audibility.low_render_limit
-                         : config_.echo_audibility.normal_render_limit;
+        low_noise_render ? echo_audibility_config_.low_render_limit
+                         : echo_audibility_config_.normal_render_limit;
 
     for (size_t k = 0; k < min_gain.size(); ++k) {
       min_gain[k] = weighted_residual_echo[k] > 0.f
@@ -249,16 +255,16 @@
     }
 
     if (!initial_state_ ||
-        config_.suppressor.lf_smoothing_during_initial_phase) {
+        suppressor_config.lf_smoothing_during_initial_phase) {
       const float& dec = dominant_nearend_detector_->IsNearendState()
                              ? nearend_params_.max_dec_factor_lf
                              : normal_params_.max_dec_factor_lf;
 
-      for (int k = 0; k <= config_.suppressor.last_lf_smoothing_band; ++k) {
+      for (int k = 0; k <= suppressor_config.last_lf_smoothing_band; ++k) {
         // Make sure the gains of the low frequencies do not decrease too
         // quickly after strong nearend.
         if (last_nearend[k] > last_echo[k] ||
-            k <= config_.suppressor.last_permanent_lf_smoothing_band) {
+            k <= suppressor_config.last_permanent_lf_smoothing_band) {
           min_gain[k] = std::max(min_gain[k], last_gain_[k] * dec);
           min_gain[k] = std::min(min_gain[k], 1.f);
         }
@@ -271,17 +277,19 @@
 
 // Compute the maximum gain by limiting the gain increase from the previous
 // gain.
-void SuppressionGain::GetMaxGain(std::span<float> max_gain) const {
+void SuppressionGain::GetMaxGain(float floor_first_increase,
+                                 std::span<float> max_gain) const {
   const auto& inc = dominant_nearend_detector_->IsNearendState()
                         ? nearend_params_.max_inc_factor
                         : normal_params_.max_inc_factor;
-  const auto& floor = config_.suppressor.floor_first_increase;
   for (size_t k = 0; k < max_gain.size(); ++k) {
-    max_gain[k] = std::min(std::max(last_gain_[k] * inc, floor), 1.f);
+    max_gain[k] =
+        std::min(std::max(last_gain_[k] * inc, floor_first_increase), 1.f);
   }
 }
 
 void SuppressionGain::LowerBandGain(
+    const EchoCanceller3Config::Suppressor& suppressor_config,
     bool low_noise_render,
     const AecState& aec_state,
     std::span<const std::array<float, kFftLengthBy2Plus1>> suppressor_input,
@@ -292,7 +300,7 @@
   gain->fill(1.f);
   const bool saturated_echo = aec_state.SaturatedEcho();
   std::array<float, kFftLengthBy2Plus1> max_gain;
-  GetMaxGain(max_gain);
+  GetMaxGain(suppressor_config.floor_first_increase, max_gain);
 
   for (size_t ch = 0; ch < num_capture_channels_; ++ch) {
     std::array<float, kFftLengthBy2Plus1> G;
@@ -301,11 +309,12 @@
 
     // Weight echo power in terms of audibility.
     std::array<float, kFftLengthBy2Plus1> weighted_residual_echo;
-    WeightEchoForAudibility(config_, residual_echo[ch], weighted_residual_echo);
+    WeightEchoForAudibility(echo_audibility_config_, residual_echo[ch],
+                            weighted_residual_echo);
 
     std::array<float, kFftLengthBy2Plus1> min_gain;
-    GetMinGain(weighted_residual_echo, last_nearend_[ch], last_echo_[ch],
-               low_noise_render, saturated_echo, min_gain);
+    GetMinGain(suppressor_config, weighted_residual_echo, last_nearend_[ch],
+               last_echo_[ch], low_noise_render, saturated_echo, min_gain);
 
     GainToNoAudibleEcho(nearend, weighted_residual_echo, comfort_noise[0], &G);
 
@@ -325,8 +334,10 @@
   // Use conservative high-frequency gains during clock-drift or when not in
   // dominant nearend.
   if (!dominant_nearend_detector_->IsNearendState() || clock_drift ||
-      config_.suppressor.conservative_hf_suppression) {
-    LimitHighFrequencyGains(config_.suppressor, gain);
+      suppressor_config.conservative_hf_suppression) {
+    LimitHighFrequencyGains(suppressor_config.high_frequency_suppression,
+                            suppressor_config.conservative_hf_suppression,
+                            gain);
   }
 
   // Store computed gains.
@@ -342,32 +353,29 @@
                                  size_t num_capture_channels)
     : data_dumper_(new ApmDataDumper(instance_count_.fetch_add(1) + 1)),
       optimization_(optimization),
-      config_(config),
       num_capture_channels_(num_capture_channels),
-      state_change_duration_blocks_(
-          static_cast<int>(config_.filter.config_change_duration_blocks)),
+      echo_audibility_config_(config.echo_audibility),
+      use_subband_nearend_detection_(
+          config.suppressor.use_subband_nearend_detection),
       last_nearend_(num_capture_channels_, {0}),
       last_echo_(num_capture_channels_, {0}),
       nearend_smoothers_(
           num_capture_channels_,
           MovingAverageSpectrum(kFftLengthBy2Plus1,
                                 config.suppressor.nearend_average_blocks)),
-      nearend_params_(config_.suppressor.last_lf_band,
-                      config_.suppressor.first_hf_band,
-                      config_.suppressor.nearend_tuning),
-      normal_params_(config_.suppressor.last_lf_band,
-                     config_.suppressor.first_hf_band,
-                     config_.suppressor.normal_tuning),
-      use_unbounded_echo_spectrum_(config.suppressor.dominant_nearend_detection
-                                       .use_unbounded_echo_spectrum) {
-  RTC_DCHECK_LT(0, state_change_duration_blocks_);
+      nearend_params_(config.suppressor.last_lf_band,
+                      config.suppressor.first_hf_band,
+                      config.suppressor.nearend_tuning),
+      normal_params_(config.suppressor.last_lf_band,
+                     config.suppressor.first_hf_band,
+                     config.suppressor.normal_tuning) {
   last_gain_.fill(1.f);
-  if (config_.suppressor.use_subband_nearend_detection) {
+  if (config.suppressor.use_subband_nearend_detection) {
     dominant_nearend_detector_ = std::make_unique<SubbandNearendDetector>(
-        config_.suppressor.subband_nearend_detection, num_capture_channels_);
+        config.suppressor.subband_nearend_detection, num_capture_channels_);
   } else {
     dominant_nearend_detector_ = std::make_unique<DominantNearendDetector>(
-        config_.suppressor.dominant_nearend_detection, num_capture_channels_);
+        config.suppressor.dominant_nearend_detection, num_capture_channels_);
   }
   RTC_DCHECK(dominant_nearend_detector_);
 }
@@ -375,6 +383,8 @@
 SuppressionGain::~SuppressionGain() = default;
 
 void SuppressionGain::GetGain(
+    const EchoCanceller3Config::Suppressor& suppressor_config,
+    bool config_changed,
     std::span<const std::array<float, kFftLengthBy2Plus1>> nearend_spectrum,
     std::span<const std::array<float, kFftLengthBy2Plus1>> echo_spectrum,
     std::span<const std::array<float, kFftLengthBy2Plus1>>
@@ -392,10 +402,15 @@
   RTC_DCHECK(high_bands_gain);
   RTC_DCHECK(low_band_gain);
 
+  if (config_changed) {
+    UpdateStateDependingOnConfig(suppressor_config);
+  }
+
   // Choose residual echo spectrum for dominant nearend detection.
-  const auto echo = use_unbounded_echo_spectrum_
-                        ? residual_echo_spectrum_unbounded
-                        : residual_echo_spectrum;
+  const auto echo =
+      suppressor_config.dominant_nearend_detection.use_unbounded_echo_spectrum
+          ? residual_echo_spectrum_unbounded
+          : residual_echo_spectrum;
 
   // Update the nearend state selection.
   dominant_nearend_detector_->Update(nearend_spectrum, echo,
@@ -403,17 +418,17 @@
 
   // Compute gain for the lower band.
   bool low_noise_render = low_render_detector_.Detect(render);
-  LowerBandGain(low_noise_render, aec_state, nearend_spectrum,
-                residual_echo_spectrum, comfort_noise_spectrum, clock_drift,
-                low_band_gain);
+  LowerBandGain(suppressor_config, low_noise_render, aec_state,
+                nearend_spectrum, residual_echo_spectrum,
+                comfort_noise_spectrum, clock_drift, low_band_gain);
 
   // Compute the gain for the upper bands.
   const std::optional<int> narrow_peak_band =
       render_signal_analyzer.NarrowPeakBand();
 
-  *high_bands_gain =
-      UpperBandsGain(echo_spectrum, comfort_noise_spectrum, narrow_peak_band,
-                     aec_state.SaturatedEcho(), render, *low_band_gain);
+  *high_bands_gain = UpperBandsGain(
+      suppressor_config, echo_spectrum, comfort_noise_spectrum,
+      narrow_peak_band, aec_state.SaturatedEcho(), render, *low_band_gain);
 
   data_dumper_->DumpRaw("aec3_dominant_nearend",
                         dominant_nearend_detector_->IsNearendState());
@@ -421,11 +436,24 @@
 
 void SuppressionGain::SetInitialState(bool state) {
   initial_state_ = state;
-  if (state) {
-    initial_state_change_counter_ = state_change_duration_blocks_;
-  } else {
-    initial_state_change_counter_ = 0;
+}
+
+void SuppressionGain::UpdateStateDependingOnConfig(
+    const EchoCanceller3Config::Suppressor& suppressor_config) {
+  RTC_DCHECK_EQ(suppressor_config.use_subband_nearend_detection,
+                use_subband_nearend_detection_);
+  // Update nearend average blocks.
+  for (auto& smoother : nearend_smoothers_) {
+    smoother.UpdateMemoryLength(suppressor_config.nearend_average_blocks);
   }
+  dominant_nearend_detector_->SetConfig(suppressor_config);
+  nearend_params_.SetConfig(suppressor_config.last_lf_band,
+                            suppressor_config.first_hf_band,
+                            suppressor_config.nearend_tuning);
+
+  normal_params_.SetConfig(suppressor_config.last_lf_band,
+                           suppressor_config.first_hf_band,
+                           suppressor_config.normal_tuning);
 }
 
 // Detects when the render signal can be considered to have low power and
@@ -452,9 +480,16 @@
 SuppressionGain::GainParameters::GainParameters(
     int last_lf_band,
     int first_hf_band,
-    const EchoCanceller3Config::Suppressor::Tuning& tuning)
-    : max_inc_factor(tuning.max_inc_factor),
-      max_dec_factor_lf(tuning.max_dec_factor_lf) {
+    const EchoCanceller3Config::Suppressor::Tuning& tuning) {
+  SetConfig(last_lf_band, first_hf_band, tuning);
+}
+
+void SuppressionGain::GainParameters::SetConfig(
+    int last_lf_band,
+    int first_hf_band,
+    const EchoCanceller3Config::Suppressor::Tuning& tuning) {
+  max_inc_factor = tuning.max_inc_factor;
+  max_dec_factor_lf = tuning.max_dec_factor_lf;
   // Compute per-band masking thresholds.
   RTC_DCHECK_LT(last_lf_band, first_hf_band);
   auto& lf = tuning.mask_lf;
diff --git a/modules/audio_processing/aec3/suppression_gain.h b/modules/audio_processing/aec3/suppression_gain.h
index 4bcd0d2..6d09207 100644
--- a/modules/audio_processing/aec3/suppression_gain.h
+++ b/modules/audio_processing/aec3/suppression_gain.h
@@ -27,6 +27,7 @@
 #include "modules/audio_processing/aec3/nearend_detector.h"
 #include "modules/audio_processing/aec3/render_signal_analyzer.h"
 #include "modules/audio_processing/logging/apm_data_dumper.h"
+#include "rtc_base/gtest_prod_util.h"
 
 namespace webrtc {
 
@@ -42,6 +43,8 @@
   SuppressionGain& operator=(const SuppressionGain&) = delete;
 
   void GetGain(
+      const EchoCanceller3Config::Suppressor& suppressor_config,
+      bool config_changed,
       std::span<const std::array<float, kFftLengthBy2Plus1>> nearend_spectrum,
       std::span<const std::array<float, kFftLengthBy2Plus1>> echo_spectrum,
       std::span<const std::array<float, kFftLengthBy2Plus1>>
@@ -65,8 +68,15 @@
   void SetInitialState(bool state);
 
  private:
+  FRIEND_TEST_ALL_PREFIXES(SuppressionGainTest, UpdateStateDependingOnConfig);
+
+  // Updates the internal state, e.g. sizes and parameters, if the config
+  // changes.
+  void UpdateStateDependingOnConfig(
+      const EchoCanceller3Config::Suppressor& suppressor_config);
   // Computes the gain to apply for the bands beyond the first band.
   float UpperBandsGain(
+      const EchoCanceller3Config::Suppressor& suppressor_config,
       std::span<const std::array<float, kFftLengthBy2Plus1>> echo_spectrum,
       std::span<const std::array<float, kFftLengthBy2Plus1>>
           comfort_noise_spectrum,
@@ -81,6 +91,7 @@
                            std::array<float, kFftLengthBy2Plus1>* gain) const;
 
   void LowerBandGain(
+      const EchoCanceller3Config::Suppressor& suppressor_config,
       bool stationary_with_low_power,
       const AecState& aec_state,
       std::span<const std::array<float, kFftLengthBy2Plus1>> suppressor_input,
@@ -89,14 +100,15 @@
       bool clock_drift,
       std::array<float, kFftLengthBy2Plus1>* gain);
 
-  void GetMinGain(std::span<const float> weighted_residual_echo,
+  void GetMinGain(const EchoCanceller3Config::Suppressor& suppressor_config,
+                  std::span<const float> weighted_residual_echo,
                   std::span<const float> last_nearend,
                   std::span<const float> last_echo,
                   bool low_noise_render,
                   bool saturated_echo,
                   std::span<float> min_gain) const;
 
-  void GetMaxGain(std::span<float> max_gain) const;
+  void GetMaxGain(float floor_first_increase, std::span<float> max_gain) const;
 
   class LowNoiseRenderDetector {
    public:
@@ -111,8 +123,11 @@
         int last_lf_band,
         int first_hf_band,
         const EchoCanceller3Config::Suppressor::Tuning& tuning);
-    const float max_inc_factor;
-    const float max_dec_factor_lf;
+    void SetConfig(int last_lf_band,
+                   int first_hf_band,
+                   const EchoCanceller3Config::Suppressor::Tuning& tuning);
+    float max_inc_factor;
+    float max_dec_factor_lf;
     std::array<float, kFftLengthBy2Plus1> enr_transparent_;
     std::array<float, kFftLengthBy2Plus1> enr_suppress_;
     std::array<float, kFftLengthBy2Plus1> emr_transparent_;
@@ -121,21 +136,17 @@
   static std::atomic<int> instance_count_;
   std::unique_ptr<ApmDataDumper> data_dumper_;
   const Aec3Optimization optimization_;
-  const EchoCanceller3Config config_;
   const size_t num_capture_channels_;
-  const int state_change_duration_blocks_;
+  const EchoCanceller3Config::EchoAudibility echo_audibility_config_;
+  const bool use_subband_nearend_detection_;
   std::array<float, kFftLengthBy2Plus1> last_gain_;
   std::vector<std::array<float, kFftLengthBy2Plus1>> last_nearend_;
   std::vector<std::array<float, kFftLengthBy2Plus1>> last_echo_;
   LowNoiseRenderDetector low_render_detector_;
   bool initial_state_ = true;
-  int initial_state_change_counter_ = 0;
   std::vector<MovingAverageSpectrum> nearend_smoothers_;
-  const GainParameters nearend_params_;
-  const GainParameters normal_params_;
-  // Determines if the dominant nearend detector uses the unbounded residual
-  // echo spectrum.
-  const bool use_unbounded_echo_spectrum_;
+  GainParameters nearend_params_;
+  GainParameters normal_params_;
   std::unique_ptr<NearendDetector> dominant_nearend_detector_;
 };
 
diff --git a/modules/audio_processing/aec3/suppression_gain_unittest.cc b/modules/audio_processing/aec3/suppression_gain_unittest.cc
index 60d290d..0bd36a1 100644
--- a/modules/audio_processing/aec3/suppression_gain_unittest.cc
+++ b/modules/audio_processing/aec3/suppression_gain_unittest.cc
@@ -58,23 +58,26 @@
   AecState aec_state(CreateEnvironment(), EchoCanceller3Config{}, 1);
   EXPECT_DEATH(
       SuppressionGain(EchoCanceller3Config{}, DetectOptimization(), 16000, 1)
-          .GetGain(E2, S2, R2, R2_unbounded, N2,
+          .GetGain(EchoCanceller3Config{}.suppressor, /*config_changed=*/false,
+                   E2, S2, R2, R2_unbounded, N2,
                    RenderSignalAnalyzer((EchoCanceller3Config{})), aec_state,
-                   Block(3, 1), false, &high_bands_gain, nullptr),
+                   Block(3, 1), /*clock_drift=*/false, &high_bands_gain,
+                   nullptr),
       "");
 }
 
 #endif
 
 // Does a sanity check that the gains are correctly computed.
-TEST(SuppressionGain, BasicGainComputation) {
+TEST(SuppressionGainTest, BasicGainComputation) {
   constexpr size_t kNumRenderChannels = 1;
   constexpr size_t kNumCaptureChannels = 2;
   constexpr int kSampleRateHz = 16000;
   constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz);
-  SuppressionGain suppression_gain(EchoCanceller3Config(), DetectOptimization(),
-                                   kSampleRateHz, kNumCaptureChannels);
-  RenderSignalAnalyzer analyzer(EchoCanceller3Config{});
+  EchoCanceller3Config config;
+  SuppressionGain suppression_gain(config, DetectOptimization(), kSampleRateHz,
+                                   kNumCaptureChannels);
+  RenderSignalAnalyzer analyzer(config);
   float high_bands_gain;
   std::vector<std::array<float, kFftLengthBy2Plus1>> E2(kNumCaptureChannels);
   std::vector<std::array<float, kFftLengthBy2Plus1>> S2(kNumCaptureChannels,
@@ -88,7 +91,6 @@
   std::vector<SubtractorOutput> output(kNumCaptureChannels);
   Block x(kNumBands, kNumRenderChannels);
   const Environment env = CreateEnvironment();
-  EchoCanceller3Config config;
   AecState aec_state(env, config, kNumCaptureChannels);
   ApmDataDumper data_dumper(42);
   Subtractor subtractor(env, config, kNumRenderChannels, kNumCaptureChannels,
@@ -120,8 +122,9 @@
     aec_state.Update(delay_estimate, subtractor.FilterFrequencyResponses(),
                      subtractor.FilterImpulseResponses(),
                      *render_delay_buffer->GetRenderBuffer(), E2, Y2, output);
-    suppression_gain.GetGain(E2, S2, R2, R2_unbounded, N2, analyzer, aec_state,
-                             x, false, &high_bands_gain, &g);
+    suppression_gain.GetGain(config.suppressor, /*config_changed=*/false, E2,
+                             S2, R2, R2_unbounded, N2, analyzer, aec_state, x,
+                             /*clock_drift=*/false, &high_bands_gain, &g);
   }
   std::for_each(g.begin(), g.end(),
                 [](float a) { EXPECT_NEAR(1.0f, a, 0.001f); });
@@ -140,8 +143,9 @@
     aec_state.Update(delay_estimate, subtractor.FilterFrequencyResponses(),
                      subtractor.FilterImpulseResponses(),
                      *render_delay_buffer->GetRenderBuffer(), E2, Y2, output);
-    suppression_gain.GetGain(E2, S2, R2, R2_unbounded, N2, analyzer, aec_state,
-                             x, false, &high_bands_gain, &g);
+    suppression_gain.GetGain(config.suppressor, /*config_changed=*/false, E2,
+                             S2, R2, R2_unbounded, N2, analyzer, aec_state, x,
+                             /*clock_drift=*/false, &high_bands_gain, &g);
   }
   std::for_each(g.begin(), g.end(),
                 [](float a) { EXPECT_NEAR(1.0f, a, 0.001f); });
@@ -152,11 +156,39 @@
   R2_unbounded[1].fill(10000000000000.0f);
 
   for (int k = 0; k < 10; ++k) {
-    suppression_gain.GetGain(E2, S2, R2, R2_unbounded, N2, analyzer, aec_state,
-                             x, false, &high_bands_gain, &g);
+    suppression_gain.GetGain(config.suppressor, /*config_changed=*/false, E2,
+                             S2, R2, R2_unbounded, N2, analyzer, aec_state, x,
+                             /*clock_drift=*/false, &high_bands_gain, &g);
   }
   std::for_each(g.begin(), g.end(),
                 [](float a) { EXPECT_NEAR(0.0f, a, 0.001f); });
 }
 
+TEST(SuppressionGainTest, UpdateStateDependingOnConfig) {
+  constexpr size_t kNumCaptureChannels = 1;
+  constexpr int kSampleRateHz = 16000;
+  EchoCanceller3Config config;
+  config.suppressor.nearend_tuning.max_inc_factor = 2.0f;
+  config.suppressor.normal_tuning.max_dec_factor_lf = 0.2f;
+
+  SuppressionGain suppression_gain(config, DetectOptimization(), kSampleRateHz,
+                                   kNumCaptureChannels);
+
+  // Initial call to set up the state.
+  suppression_gain.UpdateStateDependingOnConfig(config.suppressor);
+
+  EXPECT_EQ(suppression_gain.nearend_params_.max_inc_factor, 2.0f);
+  EXPECT_EQ(suppression_gain.normal_params_.max_dec_factor_lf, 0.2f);
+
+  // Change config and verify state is updated.
+  EchoCanceller3Config new_config = config;
+  new_config.suppressor.nearend_tuning.max_inc_factor = 3.0f;
+  new_config.suppressor.normal_tuning.max_dec_factor_lf = 0.3f;
+
+  suppression_gain.UpdateStateDependingOnConfig(new_config.suppressor);
+
+  EXPECT_EQ(suppression_gain.nearend_params_.max_inc_factor, 3.0f);
+  EXPECT_EQ(suppression_gain.normal_params_.max_dec_factor_lf, 0.3f);
+}
+
 }  // namespace webrtc
diff --git a/modules/audio_processing/test/audioproc_float_impl.cc b/modules/audio_processing/test/audioproc_float_impl.cc
index 589fa1a..39ee176 100644
--- a/modules/audio_processing/test/audioproc_float_impl.cc
+++ b/modules/audio_processing/test/audioproc_float_impl.cc
@@ -780,7 +780,6 @@
                      BuiltinAudioProcessingBuilder& builder,
                      AudioProcessingBuilderState& builder_state) {
   EchoCanceller3Config aec3_config;
-  std::optional<EchoCanceller3Config> aec3_multichannel_config;
   if (settings.neural_echo_residual_estimator_model) {
     tflite::ops::builtin::BuiltinOpResolver op_resolver;
     builder_state.model = tflite::FlatBufferModel::BuildFromFile(
@@ -789,9 +788,6 @@
     std::unique_ptr<NeuralResidualEchoEstimator> estimator =
         CreateNeuralResidualEchoEstimator(builder_state.model.get(),
                                           &op_resolver);
-    aec3_config = estimator->GetConfiguration(/*multi_channel=*/false);
-    aec3_multichannel_config =
-        estimator->GetConfiguration(/*multi_channel=*/true);
     RTC_CHECK(estimator);
     builder.SetNeuralResidualEchoEstimator(std::move(estimator));
   }
@@ -813,7 +809,7 @@
     }
     std::cout << Aec3ConfigToJsonString(aec3_config) << std::endl;
   }
-  builder.SetEchoCancellerConfig(aec3_config, aec3_multichannel_config);
+  builder.SetEchoCancellerConfig(aec3_config, std::nullopt);
 
   if (settings.use_ed && *settings.use_ed) {
     builder.SetEchoDetector(CreateEchoDetector());