AEC3: Add support for multiple channels to the reverb modelling

This CL adds support for multiple channels in the reverb
modelling. As a side effect, it also partly adds multi-channel
supports for the sections of the code.

Beyond adding the multi-channel support, a bug is fixed as part of
this CL. Since the bug fix affects the bitexactness, as a safety
precaution the CL includes the ability to override the bugfix.

Apart from the contributions from the bugfix, the changes have
been verified to be bitexact for a large set of mono recordings.

Bug: webrtc:10913
Change-Id: I1f307b532be85ef4182f8db41384f44d40a25219
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/156382
Commit-Queue: Per Åhgren <peah@webrtc.org>
Reviewed-by: Sam Zackrisson <saza@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#29456}
diff --git a/api/audio/echo_canceller3_config.h b/api/audio/echo_canceller3_config.h
index 3b7cf25..7c8ca1b 100644
--- a/api/audio/echo_canceller3_config.h
+++ b/api/audio/echo_canceller3_config.h
@@ -86,6 +86,8 @@
     float max_h = 1.5f;
     bool onset_detection = true;
     size_t num_sections = 1;
+    bool clamp_quality_estimate_to_zero = true;
+    bool clamp_quality_estimate_to_one = true;
   } erle;
 
   struct EpStrength {
diff --git a/api/audio/echo_canceller3_config_json.cc b/api/audio/echo_canceller3_config_json.cc
index d07491d..c17497a 100644
--- a/api/audio/echo_canceller3_config_json.cc
+++ b/api/audio/echo_canceller3_config_json.cc
@@ -197,6 +197,10 @@
     ReadParam(section, "max_h", &cfg.erle.max_h);
     ReadParam(section, "onset_detection", &cfg.erle.onset_detection);
     ReadParam(section, "num_sections", &cfg.erle.num_sections);
+    ReadParam(section, "clamp_quality_estimate_to_zero",
+              &cfg.erle.clamp_quality_estimate_to_zero);
+    ReadParam(section, "clamp_quality_estimate_to_one",
+              &cfg.erle.clamp_quality_estimate_to_one);
   }
 
   if (rtc::GetValueFromJsonObject(aec3_root, "ep_strength", &section)) {
@@ -408,7 +412,11 @@
   ost << "\"max_h\": " << config.erle.max_h << ",";
   ost << "\"onset_detection\": "
       << (config.erle.onset_detection ? "true" : "false") << ",";
-  ost << "\"num_sections\": " << config.erle.num_sections;
+  ost << "\"num_sections\": " << config.erle.num_sections << ",";
+  ost << "\"clamp_quality_estimate_to_zero\": "
+      << (config.erle.clamp_quality_estimate_to_zero ? "true" : "false") << ",";
+  ost << "\"clamp_quality_estimate_to_one\": "
+      << (config.erle.clamp_quality_estimate_to_one ? "true" : "false");
   ost << "},";
 
   ost << "\"ep_strength\": {";
diff --git a/modules/audio_processing/aec3/aec_state.cc b/modules/audio_processing/aec3/aec_state.cc
index 6865923..7518e3a3 100644
--- a/modules/audio_processing/aec3/aec_state.cc
+++ b/modules/audio_processing/aec3/aec_state.cc
@@ -111,29 +111,23 @@
           new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
       config_(config),
       initial_state_(config_),
-      delay_state_(config_),
+      delay_state_(config_, num_capture_channels),
       transparent_state_(config_),
-      filter_quality_state_(config_),
+      filter_quality_state_(config_, num_capture_channels),
       erl_estimator_(2 * kNumBlocksPerSecond),
       erle_estimator_(2 * kNumBlocksPerSecond, config_, num_capture_channels),
-      filter_analyzers_(num_capture_channels),
+      filter_analyzer_(config_, num_capture_channels),
       echo_audibility_(
           config_.echo_audibility.use_stationarity_properties_at_init),
-      reverb_model_estimator_(config_),
-      subtractor_output_analyzers_(num_capture_channels) {
-  for (size_t ch = 0; ch < num_capture_channels; ++ch) {
-    filter_analyzers_[ch] = std::make_unique<FilterAnalyzer>(config_);
-  }
-}
+      reverb_model_estimator_(config_, num_capture_channels),
+      subtractor_output_analyzers_(num_capture_channels) {}
 
 AecState::~AecState() = default;
 
 void AecState::HandleEchoPathChange(
     const EchoPathVariability& echo_path_variability) {
   const auto full_reset = [&]() {
-    for (auto& filter_analyzer : filter_analyzers_) {
-      filter_analyzer->Reset();
-    }
+    filter_analyzer_.Reset();
     capture_signal_saturation_ = false;
     strong_not_saturated_render_blocks_ = 0;
     blocks_with_active_render_ = 0;
@@ -161,49 +155,44 @@
 void AecState::Update(
     const absl::optional<DelayEstimate>& external_delay,
     rtc::ArrayView<const std::vector<std::array<float, kFftLengthBy2Plus1>>>
-        adaptive_filter_frequency_response,
-    rtc::ArrayView<const std::vector<float>> adaptive_filter_impulse_response,
+        adaptive_filter_frequency_responses,
+    rtc::ArrayView<const std::vector<float>> adaptive_filter_impulse_responses,
     const RenderBuffer& render_buffer,
     rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>> E2_main,
     rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>> Y2,
     rtc::ArrayView<const SubtractorOutput> subtractor_output) {
-  const size_t num_capture_channels = filter_analyzers_.size();
+  const size_t num_capture_channels = subtractor_output_analyzers_.size();
   RTC_DCHECK_EQ(num_capture_channels, E2_main.size());
   RTC_DCHECK_EQ(num_capture_channels, Y2.size());
   RTC_DCHECK_EQ(num_capture_channels, subtractor_output.size());
   RTC_DCHECK_EQ(num_capture_channels, subtractor_output_analyzers_.size());
   RTC_DCHECK_EQ(num_capture_channels,
-                adaptive_filter_frequency_response.size());
-  RTC_DCHECK_EQ(num_capture_channels, adaptive_filter_impulse_response.size());
+                adaptive_filter_frequency_responses.size());
+  RTC_DCHECK_EQ(num_capture_channels, adaptive_filter_impulse_responses.size());
 
   // Analyze the filter outputs and filters.
   bool any_filter_converged = false;
   bool all_filters_diverged = true;
-  bool any_filter_consistent = false;
-  float max_echo_path_gain = 0.f;
   for (size_t ch = 0; ch < subtractor_output.size(); ++ch) {
     subtractor_output_analyzers_[ch].Update(subtractor_output[ch]);
     any_filter_converged = any_filter_converged ||
                            subtractor_output_analyzers_[ch].ConvergedFilter();
     all_filters_diverged = all_filters_diverged &&
                            subtractor_output_analyzers_[ch].DivergedFilter();
-
-    filter_analyzers_[ch]->Update(adaptive_filter_impulse_response[ch],
-                                  render_buffer);
-    any_filter_consistent =
-        any_filter_consistent || filter_analyzers_[ch]->Consistent();
-    max_echo_path_gain =
-        std::max(max_echo_path_gain, filter_analyzers_[ch]->Gain());
   }
+  bool any_filter_consistent;
+  float max_echo_path_gain;
+  filter_analyzer_.Update(adaptive_filter_impulse_responses, render_buffer,
+                          &any_filter_consistent, &max_echo_path_gain);
 
   // Estimate the direct path delay of the filter.
   if (config_.filter.use_linear_filter) {
-    delay_state_.Update(filter_analyzers_, external_delay,
+    delay_state_.Update(filter_analyzer_.FilterDelaysBlocks(), external_delay,
                         strong_not_saturated_render_blocks_);
   }
 
   const std::vector<std::vector<float>>& aligned_render_block =
-      render_buffer.Block(-delay_state_.DirectPathFilterDelay())[0];
+      render_buffer.Block(-delay_state_.DirectPathFilterDelays()[0])[0];
 
   // Update render counters.
   bool active_render = false;
@@ -225,13 +214,13 @@
   std::array<float, kFftLengthBy2Plus1> X2_reverb;
 
   UpdateAndComputeReverb(render_buffer.GetSpectrumBuffer(),
-                         delay_state_.DirectPathFilterDelay(), ReverbDecay(),
-                         &reverb_model_, X2_reverb);
+                         delay_state_.DirectPathFilterDelays()[0],
+                         ReverbDecay(), &reverb_model_, X2_reverb);
 
   if (config_.echo_audibility.use_stationarity_properties) {
     // Update the echo audibility evaluator.
     echo_audibility_.Update(render_buffer, reverb_model_.reverb(),
-                            delay_state_.DirectPathFilterDelay(),
+                            delay_state_.DirectPathFilterDelays()[0],
                             delay_state_.ExternalDelayReported());
   }
 
@@ -241,11 +230,12 @@
   }
 
   // TODO(bugs.webrtc.org/10913): Take all channels into account.
-  const auto& X2 = render_buffer.Spectrum(delay_state_.DirectPathFilterDelay(),
-                                          /*channel=*/0);
+  const auto& X2 =
+      render_buffer.Spectrum(delay_state_.DirectPathFilterDelays()[0],
+                             /*channel=*/0);
   const auto& X2_input_erle = X2_reverb;
 
-  erle_estimator_.Update(render_buffer, adaptive_filter_frequency_response[0],
+  erle_estimator_.Update(render_buffer, adaptive_filter_frequency_responses[0],
                          X2_input_erle, Y2[0], E2_main[0],
                          subtractor_output_analyzers_[0].ConvergedFilter(),
                          config_.erle.onset_detection);
@@ -262,7 +252,7 @@
   initial_state_.Update(active_render, SaturatedCapture());
 
   // Detect whether the transparent mode should be activated.
-  transparent_state_.Update(delay_state_.DirectPathFilterDelay(),
+  transparent_state_.Update(delay_state_.DirectPathFilterDelays()[0],
                             any_filter_consistent, any_filter_converged,
                             all_filters_diverged, active_render,
                             SaturatedCapture());
@@ -277,11 +267,12 @@
       config_.echo_audibility.use_stationarity_properties &&
       echo_audibility_.IsBlockStationary();
 
-  reverb_model_estimator_.Update(filter_analyzers_[0]->GetAdjustedFilter(),
-                                 adaptive_filter_frequency_response[0],
-                                 erle_estimator_.GetInstLinearQualityEstimate(),
-                                 delay_state_.DirectPathFilterDelay(),
-                                 UsableLinearEstimate(), stationary_block);
+  reverb_model_estimator_.Update(
+      filter_analyzer_.GetAdjustedFilters(),
+      adaptive_filter_frequency_responses,
+      erle_estimator_.GetInstLinearQualityEstimates(),
+      delay_state_.DirectPathFilterDelays(),
+      filter_quality_state_.UsableLinearFilterOutputs(), stationary_block);
 
   erle_estimator_.Dump(data_dumper_);
   reverb_model_estimator_.Dump(data_dumper_.get());
@@ -291,7 +282,7 @@
   data_dumper_->DumpRaw("aec3_usable_linear_estimate", UsableLinearEstimate());
   data_dumper_->DumpRaw("aec3_transparent_mode", TransparentMode());
   data_dumper_->DumpRaw("aec3_filter_delay",
-                        filter_analyzers_[0]->DelayBlocks());
+                        filter_analyzer_.MinFilterDelayBlocks());
 
   data_dumper_->DumpRaw("aec3_any_filter_consistent", any_filter_consistent);
   data_dumper_->DumpRaw("aec3_initial_state",
@@ -335,11 +326,13 @@
   transition_triggered_ = !initial_state_ && prev_initial_state;
 }
 
-AecState::FilterDelay::FilterDelay(const EchoCanceller3Config& config)
-    : delay_headroom_samples_(config.delay.delay_headroom_samples) {}
+AecState::FilterDelay::FilterDelay(const EchoCanceller3Config& config,
+                                   size_t num_capture_channels)
+    : delay_headroom_samples_(config.delay.delay_headroom_samples),
+      filter_delays_blocks_(num_capture_channels, 0) {}
 
 void AecState::FilterDelay::Update(
-    const std::vector<std::unique_ptr<FilterAnalyzer>>& filter_analyzers,
+    rtc::ArrayView<const int> analyzer_filter_delay_estimates_blocks,
     const absl::optional<DelayEstimate>& external_delay,
     size_t blocks_with_proper_filter_adaptation) {
   // Update the delay based on the external delay.
@@ -354,14 +347,15 @@
   const bool delay_estimator_may_not_have_converged =
       blocks_with_proper_filter_adaptation < 2 * kNumBlocksPerSecond;
   if (delay_estimator_may_not_have_converged && external_delay_) {
-    filter_delay_blocks_ = delay_headroom_samples_ / kBlockSize;
+    int delay_guess = delay_headroom_samples_ / kBlockSize;
+    std::fill(filter_delays_blocks_.begin(), filter_delays_blocks_.end(),
+              delay_guess);
   } else {
-    // Conservatively use the min delay among the filters.
-    filter_delay_blocks_ = filter_analyzers[0]->DelayBlocks();
-    for (size_t ch = 1; ch < filter_analyzers.size(); ++ch) {
-      filter_delay_blocks_ =
-          std::min(filter_delay_blocks_, filter_analyzers[ch]->DelayBlocks());
-    }
+    RTC_DCHECK_EQ(filter_delays_blocks_.size(),
+                  analyzer_filter_delay_estimates_blocks.size());
+    std::copy(analyzer_filter_delay_estimates_blocks.begin(),
+              analyzer_filter_delay_estimates_blocks.end(),
+              filter_delays_blocks_.begin());
   }
 }
 
@@ -452,10 +446,15 @@
 }
 
 AecState::FilteringQualityAnalyzer::FilteringQualityAnalyzer(
-    const EchoCanceller3Config& config) {}
+    const EchoCanceller3Config& config,
+    size_t num_capture_channels)
+    : use_linear_filter_(config.filter.use_linear_filter),
+      usable_linear_filter_estimates_(num_capture_channels, false) {}
 
 void AecState::FilteringQualityAnalyzer::Reset() {
-  usable_linear_estimate_ = false;
+  std::fill(usable_linear_filter_estimates_.begin(),
+            usable_linear_filter_estimates_.end(), false);
+  overall_usable_linear_estimates_ = false;
   filter_update_blocks_since_reset_ = 0;
 }
 
@@ -482,17 +481,24 @@
       sufficient_data_to_converge_at_startup &&
       filter_update_blocks_since_reset_ > kNumBlocksPerSecond * 0.2f;
 
-  // The linear filter can only be used it has had time to converge.
-  usable_linear_estimate_ = sufficient_data_to_converge_at_startup &&
-                            sufficient_data_to_converge_at_reset;
+  // The linear filter can only be used if it has had time to converge.
+  overall_usable_linear_estimates_ = sufficient_data_to_converge_at_startup &&
+                                     sufficient_data_to_converge_at_reset;
 
   // The linear filter can only be used if an external delay or convergence have
   // been identified
-  usable_linear_estimate_ =
-      usable_linear_estimate_ && (external_delay || convergence_seen_);
+  overall_usable_linear_estimates_ =
+      overall_usable_linear_estimates_ && (external_delay || convergence_seen_);
 
   // If transparent mode is on, deactivate usign the linear filter.
-  usable_linear_estimate_ = usable_linear_estimate_ && !transparent_mode;
+  overall_usable_linear_estimates_ =
+      overall_usable_linear_estimates_ && !transparent_mode;
+
+  if (use_linear_filter_) {
+    std::fill(usable_linear_filter_estimates_.begin(),
+              usable_linear_filter_estimates_.end(),
+              overall_usable_linear_estimates_);
+  }
 }
 
 void AecState::SaturationDetector::Update(
diff --git a/modules/audio_processing/aec3/aec_state.h b/modules/audio_processing/aec3/aec_state.h
index 7a7a71e..79fe13e 100644
--- a/modules/audio_processing/aec3/aec_state.h
+++ b/modules/audio_processing/aec3/aec_state.h
@@ -91,7 +91,9 @@
   float ErlTimeDomain() const { return erl_estimator_.ErlTimeDomain(); }
 
   // Returns the delay estimate based on the linear filter.
-  int FilterDelayBlocks() const { return delay_state_.DirectPathFilterDelay(); }
+  int FilterDelayBlocks() const {
+    return delay_state_.DirectPathFilterDelays()[0];
+  }
 
   // Returns whether the capture signal is saturated.
   bool SaturatedCapture() const { return capture_signal_saturation_; }
@@ -130,8 +132,9 @@
   void Update(
       const absl::optional<DelayEstimate>& external_delay,
       rtc::ArrayView<const std::vector<std::array<float, kFftLengthBy2Plus1>>>
-          adaptive_filter_frequency_response,
-      rtc::ArrayView<const std::vector<float>> adaptive_filter_impulse_response,
+          adaptive_filter_frequency_responses,
+      rtc::ArrayView<const std::vector<float>>
+          adaptive_filter_impulse_responses,
       const RenderBuffer& render_buffer,
       rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>> E2_main,
       rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>> Y2,
@@ -140,7 +143,7 @@
   // Returns filter length in blocks.
   int FilterLengthBlocks() const {
     // All filters have the same length, so arbitrarily return channel 0 length.
-    return filter_analyzers_[/*channel=*/0]->FilterLengthBlocks();
+    return filter_analyzer_.FilterLengthBlocks();
   }
 
  private:
@@ -178,7 +181,8 @@
   // AecState.
   class FilterDelay {
    public:
-    explicit FilterDelay(const EchoCanceller3Config& config);
+    FilterDelay(const EchoCanceller3Config& config,
+                size_t num_capture_channels);
 
     // Returns whether an external delay has been reported to the AecState (from
     // the delay estimator).
@@ -186,18 +190,20 @@
 
     // Returns the delay in blocks relative to the beginning of the filter that
     // corresponds to the direct path of the echo.
-    int DirectPathFilterDelay() const { return filter_delay_blocks_; }
+    rtc::ArrayView<const int> DirectPathFilterDelays() const {
+      return filter_delays_blocks_;
+    }
 
     // Updates the delay estimates based on new data.
     void Update(
-        const std::vector<std::unique_ptr<FilterAnalyzer>>& filter_analyzer,
+        rtc::ArrayView<const int> analyzer_filter_delay_estimates_blocks,
         const absl::optional<DelayEstimate>& external_delay,
         size_t blocks_with_proper_filter_adaptation);
 
    private:
     const int delay_headroom_samples_;
     bool external_delay_reported_ = false;
-    int filter_delay_blocks_ = 0;
+    std::vector<int> filter_delays_blocks_;
     absl::optional<DelayEstimate> external_delay_;
   } delay_state_;
 
@@ -243,11 +249,18 @@
   // suppressor.
   class FilteringQualityAnalyzer {
    public:
-    FilteringQualityAnalyzer(const EchoCanceller3Config& config);
+    FilteringQualityAnalyzer(const EchoCanceller3Config& config,
+                             size_t num_capture_channels);
 
-    // Returns whether the the linear filter can be used for the echo
+    // Returns whether the linear filter can be used for the echo
     // canceller output.
-    bool LinearFilterUsable() const { return usable_linear_estimate_; }
+    bool LinearFilterUsable() const { return overall_usable_linear_estimates_; }
+
+    // Returns whether an individual filter output can be used for the echo
+    // canceller output.
+    const std::vector<bool>& UsableLinearFilterOutputs() const {
+      return usable_linear_filter_estimates_;
+    }
 
     // Resets the state of the analyzer.
     void Reset();
@@ -260,10 +273,12 @@
                 bool any_filter_converged);
 
    private:
-    bool usable_linear_estimate_ = false;
+    const bool use_linear_filter_;
+    bool overall_usable_linear_estimates_ = false;
     size_t filter_update_blocks_since_reset_ = 0;
     size_t filter_update_blocks_since_start_ = 0;
     bool convergence_seen_ = false;
+    std::vector<bool> usable_linear_filter_estimates_;
   } filter_quality_state_;
 
   // Class for detecting whether the echo is to be considered to be
@@ -289,7 +304,7 @@
   size_t strong_not_saturated_render_blocks_ = 0;
   size_t blocks_with_active_render_ = 0;
   bool capture_signal_saturation_ = false;
-  std::vector<std::unique_ptr<FilterAnalyzer>> filter_analyzers_;
+  FilterAnalyzer filter_analyzer_;
   absl::optional<DelayEstimate> external_delay_;
   EchoAudibility echo_audibility_;
   ReverbModelEstimator reverb_model_estimator_;
diff --git a/modules/audio_processing/aec3/echo_canceller3.cc b/modules/audio_processing/aec3/echo_canceller3.cc
index ffff1b6..a7a76d3 100644
--- a/modules/audio_processing/aec3/echo_canceller3.cc
+++ b/modules/audio_processing/aec3/echo_canceller3.cc
@@ -42,6 +42,14 @@
     adjusted_cfg.delay.delay_headroom_samples = kBlockSize * 2;
   }
 
+  if (field_trial::IsEnabled("WebRTC-Aec3ClampInstQualityToZeroKillSwitch")) {
+    adjusted_cfg.erle.clamp_quality_estimate_to_zero = false;
+  }
+
+  if (field_trial::IsEnabled("WebRTC-Aec3ClampInstQualityToOneKillSwitch")) {
+    adjusted_cfg.erle.clamp_quality_estimate_to_one = false;
+  }
+
   return adjusted_cfg;
 }
 
diff --git a/modules/audio_processing/aec3/erle_estimator.cc b/modules/audio_processing/aec3/erle_estimator.cc
index 17bb79d..a3f68d1 100644
--- a/modules/audio_processing/aec3/erle_estimator.cc
+++ b/modules/audio_processing/aec3/erle_estimator.cc
@@ -20,7 +20,7 @@
                              size_t num_capture_channels)
     : startup_phase_length_blocks__(startup_phase_length_blocks_),
       use_signal_dependent_erle_(config.erle.num_sections > 1),
-      fullband_erle_estimator_(config.erle.min, config.erle.max_l),
+      fullband_erle_estimator_(config.erle, num_capture_channels),
       subband_erle_estimator_(config, num_capture_channels),
       signal_dependent_erle_estimator_(config, num_capture_channels) {
   Reset(true);
diff --git a/modules/audio_processing/aec3/erle_estimator.h b/modules/audio_processing/aec3/erle_estimator.h
index 7f882ca..cac6741 100644
--- a/modules/audio_processing/aec3/erle_estimator.h
+++ b/modules/audio_processing/aec3/erle_estimator.h
@@ -69,10 +69,12 @@
 
   // Returns an estimation of the current linear filter quality based on the
   // current and past fullband ERLE estimates. The returned value is a float
-  // between 0 and 1 where 1 indicates that, at this current time instant, the
-  // linear filter is reaching its maximum subtraction performance.
-  absl::optional<float> GetInstLinearQualityEstimate() const {
-    return fullband_erle_estimator_.GetInstLinearQualityEstimate();
+  // vector with content between 0 and 1 where 1 indicates that, at this current
+  // time instant, the linear filter is reaching its maximum subtraction
+  // performance.
+  rtc::ArrayView<const absl::optional<float>> GetInstLinearQualityEstimates()
+      const {
+    return fullband_erle_estimator_.GetInstLinearQualityEstimates();
   }
 
   void Dump(const std::unique_ptr<ApmDataDumper>& data_dumper) const;
diff --git a/modules/audio_processing/aec3/filter_analyzer.cc b/modules/audio_processing/aec3/filter_analyzer.cc
index 313460f..f5920f0 100644
--- a/modules/audio_processing/aec3/filter_analyzer.cc
+++ b/modules/audio_processing/aec3/filter_analyzer.cc
@@ -47,91 +47,136 @@
 
 int FilterAnalyzer::instance_count_ = 0;
 
-FilterAnalyzer::FilterAnalyzer(const EchoCanceller3Config& config)
+FilterAnalyzer::FilterAnalyzer(const EchoCanceller3Config& config,
+                               size_t num_capture_channels)
     : data_dumper_(
           new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
       bounded_erl_(config.ep_strength.bounded_erl),
       default_gain_(config.ep_strength.default_gain),
-      h_highpass_(GetTimeDomainLength(config.filter.main.length_blocks), 0.f),
-      filter_length_blocks_(config.filter.main_initial.length_blocks),
-      consistent_filter_detector_(config) {
+      h_highpass_(num_capture_channels,
+                  std::vector<float>(
+                      GetTimeDomainLength(config.filter.main.length_blocks),
+                      0.f)),
+      filter_analysis_states_(num_capture_channels,
+                              FilterAnalysisState(config)),
+      filter_delays_blocks_(num_capture_channels, 0) {
   Reset();
 }
 
 FilterAnalyzer::~FilterAnalyzer() = default;
 
 void FilterAnalyzer::Reset() {
-  delay_blocks_ = 0;
   blocks_since_reset_ = 0;
-  gain_ = default_gain_;
-  peak_index_ = 0;
   ResetRegion();
-  consistent_filter_detector_.Reset();
+  for (auto& state : filter_analysis_states_) {
+    state.peak_index = 0;
+    state.gain = default_gain_;
+    state.consistent_filter_detector.Reset();
+  }
+  std::fill(filter_delays_blocks_.begin(), filter_delays_blocks_.end(), 0);
 }
 
-void FilterAnalyzer::Update(rtc::ArrayView<const float> filter_time_domain,
-                            const RenderBuffer& render_buffer) {
-  SetRegionToAnalyze(filter_time_domain);
-  AnalyzeRegion(filter_time_domain, render_buffer);
+void FilterAnalyzer::Update(
+    rtc::ArrayView<const std::vector<float>> filters_time_domain,
+    const RenderBuffer& render_buffer,
+    bool* any_filter_consistent,
+    float* max_echo_path_gain) {
+  RTC_DCHECK(any_filter_consistent);
+  RTC_DCHECK(max_echo_path_gain);
+  RTC_DCHECK_EQ(filters_time_domain.size(), filter_analysis_states_.size());
+  RTC_DCHECK_EQ(filters_time_domain.size(), h_highpass_.size());
+
+  ++blocks_since_reset_;
+  SetRegionToAnalyze(filters_time_domain[0].size());
+  AnalyzeRegion(filters_time_domain, render_buffer);
+
+  // Aggregate the results for all capture channels.
+  auto& st_ch0 = filter_analysis_states_[0];
+  *any_filter_consistent = st_ch0.consistent_estimate;
+  *max_echo_path_gain = st_ch0.gain;
+  min_filter_delay_blocks_ = filter_delays_blocks_[0];
+  for (size_t ch = 1; ch < filters_time_domain.size(); ++ch) {
+    auto& st_ch = filter_analysis_states_[ch];
+    *any_filter_consistent =
+        *any_filter_consistent || st_ch.consistent_estimate;
+    *max_echo_path_gain = std::max(*max_echo_path_gain, st_ch.gain);
+    min_filter_delay_blocks_ =
+        std::min(min_filter_delay_blocks_, filter_delays_blocks_[ch]);
+  }
 }
 
 void FilterAnalyzer::AnalyzeRegion(
-    rtc::ArrayView<const float> filter_time_domain,
+    rtc::ArrayView<const std::vector<float>> filters_time_domain,
     const RenderBuffer& render_buffer) {
-  RTC_DCHECK_LT(region_.start_sample_, filter_time_domain.size());
-  RTC_DCHECK_LT(peak_index_, filter_time_domain.size());
-  RTC_DCHECK_LT(region_.end_sample_, filter_time_domain.size());
-
   // Preprocess the filter to avoid issues with low-frequency components in the
   // filter.
-  PreProcessFilter(filter_time_domain);
-  data_dumper_->DumpRaw("aec3_linear_filter_processed_td", h_highpass_);
+  PreProcessFilters(filters_time_domain);
+  data_dumper_->DumpRaw("aec3_linear_filter_processed_td", h_highpass_[0]);
 
-  RTC_DCHECK_EQ(h_highpass_.size(), filter_time_domain.size());
+  constexpr float kOneByBlockSize = 1.f / kBlockSize;
+  for (size_t ch = 0; ch < filters_time_domain.size(); ++ch) {
+    RTC_DCHECK_LT(region_.start_sample_, filters_time_domain[ch].size());
+    RTC_DCHECK_LT(filter_analysis_states_[ch].peak_index,
+                  filters_time_domain[0].size());
+    RTC_DCHECK_LT(region_.end_sample_, filters_time_domain[ch].size());
 
-  peak_index_ = FindPeakIndex(h_highpass_, peak_index_, region_.start_sample_,
-                              region_.end_sample_);
-  delay_blocks_ = peak_index_ >> kBlockSizeLog2;
-  UpdateFilterGain(h_highpass_, peak_index_);
-  filter_length_blocks_ = filter_time_domain.size() * (1.f / kBlockSize);
+    auto& st_ch = filter_analysis_states_[ch];
+    RTC_DCHECK_EQ(h_highpass_[ch].size(), filters_time_domain[ch].size());
 
-  consistent_estimate_ = consistent_filter_detector_.Detect(
-      h_highpass_, region_, render_buffer.Block(-delay_blocks_)[0], peak_index_,
-      delay_blocks_);
+    st_ch.peak_index =
+        FindPeakIndex(h_highpass_[ch], st_ch.peak_index, region_.start_sample_,
+                      region_.end_sample_);
+    filter_delays_blocks_[ch] = st_ch.peak_index >> kBlockSizeLog2;
+    UpdateFilterGain(h_highpass_[ch], &st_ch);
+    st_ch.filter_length_blocks =
+        filters_time_domain[ch].size() * kOneByBlockSize;
+
+    st_ch.consistent_estimate = st_ch.consistent_filter_detector.Detect(
+        h_highpass_[ch], region_,
+        render_buffer.Block(-filter_delays_blocks_[ch])[0], st_ch.peak_index,
+        filter_delays_blocks_[ch]);
+  }
 }
 
 void FilterAnalyzer::UpdateFilterGain(
     rtc::ArrayView<const float> filter_time_domain,
-    size_t peak_index) {
+    FilterAnalysisState* st) {
   bool sufficient_time_to_converge =
-      ++blocks_since_reset_ > 5 * kNumBlocksPerSecond;
+      blocks_since_reset_ > 5 * kNumBlocksPerSecond;
 
-  if (sufficient_time_to_converge && consistent_estimate_) {
-    gain_ = fabsf(filter_time_domain[peak_index]);
+  if (sufficient_time_to_converge && st->consistent_estimate) {
+    st->gain = fabsf(filter_time_domain[st->peak_index]);
   } else {
-    if (gain_) {
-      gain_ = std::max(gain_, fabsf(filter_time_domain[peak_index]));
+    // TODO(peah): Verify whether this check against a float is ok.
+    if (st->gain) {
+      st->gain = std::max(st->gain, fabsf(filter_time_domain[st->peak_index]));
     }
   }
 
-  if (bounded_erl_ && gain_) {
-    gain_ = std::max(gain_, 0.01f);
+  if (bounded_erl_ && st->gain) {
+    st->gain = std::max(st->gain, 0.01f);
   }
 }
 
-void FilterAnalyzer::PreProcessFilter(
-    rtc::ArrayView<const float> filter_time_domain) {
-  RTC_DCHECK_GE(h_highpass_.capacity(), filter_time_domain.size());
-  h_highpass_.resize(filter_time_domain.size());
-  // Minimum phase high-pass filter with cutoff frequency at about 600 Hz.
-  constexpr std::array<float, 3> h = {{0.7929742f, -0.36072128f, -0.47047766f}};
+void FilterAnalyzer::PreProcessFilters(
+    rtc::ArrayView<const std::vector<float>> filters_time_domain) {
+  for (size_t ch = 0; ch < filters_time_domain.size(); ++ch) {
+    RTC_DCHECK_LT(region_.start_sample_, filters_time_domain[ch].size());
+    RTC_DCHECK_LT(region_.end_sample_, filters_time_domain[ch].size());
 
-  std::fill(h_highpass_.begin() + region_.start_sample_,
-            h_highpass_.begin() + region_.end_sample_ + 1, 0.f);
-  for (size_t k = std::max(h.size() - 1, region_.start_sample_);
-       k <= region_.end_sample_; ++k) {
-    for (size_t j = 0; j < h.size(); ++j) {
-      h_highpass_[k] += filter_time_domain[k - j] * h[j];
+    RTC_DCHECK_GE(h_highpass_[ch].capacity(), filters_time_domain[ch].size());
+    h_highpass_[ch].resize(filters_time_domain[ch].size());
+    // Minimum phase high-pass filter with cutoff frequency at about 600 Hz.
+    constexpr std::array<float, 3> h = {
+        {0.7929742f, -0.36072128f, -0.47047766f}};
+
+    std::fill(h_highpass_[ch].begin() + region_.start_sample_,
+              h_highpass_[ch].begin() + region_.end_sample_ + 1, 0.f);
+    for (size_t k = std::max(h.size() - 1, region_.start_sample_);
+         k <= region_.end_sample_; ++k) {
+      for (size_t j = 0; j < h.size(); ++j) {
+        h_highpass_[ch][k] += filters_time_domain[ch][k - j] * h[j];
+      }
     }
   }
 }
@@ -141,19 +186,17 @@
   region_.end_sample_ = 0;
 }
 
-void FilterAnalyzer::SetRegionToAnalyze(
-    rtc::ArrayView<const float> filter_time_domain) {
+void FilterAnalyzer::SetRegionToAnalyze(size_t filter_size) {
   constexpr size_t kNumberBlocksToUpdate = 1;
   auto& r = region_;
-  r.start_sample_ =
-      r.end_sample_ >= filter_time_domain.size() - 1 ? 0 : r.end_sample_ + 1;
+  r.start_sample_ = r.end_sample_ >= filter_size - 1 ? 0 : r.end_sample_ + 1;
   r.end_sample_ =
       std::min(r.start_sample_ + kNumberBlocksToUpdate * kBlockSize - 1,
-               filter_time_domain.size() - 1);
+               filter_size - 1);
 
   // Check range.
-  RTC_DCHECK_LT(r.start_sample_, filter_time_domain.size());
-  RTC_DCHECK_LT(r.end_sample_, filter_time_domain.size());
+  RTC_DCHECK_LT(r.start_sample_, filter_size);
+  RTC_DCHECK_LT(r.end_sample_, filter_size);
   RTC_DCHECK_LE(r.start_sample_, r.end_sample_);
 }
 
diff --git a/modules/audio_processing/aec3/filter_analyzer.h b/modules/audio_processing/aec3/filter_analyzer.h
index de6c8a7..a737577 100644
--- a/modules/audio_processing/aec3/filter_analyzer.h
+++ b/modules/audio_processing/aec3/filter_analyzer.h
@@ -30,7 +30,8 @@
 // Class for analyzing the properties of an adaptive filter.
 class FilterAnalyzer {
  public:
-  explicit FilterAnalyzer(const EchoCanceller3Config& config);
+  FilterAnalyzer(const EchoCanceller3Config& config,
+                 size_t num_capture_channels);
   ~FilterAnalyzer();
 
   FilterAnalyzer(const FilterAnalyzer&) = delete;
@@ -40,35 +41,43 @@
   void Reset();
 
   // Updates the estimates with new input data.
-  void Update(rtc::ArrayView<const float> filter_time_domain,
-              const RenderBuffer& render_buffer);
+  void Update(rtc::ArrayView<const std::vector<float>> filters_time_domain,
+              const RenderBuffer& render_buffer,
+              bool* any_filter_consistent,
+              float* max_echo_path_gain);
 
-  // Returns the delay of the filter in terms of blocks.
-  int DelayBlocks() const { return delay_blocks_; }
+  // Returns the delay in blocks for each filter.
+  rtc::ArrayView<const int> FilterDelaysBlocks() const {
+    return filter_delays_blocks_;
+  }
 
-  // Returns whether the filter is consistent in the sense that it does not
-  // change much over time.
-  bool Consistent() const { return consistent_estimate_; }
-
-  // Returns the estimated filter gain.
-  float Gain() const { return gain_; }
+  // Returns the minimum delay of all filters in terms of blocks.
+  int MinFilterDelayBlocks() const { return min_filter_delay_blocks_; }
 
   // Returns the number of blocks for the current used filter.
-  int FilterLengthBlocks() const { return filter_length_blocks_; }
+  int FilterLengthBlocks() const {
+    return filter_analysis_states_[0].filter_length_blocks;
+  }
 
   // Returns the preprocessed filter.
-  rtc::ArrayView<const float> GetAdjustedFilter() const { return h_highpass_; }
+  rtc::ArrayView<const std::vector<float>> GetAdjustedFilters() const {
+    return h_highpass_;
+  }
 
   // Public for testing purposes only.
-  void SetRegionToAnalyze(rtc::ArrayView<const float> filter_time_domain);
+  void SetRegionToAnalyze(size_t filter_size);
 
  private:
-  void AnalyzeRegion(rtc::ArrayView<const float> filter_time_domain,
-                     const RenderBuffer& render_buffer);
+  struct FilterAnalysisState;
 
-  void UpdateFilterGain(rtc::ArrayView<const float> filter_time_domain,
-                        size_t max_index);
-  void PreProcessFilter(rtc::ArrayView<const float> filter_time_domain);
+  void AnalyzeRegion(
+      rtc::ArrayView<const std::vector<float>> filters_time_domain,
+      const RenderBuffer& render_buffer);
+
+  void UpdateFilterGain(rtc::ArrayView<const float> filters_time_domain,
+                        FilterAnalysisState* st);
+  void PreProcessFilters(
+      rtc::ArrayView<const std::vector<float>> filters_time_domain);
 
   void ResetRegion();
 
@@ -100,19 +109,30 @@
     int consistent_delay_reference_ = -10;
   };
 
+  struct FilterAnalysisState {
+    explicit FilterAnalysisState(const EchoCanceller3Config& config)
+        : filter_length_blocks(config.filter.main_initial.length_blocks),
+          consistent_filter_detector(config) {}
+    float gain;
+    size_t peak_index;
+    int filter_length_blocks;
+    bool consistent_estimate = false;
+    ConsistentFilterDetector consistent_filter_detector;
+  };
+
   static int instance_count_;
   std::unique_ptr<ApmDataDumper> data_dumper_;
   const bool bounded_erl_;
   const float default_gain_;
-  std::vector<float> h_highpass_;
-  int delay_blocks_ = 0;
+  std::vector<std::vector<float>> h_highpass_;
+
   size_t blocks_since_reset_ = 0;
-  bool consistent_estimate_ = false;
-  float gain_;
-  size_t peak_index_;
-  int filter_length_blocks_;
   FilterRegion region_;
-  ConsistentFilterDetector consistent_filter_detector_;
+
+  std::vector<FilterAnalysisState> filter_analysis_states_;
+  std::vector<int> filter_delays_blocks_;
+
+  int min_filter_delay_blocks_ = 0;
 };
 
 }  // namespace webrtc
diff --git a/modules/audio_processing/aec3/filter_analyzer_unittest.cc b/modules/audio_processing/aec3/filter_analyzer_unittest.cc
index 474d67d..34104c3 100644
--- a/modules/audio_processing/aec3/filter_analyzer_unittest.cc
+++ b/modules/audio_processing/aec3/filter_analyzer_unittest.cc
@@ -21,11 +21,11 @@
 TEST(FilterAnalyzer, FilterResize) {
   EchoCanceller3Config c;
   std::vector<float> filter(65, 0.f);
-  FilterAnalyzer fa(c);
-  fa.SetRegionToAnalyze(filter);
-  fa.SetRegionToAnalyze(filter);
+  FilterAnalyzer fa(c, 1);
+  fa.SetRegionToAnalyze(filter.size());
+  fa.SetRegionToAnalyze(filter.size());
   filter.resize(32);
-  fa.SetRegionToAnalyze(filter);
+  fa.SetRegionToAnalyze(filter.size());
 }
 
 }  // namespace webrtc
diff --git a/modules/audio_processing/aec3/fullband_erle_estimator.cc b/modules/audio_processing/aec3/fullband_erle_estimator.cc
index 7893b97..086638d 100644
--- a/modules/audio_processing/aec3/fullband_erle_estimator.cc
+++ b/modules/audio_processing/aec3/fullband_erle_estimator.cc
@@ -30,9 +30,13 @@
 constexpr int kPointsToAccumulate = 6;
 }  // namespace
 
-FullBandErleEstimator::FullBandErleEstimator(float min_erle, float max_erle_lf)
-    : min_erle_log2_(FastApproxLog2f(min_erle + kEpsilon)),
-      max_erle_lf_log2(FastApproxLog2f(max_erle_lf + kEpsilon)) {
+FullBandErleEstimator::FullBandErleEstimator(
+    const EchoCanceller3Config::Erle& config,
+    size_t num_capture_channels)
+    : min_erle_log2_(FastApproxLog2f(config.min + kEpsilon)),
+      max_erle_lf_log2(FastApproxLog2f(config.max_l + kEpsilon)),
+      instantaneous_erle_(config),
+      linear_filters_qualities_(num_capture_channels) {
   Reset();
 }
 
@@ -40,6 +44,7 @@
 
 void FullBandErleEstimator::Reset() {
   instantaneous_erle_.Reset();
+  UpdateQualityEstimates();
   erle_time_domain_log2_ = min_erle_log2_;
   hold_counter_time_domain_ = 0;
 }
@@ -72,6 +77,8 @@
   if (hold_counter_time_domain_ == 0) {
     instantaneous_erle_.ResetAccumulators();
   }
+
+  UpdateQualityEstimates();
 }
 
 void FullBandErleEstimator::Dump(
@@ -80,7 +87,15 @@
   instantaneous_erle_.Dump(data_dumper);
 }
 
-FullBandErleEstimator::ErleInstantaneous::ErleInstantaneous() {
+void FullBandErleEstimator::UpdateQualityEstimates() {
+  std::fill(linear_filters_qualities_.begin(), linear_filters_qualities_.end(),
+            instantaneous_erle_.GetQualityEstimate());
+}
+
+FullBandErleEstimator::ErleInstantaneous::ErleInstantaneous(
+    const EchoCanceller3Config::Erle& config)
+    : clamp_inst_quality_to_zero_(config.clamp_quality_estimate_to_zero),
+      clamp_inst_quality_to_one_(config.clamp_quality_estimate_to_one) {
   Reset();
 }
 
@@ -154,6 +169,8 @@
   const float alpha = 0.07f;
   float quality_estimate = 0.f;
   RTC_DCHECK(erle_log2_);
+  // TODO(peah): Currently, the estimate can become be less than 0; this should
+  // be corrected.
   if (max_erle_log2_ > min_erle_log2_) {
     quality_estimate = (erle_log2_.value() - min_erle_log2_) /
                        (max_erle_log2_ - min_erle_log2_);
diff --git a/modules/audio_processing/aec3/fullband_erle_estimator.h b/modules/audio_processing/aec3/fullband_erle_estimator.h
index 175db55..64372a2 100644
--- a/modules/audio_processing/aec3/fullband_erle_estimator.h
+++ b/modules/audio_processing/aec3/fullband_erle_estimator.h
@@ -12,9 +12,11 @@
 #define MODULES_AUDIO_PROCESSING_AEC3_FULLBAND_ERLE_ESTIMATOR_H_
 
 #include <memory>
+#include <vector>
 
 #include "absl/types/optional.h"
 #include "api/array_view.h"
+#include "api/audio/echo_canceller3_config.h"
 #include "modules/audio_processing/logging/apm_data_dumper.h"
 
 namespace webrtc {
@@ -23,7 +25,8 @@
 // freuquency bands.
 class FullBandErleEstimator {
  public:
-  FullBandErleEstimator(float min_erle, float max_erle_lf);
+  FullBandErleEstimator(const EchoCanceller3Config::Erle& config,
+                        size_t num_capture_channels);
   ~FullBandErleEstimator();
   // Resets the ERLE estimator.
   void Reset();
@@ -39,16 +42,19 @@
 
   // Returns an estimation of the current linear filter quality. It returns a
   // float number between 0 and 1 mapping 1 to the highest possible quality.
-  absl::optional<float> GetInstLinearQualityEstimate() const {
-    return instantaneous_erle_.GetQualityEstimate();
+  rtc::ArrayView<const absl::optional<float>> GetInstLinearQualityEstimates()
+      const {
+    return linear_filters_qualities_;
   }
 
   void Dump(const std::unique_ptr<ApmDataDumper>& data_dumper) const;
 
  private:
+  void UpdateQualityEstimates();
+
   class ErleInstantaneous {
    public:
-    ErleInstantaneous();
+    explicit ErleInstantaneous(const EchoCanceller3Config::Erle& config);
     ~ErleInstantaneous();
 
     // Updates the estimator with a new point, returns true
@@ -64,14 +70,25 @@
     // Gets an indication between 0 and 1 of the performance of the linear
     // filter for the current time instant.
     absl::optional<float> GetQualityEstimate() const {
-      return erle_log2_ ? absl::optional<float>(inst_quality_estimate_)
-                        : absl::nullopt;
+      if (erle_log2_) {
+        float value = inst_quality_estimate_;
+        if (clamp_inst_quality_to_zero_) {
+          value = std::max(0.f, value);
+        }
+        if (clamp_inst_quality_to_one_) {
+          value = std::min(1.f, value);
+        }
+        return absl::optional<float>(value);
+      }
+      return absl::nullopt;
     }
     void Dump(const std::unique_ptr<ApmDataDumper>& data_dumper) const;
 
    private:
     void UpdateMaxMin();
     void UpdateQualityEstimate();
+    const bool clamp_inst_quality_to_zero_;
+    const bool clamp_inst_quality_to_one_;
     absl::optional<float> erle_log2_;
     float inst_quality_estimate_;
     float max_erle_log2_;
@@ -86,6 +103,7 @@
   const float min_erle_log2_;
   const float max_erle_lf_log2;
   ErleInstantaneous instantaneous_erle_;
+  std::vector<absl::optional<float>> linear_filters_qualities_;
 };
 
 }  // namespace webrtc
diff --git a/modules/audio_processing/aec3/reverb_model_estimator.cc b/modules/audio_processing/aec3/reverb_model_estimator.cc
index ce3e2be..7174311 100644
--- a/modules/audio_processing/aec3/reverb_model_estimator.cc
+++ b/modules/audio_processing/aec3/reverb_model_estimator.cc
@@ -12,26 +12,43 @@
 
 namespace webrtc {
 
-ReverbModelEstimator::ReverbModelEstimator(const EchoCanceller3Config& config)
-    : reverb_decay_estimator_(config) {}
+ReverbModelEstimator::ReverbModelEstimator(const EchoCanceller3Config& config,
+                                           size_t num_capture_channels)
+    : reverb_decay_estimators_(num_capture_channels),
+      reverb_frequency_responses_(num_capture_channels) {
+  for (size_t ch = 0; ch < reverb_decay_estimators_.size(); ++ch) {
+    reverb_decay_estimators_[ch] =
+        std::make_unique<ReverbDecayEstimator>(config);
+  }
+}
 
 ReverbModelEstimator::~ReverbModelEstimator() = default;
 
 void ReverbModelEstimator::Update(
-    rtc::ArrayView<const float> impulse_response,
-    const std::vector<std::array<float, kFftLengthBy2Plus1>>&
-        frequency_response,
-    const absl::optional<float>& linear_filter_quality,
-    int filter_delay_blocks,
-    bool usable_linear_estimate,
+    rtc::ArrayView<const std::vector<float>> impulse_responses,
+    rtc::ArrayView<const std::vector<std::array<float, kFftLengthBy2Plus1>>>
+        frequency_responses,
+    rtc::ArrayView<const absl::optional<float>> linear_filter_qualities,
+    rtc::ArrayView<const int> filter_delays_blocks,
+    const std::vector<bool>& usable_linear_estimates,
     bool stationary_block) {
-  // Estimate the frequency response for the reverb.
-  reverb_frequency_response_.Update(frequency_response, filter_delay_blocks,
-                                    linear_filter_quality, stationary_block);
+  const size_t num_capture_channels = reverb_decay_estimators_.size();
+  RTC_DCHECK_EQ(num_capture_channels, impulse_responses.size());
+  RTC_DCHECK_EQ(num_capture_channels, frequency_responses.size());
+  RTC_DCHECK_EQ(num_capture_channels, usable_linear_estimates.size());
 
-  // Estimate the reverb decay,
-  reverb_decay_estimator_.Update(impulse_response, linear_filter_quality,
-                                 filter_delay_blocks, usable_linear_estimate,
-                                 stationary_block);
+  for (size_t ch = 0; ch < num_capture_channels; ++ch) {
+    // Estimate the frequency response for the reverb.
+    reverb_frequency_responses_[ch].Update(
+        frequency_responses[ch], filter_delays_blocks[ch],
+        linear_filter_qualities[ch], stationary_block);
+
+    // Estimate the reverb decay,
+    reverb_decay_estimators_[ch]->Update(
+        impulse_responses[ch], linear_filter_qualities[ch],
+        filter_delays_blocks[ch], usable_linear_estimates[ch],
+        stationary_block);
+  }
 }
+
 }  // namespace webrtc
diff --git a/modules/audio_processing/aec3/reverb_model_estimator.h b/modules/audio_processing/aec3/reverb_model_estimator.h
index 1112f93..3b9971a 100644
--- a/modules/audio_processing/aec3/reverb_model_estimator.h
+++ b/modules/audio_processing/aec3/reverb_model_estimator.h
@@ -28,34 +28,38 @@
 // Class for estimating the model parameters for the reverberant echo.
 class ReverbModelEstimator {
  public:
-  explicit ReverbModelEstimator(const EchoCanceller3Config& config);
+  ReverbModelEstimator(const EchoCanceller3Config& config,
+                       size_t num_capture_channels);
   ~ReverbModelEstimator();
 
   // Updates the estimates based on new data.
-  void Update(rtc::ArrayView<const float> impulse_response,
-              const std::vector<std::array<float, kFftLengthBy2Plus1>>&
-                  frequency_response,
-              const absl::optional<float>& linear_filter_quality,
-              int filter_delay_blocks,
-              bool usable_linear_estimate,
-              bool stationary_block);
+  void Update(
+      rtc::ArrayView<const std::vector<float>> impulse_responses,
+      rtc::ArrayView<const std::vector<std::array<float, kFftLengthBy2Plus1>>>
+          frequency_responses,
+      rtc::ArrayView<const absl::optional<float>> linear_filter_qualities,
+      rtc::ArrayView<const int> filter_delays_blocks,
+      const std::vector<bool>& usable_linear_estimates,
+      bool stationary_block);
 
   // Returns the exponential decay of the reverberant echo.
-  float ReverbDecay() const { return reverb_decay_estimator_.Decay(); }
+  // TODO(peah): Correct to properly support multiple channels.
+  float ReverbDecay() const { return reverb_decay_estimators_[0]->Decay(); }
 
   // Return the frequency response of the reverberant echo.
+  // TODO(peah): Correct to properly support multiple channels.
   rtc::ArrayView<const float> GetReverbFrequencyResponse() const {
-    return reverb_frequency_response_.FrequencyResponse();
+    return reverb_frequency_responses_[0].FrequencyResponse();
   }
 
   // Dumps debug data.
   void Dump(ApmDataDumper* data_dumper) const {
-    reverb_decay_estimator_.Dump(data_dumper);
+    reverb_decay_estimators_[0]->Dump(data_dumper);
   }
 
  private:
-  ReverbDecayEstimator reverb_decay_estimator_;
-  ReverbFrequencyResponse reverb_frequency_response_;
+  std::vector<std::unique_ptr<ReverbDecayEstimator>> reverb_decay_estimators_;
+  std::vector<ReverbFrequencyResponse> reverb_frequency_responses_;
 };
 
 }  // namespace webrtc
diff --git a/modules/audio_processing/aec3/reverb_model_estimator_unittest.cc b/modules/audio_processing/aec3/reverb_model_estimator_unittest.cc
index 8fce9d2..50a4dc0 100644
--- a/modules/audio_processing/aec3/reverb_model_estimator_unittest.cc
+++ b/modules/audio_processing/aec3/reverb_model_estimator_unittest.cc
@@ -11,8 +11,10 @@
 #include "modules/audio_processing/aec3/reverb_model_estimator.h"
 
 #include <algorithm>
+#include <array>
 #include <cmath>
 #include <numeric>
+#include <vector>
 
 #include "absl/types/optional.h"
 #include "api/array_view.h"
@@ -25,14 +27,32 @@
 
 namespace webrtc {
 
+namespace {
+
+EchoCanceller3Config CreateConfigForTest(float default_decay) {
+  EchoCanceller3Config cfg;
+  cfg.ep_strength.default_len = default_decay;
+  cfg.filter.main.length_blocks = 40;
+  return cfg;
+}
+
+constexpr int kFilterDelayBlocks = 2;
+
+}  // namespace
+
 class ReverbModelEstimatorTest {
  public:
-  explicit ReverbModelEstimatorTest(float default_decay)
-      : default_decay_(default_decay), estimated_decay_(default_decay) {
-    aec3_config_.ep_strength.default_len = default_decay_;
-    aec3_config_.filter.main.length_blocks = 40;
-    h_.resize(aec3_config_.filter.main.length_blocks * kBlockSize);
-    H2_.resize(aec3_config_.filter.main.length_blocks);
+  ReverbModelEstimatorTest(float default_decay, size_t num_capture_channels)
+      : aec3_config_(CreateConfigForTest(default_decay)),
+        estimated_decay_(default_decay),
+        h_(num_capture_channels,
+           std::vector<float>(
+               aec3_config_.filter.main.length_blocks * kBlockSize,
+               0.f)),
+        H2_(num_capture_channels,
+            std::vector<std::array<float, kFftLengthBy2Plus1>>(
+                aec3_config_.filter.main.length_blocks)),
+        quality_linear_(num_capture_channels, 1.0f) {
     CreateImpulseResponseWithDecay();
   }
   void RunEstimator();
@@ -43,51 +63,63 @@
 
  private:
   void CreateImpulseResponseWithDecay();
-
-  absl::optional<float> quality_linear_ = 1.0f;
-  static constexpr int kFilterDelayBlocks = 2;
-  static constexpr bool kUsableLinearEstimate = true;
   static constexpr bool kStationaryBlock = false;
   static constexpr float kTruePowerDecay = 0.5f;
-  EchoCanceller3Config aec3_config_;
-  float default_decay_;
+  const EchoCanceller3Config aec3_config_;
   float estimated_decay_;
   float estimated_power_tail_ = 0.f;
   float true_power_tail_ = 0.f;
-  std::vector<float> h_;
-  std::vector<std::array<float, kFftLengthBy2Plus1>> H2_;
+  std::vector<std::vector<float>> h_;
+  std::vector<std::vector<std::array<float, kFftLengthBy2Plus1>>> H2_;
+  std::vector<absl::optional<float>> quality_linear_;
 };
 
 void ReverbModelEstimatorTest::CreateImpulseResponseWithDecay() {
   const Aec3Fft fft;
-  RTC_DCHECK_EQ(h_.size(), aec3_config_.filter.main.length_blocks * kBlockSize);
-  RTC_DCHECK_EQ(H2_.size(), aec3_config_.filter.main.length_blocks);
+  for (const auto& h_k : h_) {
+    RTC_DCHECK_EQ(h_k.size(),
+                  aec3_config_.filter.main.length_blocks * kBlockSize);
+  }
+  for (const auto& H2_k : H2_) {
+    RTC_DCHECK_EQ(H2_k.size(), aec3_config_.filter.main.length_blocks);
+  }
   RTC_DCHECK_EQ(kFilterDelayBlocks, 2);
 
   float decay_sample = std::sqrt(powf(kTruePowerDecay, 1.f / kBlockSize));
   const size_t filter_delay_coefficients = kFilterDelayBlocks * kBlockSize;
-  std::fill(h_.begin(), h_.end(), 0.f);
-  h_[filter_delay_coefficients] = 1.f;
-  for (size_t k = filter_delay_coefficients + 1; k < h_.size(); ++k) {
-    h_[k] = h_[k - 1] * decay_sample;
+  for (auto& h_i : h_) {
+    std::fill(h_i.begin(), h_i.end(), 0.f);
+    h_i[filter_delay_coefficients] = 1.f;
+    for (size_t k = filter_delay_coefficients + 1; k < h_i.size(); ++k) {
+      h_i[k] = h_i[k - 1] * decay_sample;
+    }
   }
 
-  std::array<float, kFftLength> fft_data;
-  FftData H_j;
-  for (size_t j = 0, k = 0; j < H2_.size(); ++j, k += kBlockSize) {
-    fft_data.fill(0.f);
-    std::copy(h_.begin() + k, h_.begin() + k + kBlockSize, fft_data.begin());
-    fft.Fft(&fft_data, &H_j);
-    H_j.Spectrum(Aec3Optimization::kNone, H2_[j]);
+  for (size_t ch = 0; ch < H2_.size(); ++ch) {
+    for (size_t j = 0, k = 0; j < H2_[ch].size(); ++j, k += kBlockSize) {
+      std::array<float, kFftLength> fft_data;
+      fft_data.fill(0.f);
+      std::copy(h_[ch].begin() + k, h_[ch].begin() + k + kBlockSize,
+                fft_data.begin());
+      FftData H_j;
+      fft.Fft(&fft_data, &H_j);
+      H_j.Spectrum(Aec3Optimization::kNone, H2_[ch][j]);
+    }
   }
-  rtc::ArrayView<float> H2_tail(H2_[H2_.size() - 1]);
+  rtc::ArrayView<float> H2_tail(H2_[0][H2_[0].size() - 1]);
   true_power_tail_ = std::accumulate(H2_tail.begin(), H2_tail.end(), 0.f);
 }
 void ReverbModelEstimatorTest::RunEstimator() {
-  ReverbModelEstimator estimator(aec3_config_);
+  const size_t num_capture_channels = H2_.size();
+  constexpr bool kUsableLinearEstimate = true;
+  ReverbModelEstimator estimator(aec3_config_, num_capture_channels);
+  std::vector<bool> usable_linear_estimates(num_capture_channels,
+                                            kUsableLinearEstimate);
+  std::vector<int> filter_delay_blocks(num_capture_channels,
+                                       kFilterDelayBlocks);
   for (size_t k = 0; k < 3000; ++k) {
-    estimator.Update(h_, H2_, quality_linear_, kFilterDelayBlocks,
-                     kUsableLinearEstimate, kStationaryBlock);
+    estimator.Update(h_, H2_, quality_linear_, filter_delay_blocks,
+                     usable_linear_estimates, kStationaryBlock);
   }
   estimated_decay_ = estimator.ReverbDecay();
   auto freq_resp_tail = estimator.GetReverbFrequencyResponse();
@@ -96,19 +128,23 @@
 }
 
 TEST(ReverbModelEstimatorTests, NotChangingDecay) {
-  constexpr float default_decay = 0.9f;
-  ReverbModelEstimatorTest test(default_decay);
-  test.RunEstimator();
-  EXPECT_EQ(test.GetDecay(), default_decay);
-  EXPECT_NEAR(test.GetPowerTailDb(), test.GetTruePowerTailDb(), 5.f);
+  constexpr float kDefaultDecay = 0.9f;
+  for (size_t num_capture_channels : {1, 2, 4, 8}) {
+    ReverbModelEstimatorTest test(kDefaultDecay, num_capture_channels);
+    test.RunEstimator();
+    EXPECT_EQ(test.GetDecay(), kDefaultDecay);
+    EXPECT_NEAR(test.GetPowerTailDb(), test.GetTruePowerTailDb(), 5.f);
+  }
 }
 
 TEST(ReverbModelEstimatorTests, ChangingDecay) {
-  constexpr float default_decay = -0.9f;
-  ReverbModelEstimatorTest test(default_decay);
-  test.RunEstimator();
-  EXPECT_NEAR(test.GetDecay(), test.GetTrueDecay(), 0.1);
-  EXPECT_NEAR(test.GetPowerTailDb(), test.GetTruePowerTailDb(), 5.f);
+  constexpr float kDefaultDecay = -0.9f;
+  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.GetPowerTailDb(), test.GetTruePowerTailDb(), 5.f);
+  }
 }
 
 }  // namespace webrtc