AEC3: adding a milder exponential decay parameter that is used for dominant nearend regions when enabled.

Bug: webrtc:13143
Change-Id: Iedc6ff39ed5c7cd372b54a82c86354957c8852de
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/231131
Commit-Queue: Per Åhgren <peah@webrtc.org>
Reviewed-by: Per Åhgren <peah@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#34947}
diff --git a/api/audio/echo_canceller3_config.cc b/api/audio/echo_canceller3_config.cc
index b38d6b5..0224c71 100644
--- a/api/audio/echo_canceller3_config.cc
+++ b/api/audio/echo_canceller3_config.cc
@@ -166,6 +166,7 @@
 
   res = res & Limit(&c->ep_strength.default_gain, 0.f, 1000000.f);
   res = res & Limit(&c->ep_strength.default_len, -1.f, 1.f);
+  res = res & Limit(&c->ep_strength.nearend_len, -1.0f, 1.0f);
 
   res =
       res & Limit(&c->echo_audibility.low_render_limit, 0.f, 32768.f * 32768.f);
diff --git a/api/audio/echo_canceller3_config.h b/api/audio/echo_canceller3_config.h
index 087e8da..bc7c6f0 100644
--- a/api/audio/echo_canceller3_config.h
+++ b/api/audio/echo_canceller3_config.h
@@ -108,6 +108,7 @@
   struct EpStrength {
     float default_gain = 1.f;
     float default_len = 0.83f;
+    float nearend_len = 0.83f;
     bool echo_can_saturate = true;
     bool bounded_erl = false;
     bool erle_onset_compensation_in_dominant_nearend = false;
diff --git a/api/audio/echo_canceller3_config_json.cc b/api/audio/echo_canceller3_config_json.cc
index 263599c..eaf95b3 100644
--- a/api/audio/echo_canceller3_config_json.cc
+++ b/api/audio/echo_canceller3_config_json.cc
@@ -259,6 +259,7 @@
   if (rtc::GetValueFromJsonObject(aec3_root, "ep_strength", &section)) {
     ReadParam(section, "default_gain", &cfg.ep_strength.default_gain);
     ReadParam(section, "default_len", &cfg.ep_strength.default_len);
+    ReadParam(section, "nearend_len", &cfg.ep_strength.nearend_len);
     ReadParam(section, "echo_can_saturate", &cfg.ep_strength.echo_can_saturate);
     ReadParam(section, "bounded_erl", &cfg.ep_strength.bounded_erl);
     ReadParam(section, "erle_onset_compensation_in_dominant_nearend",
@@ -560,6 +561,7 @@
   ost << "\"ep_strength\": {";
   ost << "\"default_gain\": " << config.ep_strength.default_gain << ",";
   ost << "\"default_len\": " << config.ep_strength.default_len << ",";
+  ost << "\"nearend_len\": " << config.ep_strength.nearend_len << ",";
   ost << "\"echo_can_saturate\": "
       << (config.ep_strength.echo_can_saturate ? "true" : "false") << ",";
   ost << "\"bounded_erl\": "
diff --git a/api/audio/test/echo_canceller3_config_json_unittest.cc b/api/audio/test/echo_canceller3_config_json_unittest.cc
index d6edd07..bb28b4f 100644
--- a/api/audio/test/echo_canceller3_config_json_unittest.cc
+++ b/api/audio/test/echo_canceller3_config_json_unittest.cc
@@ -37,6 +37,8 @@
   // Expect unchanged values to remain default.
   EXPECT_EQ(cfg.ep_strength.default_len,
             cfg_transformed.ep_strength.default_len);
+  EXPECT_EQ(cfg.ep_strength.nearend_len,
+            cfg_transformed.ep_strength.nearend_len);
   EXPECT_EQ(cfg.suppressor.normal_tuning.mask_lf.enr_suppress,
             cfg_transformed.suppressor.normal_tuning.mask_lf.enr_suppress);
 
diff --git a/modules/audio_processing/aec3/aec_state.cc b/modules/audio_processing/aec3/aec_state.cc
index 21cad21..b09acfd 100644
--- a/modules/audio_processing/aec3/aec_state.cc
+++ b/modules/audio_processing/aec3/aec_state.cc
@@ -229,8 +229,9 @@
   std::array<float, kFftLengthBy2Plus1> avg_render_spectrum_with_reverb;
 
   ComputeAvgRenderReverb(render_buffer.GetSpectrumBuffer(),
-                         delay_state_.MinDirectPathFilterDelay(), ReverbDecay(),
-                         &avg_render_reverb_, avg_render_spectrum_with_reverb);
+                         delay_state_.MinDirectPathFilterDelay(),
+                         ReverbDecay(/*mild=*/false), &avg_render_reverb_,
+                         avg_render_spectrum_with_reverb);
 
   if (config_.echo_audibility.use_stationarity_properties) {
     // Update the echo audibility evaluator.
diff --git a/modules/audio_processing/aec3/aec_state.h b/modules/audio_processing/aec3/aec_state.h
index e2f70a4..5994465 100644
--- a/modules/audio_processing/aec3/aec_state.h
+++ b/modules/audio_processing/aec3/aec_state.h
@@ -116,8 +116,12 @@
   // Takes appropriate action at an echo path change.
   void HandleEchoPathChange(const EchoPathVariability& echo_path_variability);
 
-  // Returns the decay factor for the echo reverberation.
-  float ReverbDecay() const { return reverb_model_estimator_.ReverbDecay(); }
+  // Returns the decay factor for the echo reverberation. The parameter `mild`
+  // indicates which exponential decay to return. The default one or a milder
+  // one that can be used during nearend regions.
+  float ReverbDecay(bool mild) const {
+    return reverb_model_estimator_.ReverbDecay(mild);
+  }
 
   // Return the frequency response of the reverberant echo.
   rtc::ArrayView<const float> GetReverbFrequencyResponse() const {
diff --git a/modules/audio_processing/aec3/echo_canceller3.cc b/modules/audio_processing/aec3/echo_canceller3.cc
index 181b649..a0432e6 100644
--- a/modules/audio_processing/aec3/echo_canceller3.cc
+++ b/modules/audio_processing/aec3/echo_canceller3.cc
@@ -267,20 +267,23 @@
     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;
+  const std::string use_nearend_reverb_len_tunings =
+      field_trial::FindFullName("WebRTC-Aec3UseNearendReverbLen");
+  FieldTrialParameter<double> nearend_reverb_default_len(
+      "default_len", adjusted_cfg.ep_strength.default_len);
+  FieldTrialParameter<double> nearend_reverb_nearend_len(
+      "nearend_len", adjusted_cfg.ep_strength.nearend_len);
+
+  ParseFieldTrial({&nearend_reverb_default_len, &nearend_reverb_nearend_len},
+                  use_nearend_reverb_len_tunings);
+  float default_len = static_cast<float>(nearend_reverb_default_len.Get());
+  float nearend_len = static_cast<float>(nearend_reverb_nearend_len.Get());
+  if (default_len > -1 && default_len < 1 && nearend_len > -1 &&
+      nearend_len < 1) {
+    adjusted_cfg.ep_strength.default_len =
+        static_cast<float>(nearend_reverb_default_len.Get());
+    adjusted_cfg.ep_strength.nearend_len =
+        static_cast<float>(nearend_reverb_nearend_len.Get());
   }
 
   if (field_trial::IsEnabled("WebRTC-Aec3ShortHeadroomKillSwitch")) {
@@ -459,8 +462,6 @@
   FieldTrialParameter<int> dominant_nearend_detection_trigger_threshold(
       "dominant_nearend_detection_trigger_threshold",
       adjusted_cfg.suppressor.dominant_nearend_detection.trigger_threshold);
-  FieldTrialParameter<double> ep_strength_default_len(
-      "ep_strength_default_len", adjusted_cfg.ep_strength.default_len);
 
   ParseFieldTrial(
       {&nearend_tuning_mask_lf_enr_transparent,
@@ -477,7 +478,7 @@
        &dominant_nearend_detection_enr_exit_threshold,
        &dominant_nearend_detection_snr_threshold,
        &dominant_nearend_detection_hold_duration,
-       &dominant_nearend_detection_trigger_threshold, &ep_strength_default_len},
+       &dominant_nearend_detection_trigger_threshold},
       suppressor_tuning_override_trial_name);
 
   adjusted_cfg.suppressor.nearend_tuning.mask_lf.enr_transparent =
@@ -514,8 +515,6 @@
       dominant_nearend_detection_hold_duration.Get();
   adjusted_cfg.suppressor.dominant_nearend_detection.trigger_threshold =
       dominant_nearend_detection_trigger_threshold.Get();
-  adjusted_cfg.ep_strength.default_len =
-      static_cast<float>(ep_strength_default_len.Get());
 
   // Field trial-based overrides of individual suppressor parameters.
   RetrieveFieldTrialValue(
@@ -577,15 +576,13 @@
       "WebRTC-Aec3SuppressorAntiHowlingGainOverride", 0.f, 10.f,
       &adjusted_cfg.suppressor.high_bands_suppression.anti_howling_gain);
 
-  RetrieveFieldTrialValue("WebRTC-Aec3SuppressorEpStrengthDefaultLenOverride",
-                          -1.f, 1.f, &adjusted_cfg.ep_strength.default_len);
-
   // Field trial-based overrides of individual delay estimator parameters.
   RetrieveFieldTrialValue("WebRTC-Aec3DelayEstimateSmoothingOverride", 0.f, 1.f,
                           &adjusted_cfg.delay.delay_estimate_smoothing);
   RetrieveFieldTrialValue(
       "WebRTC-Aec3DelayEstimateSmoothingDelayFoundOverride", 0.f, 1.f,
       &adjusted_cfg.delay.delay_estimate_smoothing_delay_found);
+
   return adjusted_cfg;
 }
 
diff --git a/modules/audio_processing/aec3/echo_canceller3_unittest.cc b/modules/audio_processing/aec3/echo_canceller3_unittest.cc
index 4a3c466..b405e0c 100644
--- a/modules/audio_processing/aec3/echo_canceller3_unittest.cc
+++ b/modules/audio_processing/aec3/echo_canceller3_unittest.cc
@@ -698,23 +698,6 @@
 }
 
 // Tests the parameter functionality for the field trial override for the
-// default_len parameter.
-TEST(EchoCanceller3FieldTrials, Aec3SuppressorEpStrengthDefaultLenOverride) {
-  EchoCanceller3Config default_config;
-  EchoCanceller3Config adjusted_config = AdjustConfig(default_config);
-  ASSERT_EQ(default_config.ep_strength.default_len,
-            adjusted_config.ep_strength.default_len);
-
-  webrtc::test::ScopedFieldTrials field_trials(
-      "WebRTC-Aec3SuppressorEpStrengthDefaultLenOverride/-0.02/");
-  adjusted_config = AdjustConfig(default_config);
-
-  ASSERT_NE(default_config.ep_strength.default_len,
-            adjusted_config.ep_strength.default_len);
-  EXPECT_FLOAT_EQ(-0.02f, adjusted_config.ep_strength.default_len);
-}
-
-// Tests the parameter functionality for the field trial override for the
 // anti-howling gain.
 TEST(EchoCanceller3FieldTrials, Aec3SuppressorAntiHowlingGainOverride) {
   EchoCanceller3Config default_config;
@@ -767,7 +750,7 @@
       "detection_enr_threshold:1.3,dominant_nearend_detection_enr_exit_"
       "threshold:1.4,dominant_nearend_detection_snr_threshold:1.5,dominant_"
       "nearend_detection_hold_duration:10,dominant_nearend_detection_trigger_"
-      "threshold:11,ep_strength_default_len:1.6/");
+      "threshold:11/");
 
   EchoCanceller3Config default_config;
   EchoCanceller3Config adjusted_config = AdjustConfig(default_config);
@@ -808,8 +791,6 @@
   ASSERT_NE(
       adjusted_config.suppressor.dominant_nearend_detection.trigger_threshold,
       default_config.suppressor.dominant_nearend_detection.trigger_threshold);
-  ASSERT_NE(adjusted_config.ep_strength.default_len,
-            default_config.ep_strength.default_len);
 
   EXPECT_FLOAT_EQ(
       adjusted_config.suppressor.nearend_tuning.mask_lf.enr_transparent, 0.1);
@@ -846,7 +827,6 @@
   EXPECT_EQ(
       adjusted_config.suppressor.dominant_nearend_detection.trigger_threshold,
       11);
-  EXPECT_FLOAT_EQ(adjusted_config.ep_strength.default_len, 1.6);
 }
 
 // Testing the field trial-based override of the suppressor parameters for
@@ -900,6 +880,16 @@
                   0.5);
 }
 
+// Testing the field trial-based that override the exponential decay parameters.
+TEST(EchoCanceller3FieldTrials, Aec3UseNearendReverb) {
+  webrtc::test::ScopedFieldTrials field_trials(
+      "WebRTC-Aec3UseNearendReverbLen/default_len:0.9,nearend_len:0.8/");
+  EchoCanceller3Config default_config;
+  EchoCanceller3Config adjusted_config = AdjustConfig(default_config);
+  EXPECT_FLOAT_EQ(adjusted_config.ep_strength.default_len, 0.9);
+  EXPECT_FLOAT_EQ(adjusted_config.ep_strength.nearend_len, 0.8);
+}
+
 #if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
 
 TEST(EchoCanceller3InputCheckDeathTest, WrongCaptureNumBandsCheckVerification) {
diff --git a/modules/audio_processing/aec3/residual_echo_estimator.cc b/modules/audio_processing/aec3/residual_echo_estimator.cc
index 15bebec..640a3e3 100644
--- a/modules/audio_processing/aec3/residual_echo_estimator.cc
+++ b/modules/audio_processing/aec3/residual_echo_estimator.cc
@@ -203,7 +203,8 @@
       LinearEstimate(S2_linear, aec_state.ErleUnbounded(), R2_unbounded);
     }
 
-    UpdateReverb(ReverbType::kLinear, aec_state, render_buffer);
+    UpdateReverb(ReverbType::kLinear, aec_state, render_buffer,
+                 dominant_nearend);
     AddReverb(R2);
     AddReverb(R2_unbounded);
   } else {
@@ -240,7 +241,8 @@
 
     if (config_.echo_model.model_reverb_in_nonlinear_mode &&
         !aec_state.TransparentModeActive()) {
-      UpdateReverb(ReverbType::kNonLinear, aec_state, render_buffer);
+      UpdateReverb(ReverbType::kNonLinear, aec_state, render_buffer,
+                   dominant_nearend);
       AddReverb(R2);
       AddReverb(R2_unbounded);
     }
@@ -305,7 +307,8 @@
 // Updates the reverb estimation.
 void ResidualEchoEstimator::UpdateReverb(ReverbType reverb_type,
                                          const AecState& aec_state,
-                                         const RenderBuffer& render_buffer) {
+                                         const RenderBuffer& render_buffer,
+                                         bool dominant_nearend) {
   // Choose reverb partition based on what type of echo power model is used.
   const size_t first_reverb_partition =
       reverb_type == ReverbType::kLinear
@@ -330,15 +333,15 @@
   }
 
   // Update the reverb estimate.
+  float reverb_decay = aec_state.ReverbDecay(/*mild=*/dominant_nearend);
   if (reverb_type == ReverbType::kLinear) {
-    echo_reverb_.UpdateReverb(render_power,
-                              aec_state.GetReverbFrequencyResponse(),
-                              aec_state.ReverbDecay());
+    echo_reverb_.UpdateReverb(
+        render_power, aec_state.GetReverbFrequencyResponse(), reverb_decay);
   } else {
     const float echo_path_gain =
         GetEchoPathGain(aec_state, /*gain_for_early_reflections=*/false);
     echo_reverb_.UpdateReverbNoFreqShaping(render_power, echo_path_gain,
-                                           aec_state.ReverbDecay());
+                                           reverb_decay);
   }
 }
 // Adds the estimated power of the reverb to the residual echo power.
diff --git a/modules/audio_processing/aec3/residual_echo_estimator.h b/modules/audio_processing/aec3/residual_echo_estimator.h
index c071854..c468764 100644
--- a/modules/audio_processing/aec3/residual_echo_estimator.h
+++ b/modules/audio_processing/aec3/residual_echo_estimator.h
@@ -56,7 +56,8 @@
   // Updates the reverb estimation.
   void UpdateReverb(ReverbType reverb_type,
                     const AecState& aec_state,
-                    const RenderBuffer& render_buffer);
+                    const RenderBuffer& render_buffer,
+                    bool dominant_nearend);
 
   // Adds the estimated unmodelled echo power to the residual echo power
   // estimate.
diff --git a/modules/audio_processing/aec3/residual_echo_estimator_unittest.cc b/modules/audio_processing/aec3/residual_echo_estimator_unittest.cc
index 3d760b7..05a6103 100644
--- a/modules/audio_processing/aec3/residual_echo_estimator_unittest.cc
+++ b/modules/audio_processing/aec3/residual_echo_estimator_unittest.cc
@@ -10,6 +10,8 @@
 
 #include "modules/audio_processing/aec3/residual_echo_estimator.h"
 
+#include <numeric>
+
 #include "api/audio/echo_canceller3_config.h"
 #include "modules/audio_processing/aec3/aec3_fft.h"
 #include "modules/audio_processing/aec3/aec_state.h"
@@ -21,6 +23,109 @@
 
 namespace webrtc {
 
+namespace {
+constexpr int kSampleRateHz = 48000;
+constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz);
+constexpr float kEpsilon = 1e-4f;
+}  // namespace
+
+class ResidualEchoEstimatorTest {
+ public:
+  ResidualEchoEstimatorTest(size_t num_render_channels,
+                            size_t num_capture_channels,
+                            const EchoCanceller3Config& config)
+      : num_render_channels_(num_render_channels),
+        num_capture_channels_(num_capture_channels),
+        config_(config),
+        estimator_(config_, num_render_channels_),
+        aec_state_(config_, num_capture_channels_),
+        render_delay_buffer_(RenderDelayBuffer::Create(config_,
+                                                       kSampleRateHz,
+                                                       num_render_channels_)),
+        E2_refined_(num_capture_channels_),
+        S2_linear_(num_capture_channels_),
+        Y2_(num_capture_channels_),
+        R2_(num_capture_channels_),
+        R2_unbounded_(num_capture_channels_),
+        x_(kNumBands,
+           std::vector<std::vector<float>>(
+               num_render_channels_,
+               std::vector<float>(kBlockSize, 0.0f))),
+        H2_(num_capture_channels_,
+            std::vector<std::array<float, kFftLengthBy2Plus1>>(10)),
+        h_(num_capture_channels_,
+           std::vector<float>(
+               GetTimeDomainLength(config_.filter.refined.length_blocks),
+               0.0f)),
+        random_generator_(42U),
+        output_(num_capture_channels_) {
+    for (auto& H2_ch : H2_) {
+      for (auto& H2_k : H2_ch) {
+        H2_k.fill(0.01f);
+      }
+      H2_ch[2].fill(10.f);
+      H2_ch[2][0] = 0.1f;
+    }
+
+    for (auto& subtractor_output : output_) {
+      subtractor_output.Reset();
+      subtractor_output.s_refined.fill(100.f);
+    }
+    y_.fill(0.f);
+
+    constexpr float kLevel = 10.f;
+    for (auto& E2_refined_ch : E2_refined_) {
+      E2_refined_ch.fill(kLevel);
+    }
+    S2_linear_[0].fill(kLevel);
+    for (auto& Y2_ch : Y2_) {
+      Y2_ch.fill(kLevel);
+    }
+  }
+
+  void RunOneFrame(bool dominant_nearend) {
+    RandomizeSampleVector(&random_generator_, x_[0][0]);
+    render_delay_buffer_->Insert(x_);
+    if (first_frame_) {
+      render_delay_buffer_->Reset();
+      first_frame_ = false;
+    }
+    render_delay_buffer_->PrepareCaptureProcessing();
+
+    aec_state_.Update(delay_estimate_, H2_, h_,
+                      *render_delay_buffer_->GetRenderBuffer(), E2_refined_,
+                      Y2_, output_);
+
+    estimator_.Estimate(aec_state_, *render_delay_buffer_->GetRenderBuffer(),
+                        S2_linear_, Y2_, dominant_nearend, R2_, R2_unbounded_);
+  }
+
+  rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>> R2() const {
+    return R2_;
+  }
+
+ private:
+  const size_t num_render_channels_;
+  const size_t num_capture_channels_;
+  const EchoCanceller3Config& config_;
+  ResidualEchoEstimator estimator_;
+  AecState aec_state_;
+  std::unique_ptr<RenderDelayBuffer> render_delay_buffer_;
+  std::vector<std::array<float, kFftLengthBy2Plus1>> E2_refined_;
+  std::vector<std::array<float, kFftLengthBy2Plus1>> S2_linear_;
+  std::vector<std::array<float, kFftLengthBy2Plus1>> Y2_;
+  std::vector<std::array<float, kFftLengthBy2Plus1>> R2_;
+  std::vector<std::array<float, kFftLengthBy2Plus1>> R2_unbounded_;
+  std::vector<std::vector<std::vector<float>>> x_;
+  std::vector<std::vector<std::array<float, kFftLengthBy2Plus1>>> H2_;
+  std::vector<std::vector<float>> h_;
+  Random random_generator_;
+  std::vector<SubtractorOutput> output_;
+  std::array<float, kBlockSize> y_;
+  absl::optional<DelayEstimate> delay_estimate_;
+  bool first_frame_ = true;
+};
+
 class ResidualEchoEstimatorMultiChannel
     : public ::testing::Test,
       public ::testing::WithParamInterface<std::tuple<size_t, size_t>> {};
@@ -33,77 +138,63 @@
 TEST_P(ResidualEchoEstimatorMultiChannel, BasicTest) {
   const size_t num_render_channels = std::get<0>(GetParam());
   const size_t num_capture_channels = std::get<1>(GetParam());
-  constexpr int kSampleRateHz = 48000;
-  constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz);
 
   EchoCanceller3Config config;
-  ResidualEchoEstimator estimator(config, num_render_channels);
-  AecState aec_state(config, num_capture_channels);
-  std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(config, kSampleRateHz, num_render_channels));
-
-  std::vector<std::array<float, kFftLengthBy2Plus1>> E2_refined(
-      num_capture_channels);
-  std::vector<std::array<float, kFftLengthBy2Plus1>> S2_linear(
-      num_capture_channels);
-  std::vector<std::array<float, kFftLengthBy2Plus1>> Y2(num_capture_channels);
-  std::vector<std::array<float, kFftLengthBy2Plus1>> R2(num_capture_channels);
-  std::vector<std::array<float, kFftLengthBy2Plus1>> R2_unbounded(
-      num_capture_channels);
-  std::vector<std::vector<std::vector<float>>> x(
-      kNumBands, std::vector<std::vector<float>>(
-                     num_render_channels, std::vector<float>(kBlockSize, 0.f)));
-  std::vector<std::vector<std::array<float, kFftLengthBy2Plus1>>> H2(
-      num_capture_channels,
-      std::vector<std::array<float, kFftLengthBy2Plus1>>(10));
-  Random random_generator(42U);
-  std::vector<SubtractorOutput> output(num_capture_channels);
-  std::array<float, kBlockSize> y;
-  absl::optional<DelayEstimate> delay_estimate;
-
-  for (auto& H2_ch : H2) {
-    for (auto& H2_k : H2_ch) {
-      H2_k.fill(0.01f);
-    }
-    H2_ch[2].fill(10.f);
-    H2_ch[2][0] = 0.1f;
-  }
-
-  std::vector<std::vector<float>> h(
-      num_capture_channels,
-      std::vector<float>(
-          GetTimeDomainLength(config.filter.refined.length_blocks), 0.f));
-
-  for (auto& subtractor_output : output) {
-    subtractor_output.Reset();
-    subtractor_output.s_refined.fill(100.f);
-  }
-  y.fill(0.f);
-
-  constexpr float kLevel = 10.f;
-  for (auto& E2_refined_ch : E2_refined) {
-    E2_refined_ch.fill(kLevel);
-  }
-  S2_linear[0].fill(kLevel);
-  for (auto& Y2_ch : Y2) {
-    Y2_ch.fill(kLevel);
-  }
-
+  ResidualEchoEstimatorTest residual_echo_estimator_test(
+      num_render_channels, num_capture_channels, config);
   for (int k = 0; k < 1993; ++k) {
-    RandomizeSampleVector(&random_generator, x[0][0]);
-    render_delay_buffer->Insert(x);
-    if (k == 0) {
-      render_delay_buffer->Reset();
+    residual_echo_estimator_test.RunOneFrame(/*dominant_nearend=*/false);
+  }
+}
+
+TEST(ResidualEchoEstimatorMultiChannel, ReverbTest) {
+  const size_t num_render_channels = 1;
+  const size_t num_capture_channels = 1;
+  const size_t nFrames = 100;
+
+  EchoCanceller3Config reference_config;
+  reference_config.ep_strength.default_len = 0.95f;
+  reference_config.ep_strength.nearend_len = 0.95f;
+  EchoCanceller3Config config_use_nearend_len = reference_config;
+  config_use_nearend_len.ep_strength.default_len = 0.95f;
+  config_use_nearend_len.ep_strength.nearend_len = 0.83f;
+
+  ResidualEchoEstimatorTest reference_residual_echo_estimator_test(
+      num_render_channels, num_capture_channels, reference_config);
+  ResidualEchoEstimatorTest use_nearend_len_residual_echo_estimator_test(
+      num_render_channels, num_capture_channels, config_use_nearend_len);
+
+  std::vector<float> acum_energy_reference_R2(num_capture_channels, 0.0f);
+  std::vector<float> acum_energy_R2(num_capture_channels, 0.0f);
+  for (size_t frame = 0; frame < nFrames; ++frame) {
+    bool dominant_nearend = frame <= nFrames / 2 ? false : true;
+    reference_residual_echo_estimator_test.RunOneFrame(dominant_nearend);
+    use_nearend_len_residual_echo_estimator_test.RunOneFrame(dominant_nearend);
+    const auto& reference_R2 = reference_residual_echo_estimator_test.R2();
+    const auto& R2 = use_nearend_len_residual_echo_estimator_test.R2();
+    ASSERT_EQ(reference_R2.size(), R2.size());
+    for (size_t ch = 0; ch < reference_R2.size(); ++ch) {
+      float energy_reference_R2 = std::accumulate(
+          reference_R2[ch].cbegin(), reference_R2[ch].cend(), 0.0f);
+      float energy_R2 = std::accumulate(R2[ch].cbegin(), R2[ch].cend(), 0.0f);
+      if (dominant_nearend) {
+        EXPECT_GE(energy_reference_R2, energy_R2);
+      } else {
+        EXPECT_NEAR(energy_reference_R2, energy_R2, kEpsilon);
+      }
+      acum_energy_reference_R2[ch] += energy_reference_R2;
+      acum_energy_R2[ch] += energy_R2;
     }
-    render_delay_buffer->PrepareCaptureProcessing();
-
-    aec_state.Update(delay_estimate, H2, h,
-                     *render_delay_buffer->GetRenderBuffer(), E2_refined, Y2,
-                     output);
-
-    estimator.Estimate(aec_state, *render_delay_buffer->GetRenderBuffer(),
-                       S2_linear, Y2, /*dominant_nearend=*/false, R2,
-                       R2_unbounded);
+    if (frame == nFrames / 2 || frame == nFrames - 1) {
+      for (size_t ch = 0; ch < acum_energy_reference_R2.size(); ch++) {
+        if (dominant_nearend) {
+          EXPECT_GT(acum_energy_reference_R2[ch], acum_energy_R2[ch]);
+        } else {
+          EXPECT_NEAR(acum_energy_reference_R2[ch], acum_energy_R2[ch],
+                      kEpsilon);
+        }
+      }
+    }
   }
 }
 
diff --git a/modules/audio_processing/aec3/reverb_decay_estimator.cc b/modules/audio_processing/aec3/reverb_decay_estimator.cc
index f160b83..24f579b 100644
--- a/modules/audio_processing/aec3/reverb_decay_estimator.cc
+++ b/modules/audio_processing/aec3/reverb_decay_estimator.cc
@@ -93,7 +93,8 @@
       late_reverb_start_(kEarlyReverbMinSizeBlocks),
       late_reverb_end_(kEarlyReverbMinSizeBlocks),
       previous_gains_(config.filter.refined.length_blocks, 0.f),
-      decay_(std::fabs(config.ep_strength.default_len)) {
+      decay_(std::fabs(config.ep_strength.default_len)),
+      mild_decay_(std::fabs(config.ep_strength.nearend_len)) {
   RTC_DCHECK_GT(config.filter.refined.length_blocks,
                 static_cast<size_t>(kEarlyReverbMinSizeBlocks));
 }
diff --git a/modules/audio_processing/aec3/reverb_decay_estimator.h b/modules/audio_processing/aec3/reverb_decay_estimator.h
index 3bb9b2b..fee5421 100644
--- a/modules/audio_processing/aec3/reverb_decay_estimator.h
+++ b/modules/audio_processing/aec3/reverb_decay_estimator.h
@@ -34,8 +34,15 @@
               int filter_delay_blocks,
               bool usable_linear_filter,
               bool stationary_signal);
-  // Returns the decay for the exponential model.
-  float Decay() const { return decay_; }
+  // Returns the decay for the exponential model. The parameter `mild` indicates
+  // which exponential decay to return, the default one or a milder one.
+  float Decay(bool mild) const {
+    if (use_adaptive_echo_decay_) {
+      return decay_;
+    } else {
+      return mild ? mild_decay_ : decay_;
+    }
+  }
   // Dumps debug data.
   void Dump(ApmDataDumper* data_dumper) const;
 
@@ -103,6 +110,7 @@
   bool estimation_region_identified_ = false;
   std::vector<float> previous_gains_;
   float decay_;
+  float mild_decay_;
   float tail_gain_ = 0.f;
   float smoothing_constant_ = 0.f;
 };
diff --git a/modules/audio_processing/aec3/reverb_model_estimator.h b/modules/audio_processing/aec3/reverb_model_estimator.h
index e4e9540..63bade9 100644
--- a/modules/audio_processing/aec3/reverb_model_estimator.h
+++ b/modules/audio_processing/aec3/reverb_model_estimator.h
@@ -43,9 +43,13 @@
       const std::vector<bool>& usable_linear_estimates,
       bool stationary_block);
 
-  // Returns the exponential decay of the reverberant echo.
+  // Returns the exponential decay of the reverberant echo. The parameter `mild`
+  // indicates which exponential decay to return, the default one or a milder
+  // one.
   // TODO(peah): Correct to properly support multiple channels.
-  float ReverbDecay() const { return reverb_decay_estimators_[0]->Decay(); }
+  float ReverbDecay(bool mild) const {
+    return reverb_decay_estimators_[0]->Decay(mild);
+  }
 
   // Return the frequency response of the reverberant echo.
   // TODO(peah): Correct to properly support multiple channels.
diff --git a/modules/audio_processing/aec3/reverb_model_estimator_unittest.cc b/modules/audio_processing/aec3/reverb_model_estimator_unittest.cc
index f360a6f..fb7dcef 100644
--- a/modules/audio_processing/aec3/reverb_model_estimator_unittest.cc
+++ b/modules/audio_processing/aec3/reverb_model_estimator_unittest.cc
@@ -56,7 +56,9 @@
     CreateImpulseResponseWithDecay();
   }
   void RunEstimator();
-  float GetDecay() { return estimated_decay_; }
+  float GetDecay(bool mild) {
+    return mild ? mild_estimated_decay_ : estimated_decay_;
+  }
   float GetTrueDecay() { return kTruePowerDecay; }
   float GetPowerTailDb() { return 10.f * std::log10(estimated_power_tail_); }
   float GetTruePowerTailDb() { return 10.f * std::log10(true_power_tail_); }
@@ -67,6 +69,7 @@
   static constexpr float kTruePowerDecay = 0.5f;
   const EchoCanceller3Config aec3_config_;
   float estimated_decay_;
+  float mild_estimated_decay_;
   float estimated_power_tail_ = 0.f;
   float true_power_tail_ = 0.f;
   std::vector<std::vector<float>> h_;
@@ -121,7 +124,8 @@
     estimator.Update(h_, H2_, quality_linear_, filter_delay_blocks,
                      usable_linear_estimates, kStationaryBlock);
   }
-  estimated_decay_ = estimator.ReverbDecay();
+  estimated_decay_ = estimator.ReverbDecay(/*mild=*/false);
+  mild_estimated_decay_ = estimator.ReverbDecay(/*mild=*/true);
   auto freq_resp_tail = estimator.GetReverbFrequencyResponse();
   estimated_power_tail_ =
       std::accumulate(freq_resp_tail.begin(), freq_resp_tail.end(), 0.f);
@@ -132,7 +136,9 @@
   for (size_t num_capture_channels : {1, 2, 4, 8}) {
     ReverbModelEstimatorTest test(kDefaultDecay, num_capture_channels);
     test.RunEstimator();
-    EXPECT_EQ(test.GetDecay(), kDefaultDecay);
+    EXPECT_EQ(test.GetDecay(/*mild=*/false), kDefaultDecay);
+    EXPECT_EQ(test.GetDecay(/*mild=*/true),
+              EchoCanceller3Config().ep_strength.nearend_len);
     EXPECT_NEAR(test.GetPowerTailDb(), test.GetTruePowerTailDb(), 5.f);
   }
 }
@@ -142,7 +148,8 @@
   for (size_t num_capture_channels : {1, 2, 4, 8}) {
     ReverbModelEstimatorTest test(kDefaultDecay, num_capture_channels);
     test.RunEstimator();
-    EXPECT_NEAR(test.GetDecay(), test.GetTrueDecay(), 0.1);
+    EXPECT_NEAR(test.GetDecay(/*mild=*/false), test.GetTrueDecay(), 0.1f);
+    EXPECT_NEAR(test.GetDecay(/*mild=*/true), test.GetTrueDecay(), 0.1f);
     EXPECT_NEAR(test.GetPowerTailDb(), test.GetTruePowerTailDb(), 5.f);
   }
 }