First step of adding multi-channel support to the echo subtractor

This CL contains the first step of adding multi-channel support to the
echo subtractor.

The CL is bitexact for the mono case.

Bug: webrtc:10913
Change-Id: I10647b45c692bc001407afc6ff00e26a3e2cffaa
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/154356
Reviewed-by: Gustaf Ullberg <gustaf@webrtc.org>
Commit-Queue: Per Åhgren <peah@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#29303}
diff --git a/modules/audio_processing/aec3/echo_remover.cc b/modules/audio_processing/aec3/echo_remover.cc
index 717e9b0..ebd7981 100644
--- a/modules/audio_processing/aec3/echo_remover.cc
+++ b/modules/audio_processing/aec3/echo_remover.cc
@@ -147,7 +147,7 @@
   const size_t num_render_channels_;
   const size_t num_capture_channels_;
   const bool use_shadow_filter_output_;
-  std::vector<std::unique_ptr<Subtractor>> subtractors_;
+  Subtractor subtractor_;
   std::vector<std::unique_ptr<SuppressionGain>> suppression_gains_;
   std::vector<std::unique_ptr<ComfortNoiseGenerator>> cngs_;
   SuppressionFilter suppression_filter_;
@@ -190,7 +190,11 @@
       num_capture_channels_(num_capture_channels),
       use_shadow_filter_output_(
           config_.filter.enable_shadow_filter_output_usage),
-      subtractors_(num_capture_channels_),
+      subtractor_(config,
+                  num_render_channels_,
+                  num_capture_channels_,
+                  data_dumper_.get(),
+                  optimization_),
       suppression_gains_(num_capture_channels_),
       cngs_(num_capture_channels_),
       suppression_filter_(optimization_,
@@ -219,9 +223,6 @@
   for (size_t ch = 0; ch < num_capture_channels_; ++ch) {
     residual_echo_estimators_[ch] =
         std::make_unique<ResidualEchoEstimator>(config_);
-    subtractors_[ch] = std::make_unique<Subtractor>(
-        config, num_render_channels_, num_capture_channels_, data_dumper_.get(),
-        optimization_);
     suppression_gains_[ch] = std::make_unique<SuppressionGain>(
         config_, optimization_, sample_rate_hz);
     cngs_[ch] = std::make_unique<ComfortNoiseGenerator>(optimization_);
@@ -339,9 +340,7 @@
       }
     }
 
-    for (size_t ch = 0; ch < num_capture_channels_; ++ch) {
-      subtractors_[ch]->HandleEchoPathChange(echo_path_variability);
-    }
+    subtractor_.HandleEchoPathChange(echo_path_variability);
     aec_state_.HandleEchoPathChange(echo_path_variability);
 
     if (echo_path_variability.delay_change !=
@@ -359,21 +358,21 @@
   render_signal_analyzer_.Update(*render_buffer,
                                  aec_state_.FilterDelayBlocks());
 
-  // Perform linear echo cancellation.
+  // State transition.
   if (aec_state_.TransitionTriggered()) {
+    subtractor_.ExitInitialState();
     for (size_t ch = 0; ch < num_capture_channels_; ++ch) {
-      subtractors_[ch]->ExitInitialState();
       suppression_gains_[ch]->SetInitialState(false);
     }
   }
 
+  // Perform linear echo cancellation.
+  subtractor_.Process(*render_buffer, (*y)[0], render_signal_analyzer_,
+                      aec_state_, subtractor_output);
+
   for (size_t ch = 0; ch < num_capture_channels_; ++ch) {
     auto& y_low = (*y)[0][ch];
 
-    // If the delay is known, use the echo subtractor.
-    subtractors_[ch]->Process(*render_buffer, y_low, render_signal_analyzer_,
-                              aec_state_, &subtractor_output[ch]);
-
     // Compute spectra.
     FormLinearFilterOutput(subtractor_output[ch], e[ch]);
     WindowedPaddedFft(fft_, y_low, y_old_[ch], &Y[ch]);
@@ -385,9 +384,9 @@
 
   // Update the AEC state information.
   // TODO(bugs.webrtc.org/10913): Take all subtractors into account.
-  aec_state_.Update(external_delay, subtractors_[0]->FilterFrequencyResponse(),
-                    subtractors_[0]->FilterImpulseResponse(), *render_buffer,
-                    E2[0], Y2[0], subtractor_output[0], y0);
+  aec_state_.Update(external_delay, subtractor_.FilterFrequencyResponse(),
+                    subtractor_.FilterImpulseResponse(), *render_buffer, E2[0],
+                    Y2[0], subtractor_output[0], y0);
 
   // Choose the linear output.
   const auto& Y_fft = aec_state_.UseLinearFilterOutput() ? E : Y;
diff --git a/modules/audio_processing/aec3/subtractor.cc b/modules/audio_processing/aec3/subtractor.cc
index 4d86358..0c52ed6 100644
--- a/modules/audio_processing/aec3/subtractor.cc
+++ b/modules/audio_processing/aec3/subtractor.cc
@@ -65,32 +65,50 @@
       data_dumper_(data_dumper),
       optimization_(optimization),
       config_(config),
-      main_filter_(config_.filter.main.length_blocks,
-                   config_.filter.main_initial.length_blocks,
-                   config.filter.config_change_duration_blocks,
-                   num_render_channels,
-                   num_capture_channels,
-                   optimization,
-                   data_dumper_),
-      shadow_filter_(config_.filter.shadow.length_blocks,
-                     config_.filter.shadow_initial.length_blocks,
-                     config.filter.config_change_duration_blocks,
-                     num_render_channels,
-                     num_capture_channels,
-                     optimization,
-                     data_dumper_),
-      G_main_(config_.filter.main_initial,
-              config_.filter.config_change_duration_blocks),
-      G_shadow_(config_.filter.shadow_initial,
-                config.filter.config_change_duration_blocks),
-      main_frequency_response_(main_filter_.max_filter_size_partitions(),
-                               std::array<float, kFftLengthBy2Plus1>()),
+      num_capture_channels_(num_capture_channels),
+      main_filter_(num_capture_channels_),
+      shadow_filter_(num_capture_channels_),
+      G_main_(num_capture_channels_),
+      G_shadow_(num_capture_channels_),
+      filter_misadjustment_estimator_(num_capture_channels_),
+      poor_shadow_filter_counter_(num_capture_channels_, 0),
+      main_frequency_response_(
+          num_capture_channels_,
+          std::vector<std::array<float, kFftLengthBy2Plus1>>(
+              std::max(config_.filter.main_initial.length_blocks,
+                       config_.filter.main.length_blocks),
+              std::array<float, kFftLengthBy2Plus1>())),
       main_impulse_response_(
-          GetTimeDomainLength(main_filter_.max_filter_size_partitions()),
-          0.f) {
+          num_capture_channels_,
+          std::vector<float>(GetTimeDomainLength(std::max(
+                                 config_.filter.main_initial.length_blocks,
+                                 config_.filter.main.length_blocks)),
+                             0.f)) {
+  for (size_t ch = 0; ch < num_capture_channels_; ++ch) {
+    main_filter_[ch] = std::make_unique<AdaptiveFirFilter>(
+        config_.filter.main.length_blocks,
+        config_.filter.main_initial.length_blocks,
+        config.filter.config_change_duration_blocks, num_render_channels,
+        num_capture_channels, optimization, data_dumper_);
+
+    shadow_filter_[ch] = std::make_unique<AdaptiveFirFilter>(
+        config_.filter.shadow.length_blocks,
+        config_.filter.shadow_initial.length_blocks,
+        config.filter.config_change_duration_blocks, num_render_channels,
+        num_capture_channels, optimization, data_dumper_);
+    G_main_[ch] = std::make_unique<MainFilterUpdateGain>(
+        config_.filter.main_initial,
+        config_.filter.config_change_duration_blocks);
+    G_shadow_[ch] = std::make_unique<ShadowFilterUpdateGain>(
+        config_.filter.shadow_initial,
+        config.filter.config_change_duration_blocks);
+  }
+
   RTC_DCHECK(data_dumper_);
-  for (auto& H2_k : main_frequency_response_) {
-    H2_k.fill(0.f);
+  for (size_t ch = 0; ch < num_capture_channels_; ++ch) {
+    for (auto& H2_k : main_frequency_response_[ch]) {
+      H2_k.fill(0.f);
+    }
   }
 }
 
@@ -99,16 +117,18 @@
 void Subtractor::HandleEchoPathChange(
     const EchoPathVariability& echo_path_variability) {
   const auto full_reset = [&]() {
-    main_filter_.HandleEchoPathChange();
-    shadow_filter_.HandleEchoPathChange();
-    G_main_.HandleEchoPathChange(echo_path_variability);
-    G_shadow_.HandleEchoPathChange();
-    G_main_.SetConfig(config_.filter.main_initial, true);
-    G_shadow_.SetConfig(config_.filter.shadow_initial, true);
-    main_filter_.SetSizePartitions(config_.filter.main_initial.length_blocks,
-                                   true);
-    shadow_filter_.SetSizePartitions(
-        config_.filter.shadow_initial.length_blocks, true);
+    for (size_t ch = 0; ch < num_capture_channels_; ++ch) {
+      main_filter_[ch]->HandleEchoPathChange();
+      shadow_filter_[ch]->HandleEchoPathChange();
+      G_main_[ch]->HandleEchoPathChange(echo_path_variability);
+      G_shadow_[ch]->HandleEchoPathChange();
+      G_main_[ch]->SetConfig(config_.filter.main_initial, true);
+      G_shadow_[ch]->SetConfig(config_.filter.shadow_initial, true);
+      main_filter_[ch]->SetSizePartitions(
+          config_.filter.main_initial.length_blocks, true);
+      shadow_filter_[ch]->SetSizePartitions(
+          config_.filter.shadow_initial.length_blocks, true);
+    }
   };
 
   if (echo_path_variability.delay_change !=
@@ -117,128 +137,149 @@
   }
 
   if (echo_path_variability.gain_change) {
-    G_main_.HandleEchoPathChange(echo_path_variability);
+    for (size_t ch = 0; ch < num_capture_channels_; ++ch) {
+      G_main_[ch]->HandleEchoPathChange(echo_path_variability);
+    }
   }
 }
 
 void Subtractor::ExitInitialState() {
-  G_main_.SetConfig(config_.filter.main, false);
-  G_shadow_.SetConfig(config_.filter.shadow, false);
-  main_filter_.SetSizePartitions(config_.filter.main.length_blocks, false);
-  shadow_filter_.SetSizePartitions(config_.filter.shadow.length_blocks, false);
+  for (size_t ch = 0; ch < num_capture_channels_; ++ch) {
+    G_main_[ch]->SetConfig(config_.filter.main, false);
+    G_shadow_[ch]->SetConfig(config_.filter.shadow, false);
+    main_filter_[ch]->SetSizePartitions(config_.filter.main.length_blocks,
+                                        false);
+    shadow_filter_[ch]->SetSizePartitions(config_.filter.shadow.length_blocks,
+                                          false);
+  }
 }
 
 void Subtractor::Process(const RenderBuffer& render_buffer,
-                         const rtc::ArrayView<const float> capture,
+                         const std::vector<std::vector<float>>& capture,
                          const RenderSignalAnalyzer& render_signal_analyzer,
                          const AecState& aec_state,
-                         SubtractorOutput* output) {
-  RTC_DCHECK_EQ(kBlockSize, capture.size());
-  rtc::ArrayView<const float> y = capture;
-  FftData& E_main = output->E_main;
-  FftData E_shadow;
-  std::array<float, kBlockSize>& e_main = output->e_main;
-  std::array<float, kBlockSize>& e_shadow = output->e_shadow;
-
-  FftData S;
-  FftData& G = S;
-
-  // Form the outputs of the main and shadow filters.
-  main_filter_.Filter(render_buffer, &S);
-  PredictionError(fft_, S, y, &e_main, &output->s_main);
-
-  shadow_filter_.Filter(render_buffer, &S);
-  PredictionError(fft_, S, y, &e_shadow, &output->s_shadow);
-
-  // Compute the signal powers in the subtractor output.
-  output->ComputeMetrics(y);
-
-  // Adjust the filter if needed.
-  bool main_filter_adjusted = false;
-  filter_misadjustment_estimator_.Update(*output);
-  if (filter_misadjustment_estimator_.IsAdjustmentNeeded()) {
-    float scale = filter_misadjustment_estimator_.GetMisadjustment();
-    main_filter_.ScaleFilter(scale);
-    for (auto& h_k : main_impulse_response_) {
-      h_k *= scale;
-    }
-    ScaleFilterOutput(y, scale, e_main, output->s_main);
-    filter_misadjustment_estimator_.Reset();
-    main_filter_adjusted = true;
-  }
-
-  // Compute the FFts of the main and shadow filter outputs.
-  fft_.ZeroPaddedFft(e_main, Aec3Fft::Window::kHanning, &E_main);
-  fft_.ZeroPaddedFft(e_shadow, Aec3Fft::Window::kHanning, &E_shadow);
-
-  // Compute spectra for future use.
-  E_shadow.Spectrum(optimization_, output->E2_shadow);
-  E_main.Spectrum(optimization_, output->E2_main);
+                         rtc::ArrayView<SubtractorOutput> outputs) {
+  RTC_DCHECK_EQ(num_capture_channels_, capture.size());
 
   // Compute the render powers.
   std::array<float, kFftLengthBy2Plus1> X2_main;
   std::array<float, kFftLengthBy2Plus1> X2_shadow_data;
   std::array<float, kFftLengthBy2Plus1>& X2_shadow =
-      main_filter_.SizePartitions() == shadow_filter_.SizePartitions()
+      main_filter_[0]->SizePartitions() == shadow_filter_[0]->SizePartitions()
           ? X2_main
           : X2_shadow_data;
-  if (main_filter_.SizePartitions() == shadow_filter_.SizePartitions()) {
-    render_buffer.SpectralSum(main_filter_.SizePartitions(), &X2_main);
-  } else if (main_filter_.SizePartitions() > shadow_filter_.SizePartitions()) {
-    render_buffer.SpectralSums(shadow_filter_.SizePartitions(),
-                               main_filter_.SizePartitions(), &X2_shadow,
+  if (main_filter_[0]->SizePartitions() ==
+      shadow_filter_[0]->SizePartitions()) {
+    render_buffer.SpectralSum(main_filter_[0]->SizePartitions(), &X2_main);
+  } else if (main_filter_[0]->SizePartitions() >
+             shadow_filter_[0]->SizePartitions()) {
+    render_buffer.SpectralSums(shadow_filter_[0]->SizePartitions(),
+                               main_filter_[0]->SizePartitions(), &X2_shadow,
                                &X2_main);
   } else {
-    render_buffer.SpectralSums(main_filter_.SizePartitions(),
-                               shadow_filter_.SizePartitions(), &X2_main,
+    render_buffer.SpectralSums(main_filter_[0]->SizePartitions(),
+                               shadow_filter_[0]->SizePartitions(), &X2_main,
                                &X2_shadow);
   }
 
-  // Update the main filter.
-  if (!main_filter_adjusted) {
-    std::array<float, kFftLengthBy2Plus1> erl;
-    ComputeErl(optimization_, main_frequency_response_, erl);
-    G_main_.Compute(X2_main, render_signal_analyzer, *output, erl,
-                    main_filter_.SizePartitions(), aec_state.SaturatedCapture(),
-                    &G);
-  } else {
-    G.re.fill(0.f);
-    G.im.fill(0.f);
+  // Process all capture channels
+  for (size_t ch = 0; ch < num_capture_channels_; ++ch) {
+    RTC_DCHECK_EQ(kBlockSize, capture[ch].size());
+    SubtractorOutput& output = outputs[ch];
+    rtc::ArrayView<const float> y = capture[ch];
+    FftData& E_main = output.E_main;
+    FftData E_shadow;
+    std::array<float, kBlockSize>& e_main = output.e_main;
+    std::array<float, kBlockSize>& e_shadow = output.e_shadow;
+
+    FftData S;
+    FftData& G = S;
+
+    // Form the outputs of the main and shadow filters.
+    main_filter_[ch]->Filter(render_buffer, &S);
+    PredictionError(fft_, S, y, &e_main, &output.s_main);
+
+    shadow_filter_[ch]->Filter(render_buffer, &S);
+    PredictionError(fft_, S, y, &e_shadow, &output.s_shadow);
+
+    // Compute the signal powers in the subtractor output.
+    output.ComputeMetrics(y);
+
+    // Adjust the filter if needed.
+    bool main_filter_adjusted = false;
+    filter_misadjustment_estimator_[ch].Update(output);
+    if (filter_misadjustment_estimator_[ch].IsAdjustmentNeeded()) {
+      float scale = filter_misadjustment_estimator_[ch].GetMisadjustment();
+      main_filter_[ch]->ScaleFilter(scale);
+      for (auto& h_k : main_impulse_response_[ch]) {
+        h_k *= scale;
+      }
+      ScaleFilterOutput(y, scale, e_main, output.s_main);
+      filter_misadjustment_estimator_[ch].Reset();
+      main_filter_adjusted = true;
+    }
+
+    // Compute the FFts of the main and shadow filter outputs.
+    fft_.ZeroPaddedFft(e_main, Aec3Fft::Window::kHanning, &E_main);
+    fft_.ZeroPaddedFft(e_shadow, Aec3Fft::Window::kHanning, &E_shadow);
+
+    // Compute spectra for future use.
+    E_shadow.Spectrum(optimization_, output.E2_shadow);
+    E_main.Spectrum(optimization_, output.E2_main);
+
+    // Update the main filter.
+    if (!main_filter_adjusted) {
+      std::array<float, kFftLengthBy2Plus1> erl;
+      ComputeErl(optimization_, main_frequency_response_[ch], erl);
+      G_main_[ch]->Compute(X2_main, render_signal_analyzer, output, erl,
+                           main_filter_[ch]->SizePartitions(),
+                           aec_state.SaturatedCapture(), &G);
+    } else {
+      G.re.fill(0.f);
+      G.im.fill(0.f);
+    }
+    main_filter_[ch]->Adapt(render_buffer, G, &main_impulse_response_[ch]);
+    main_filter_[ch]->ComputeFrequencyResponse(&main_frequency_response_[ch]);
+
+    if (ch == 0) {
+      data_dumper_->DumpRaw("aec3_subtractor_G_main", G.re);
+      data_dumper_->DumpRaw("aec3_subtractor_G_main", G.im);
+    }
+
+    // Update the shadow filter.
+    poor_shadow_filter_counter_[ch] = output.e2_main < output.e2_shadow
+                                          ? poor_shadow_filter_counter_[ch] + 1
+                                          : 0;
+    if (poor_shadow_filter_counter_[ch] < 5) {
+      G_shadow_[ch]->Compute(X2_shadow, render_signal_analyzer, E_shadow,
+                             shadow_filter_[ch]->SizePartitions(),
+                             aec_state.SaturatedCapture(), &G);
+    } else {
+      poor_shadow_filter_counter_[ch] = 0;
+      shadow_filter_[ch]->SetFilter(main_filter_[ch]->GetFilter());
+      G_shadow_[ch]->Compute(X2_shadow, render_signal_analyzer, E_main,
+                             shadow_filter_[ch]->SizePartitions(),
+                             aec_state.SaturatedCapture(), &G);
+    }
+
+    shadow_filter_[ch]->Adapt(render_buffer, G);
+    if (ch == 0) {
+      data_dumper_->DumpRaw("aec3_subtractor_G_shadow", G.re);
+      data_dumper_->DumpRaw("aec3_subtractor_G_shadow", G.im);
+      filter_misadjustment_estimator_[ch].Dump(data_dumper_);
+      DumpFilters();
+    }
+
+    std::for_each(e_main.begin(), e_main.end(),
+                  [](float& a) { a = rtc::SafeClamp(a, -32768.f, 32767.f); });
+
+    if (ch == 0) {
+      data_dumper_->DumpWav("aec3_main_filter_output", kBlockSize, &e_main[0],
+                            16000, 1);
+      data_dumper_->DumpWav("aec3_shadow_filter_output", kBlockSize,
+                            &e_shadow[0], 16000, 1);
+    }
   }
-  main_filter_.Adapt(render_buffer, G, &main_impulse_response_);
-  main_filter_.ComputeFrequencyResponse(&main_frequency_response_);
-
-  data_dumper_->DumpRaw("aec3_subtractor_G_main", G.re);
-  data_dumper_->DumpRaw("aec3_subtractor_G_main", G.im);
-
-  // Update the shadow filter.
-  poor_shadow_filter_counter_ =
-      output->e2_main < output->e2_shadow ? poor_shadow_filter_counter_ + 1 : 0;
-  if (poor_shadow_filter_counter_ < 5) {
-    G_shadow_.Compute(X2_shadow, render_signal_analyzer, E_shadow,
-                      shadow_filter_.SizePartitions(),
-                      aec_state.SaturatedCapture(), &G);
-  } else {
-    poor_shadow_filter_counter_ = 0;
-    shadow_filter_.SetFilter(main_filter_.GetFilter());
-    G_shadow_.Compute(X2_shadow, render_signal_analyzer, E_main,
-                      shadow_filter_.SizePartitions(),
-                      aec_state.SaturatedCapture(), &G);
-  }
-
-  shadow_filter_.Adapt(render_buffer, G);
-  data_dumper_->DumpRaw("aec3_subtractor_G_shadow", G.re);
-  data_dumper_->DumpRaw("aec3_subtractor_G_shadow", G.im);
-  filter_misadjustment_estimator_.Dump(data_dumper_);
-  DumpFilters();
-
-  std::for_each(e_main.begin(), e_main.end(),
-                [](float& a) { a = rtc::SafeClamp(a, -32768.f, 32767.f); });
-
-  data_dumper_->DumpWav("aec3_main_filter_output", kBlockSize, &e_main[0],
-                        16000, 1);
-  data_dumper_->DumpWav("aec3_shadow_filter_output", kBlockSize, &e_shadow[0],
-                        16000, 1);
 }
 
 void Subtractor::FilterMisadjustmentEstimator::Update(
diff --git a/modules/audio_processing/aec3/subtractor.h b/modules/audio_processing/aec3/subtractor.h
index 7c3c5e0..c5fb765 100644
--- a/modules/audio_processing/aec3/subtractor.h
+++ b/modules/audio_processing/aec3/subtractor.h
@@ -48,35 +48,40 @@
 
   // Performs the echo subtraction.
   void Process(const RenderBuffer& render_buffer,
-               const rtc::ArrayView<const float> capture,
+               const std::vector<std::vector<float>>& capture,
                const RenderSignalAnalyzer& render_signal_analyzer,
                const AecState& aec_state,
-               SubtractorOutput* output);
+               rtc::ArrayView<SubtractorOutput> outputs);
 
   void HandleEchoPathChange(const EchoPathVariability& echo_path_variability);
 
   // Exits the initial state.
   void ExitInitialState();
 
-  // Returns the block-wise frequency response for the main adaptive filter.
+  // Returns the block-wise frequency responses for the main adaptive filters.
+  // TODO(bugs.webrtc.org/10913): Return the frequency responses for all capture
+  // channels.
   const std::vector<std::array<float, kFftLengthBy2Plus1>>&
   FilterFrequencyResponse() const {
-    return main_frequency_response_;
+    return main_frequency_response_[0];
   }
 
-  // Returns the estimate of the impulse response for the main adaptive filter.
+  // Returns the estimates of the impulse responses for the main adaptive
+  // filters.
+  // TODO(bugs.webrtc.org/10913): Return the impulse responses for all capture
+  // channels.
   const std::vector<float>& FilterImpulseResponse() const {
-    return main_impulse_response_;
+    return main_impulse_response_[0];
   }
 
   void DumpFilters() {
-    size_t current_size = main_impulse_response_.size();
-    main_impulse_response_.resize(main_impulse_response_.capacity());
-    data_dumper_->DumpRaw("aec3_subtractor_h_main", main_impulse_response_);
-    main_impulse_response_.resize(current_size);
+    size_t current_size = main_impulse_response_[0].size();
+    main_impulse_response_[0].resize(main_impulse_response_[0].capacity());
+    data_dumper_->DumpRaw("aec3_subtractor_h_main", main_impulse_response_[0]);
+    main_impulse_response_[0].resize(current_size);
 
-    main_filter_.DumpFilter("aec3_subtractor_H_main");
-    shadow_filter_.DumpFilter("aec3_subtractor_H_shadow");
+    main_filter_[0]->DumpFilter("aec3_subtractor_H_main");
+    shadow_filter_[0]->DumpFilter("aec3_subtractor_H_shadow");
   }
 
  private:
@@ -115,15 +120,17 @@
   ApmDataDumper* data_dumper_;
   const Aec3Optimization optimization_;
   const EchoCanceller3Config config_;
+  const size_t num_capture_channels_;
 
-  AdaptiveFirFilter main_filter_;
-  AdaptiveFirFilter shadow_filter_;
-  MainFilterUpdateGain G_main_;
-  ShadowFilterUpdateGain G_shadow_;
-  FilterMisadjustmentEstimator filter_misadjustment_estimator_;
-  size_t poor_shadow_filter_counter_ = 0;
-  std::vector<std::array<float, kFftLengthBy2Plus1>> main_frequency_response_;
-  std::vector<float> main_impulse_response_;
+  std::vector<std::unique_ptr<AdaptiveFirFilter>> main_filter_;
+  std::vector<std::unique_ptr<AdaptiveFirFilter>> shadow_filter_;
+  std::vector<std::unique_ptr<MainFilterUpdateGain>> G_main_;
+  std::vector<std::unique_ptr<ShadowFilterUpdateGain>> G_shadow_;
+  std::vector<FilterMisadjustmentEstimator> filter_misadjustment_estimator_;
+  std::vector<size_t> poor_shadow_filter_counter_;
+  std::vector<std::vector<std::array<float, kFftLengthBy2Plus1>>>
+      main_frequency_response_;
+  std::vector<std::vector<float>> main_impulse_response_;
 };
 
 }  // namespace webrtc
diff --git a/modules/audio_processing/aec3/subtractor_unittest.cc b/modules/audio_processing/aec3/subtractor_unittest.cc
index 40d8569..daacbd3 100644
--- a/modules/audio_processing/aec3/subtractor_unittest.cc
+++ b/modules/audio_processing/aec3/subtractor_unittest.cc
@@ -43,9 +43,9 @@
   std::vector<std::vector<std::vector<float>>> x(
       kNumBands, std::vector<std::vector<float>>(
                      kNumChannels, std::vector<float>(kBlockSize, 0.f)));
-  std::vector<float> y(kBlockSize, 0.f);
+  std::vector<std::vector<float>> y(1, std::vector<float>(kBlockSize, 0.f));
   std::array<float, kBlockSize> x_old;
-  SubtractorOutput output;
+  std::array<SubtractorOutput, 1> output;
   config.delay.default_delay = 1;
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
       RenderDelayBuffer::Create(config, kSampleRateHz, kNumChannels));
@@ -65,9 +65,9 @@
   for (int k = 0; k < num_blocks_to_process; ++k) {
     RandomizeSampleVector(&random_generator, x[0][0]);
     if (uncorrelated_inputs) {
-      RandomizeSampleVector(&random_generator, y);
+      RandomizeSampleVector(&random_generator, y[0]);
     } else {
-      delay_buffer.Delay(x[0][0], y);
+      delay_buffer.Delay(x[0][0], y[0]);
     }
     render_delay_buffer->Insert(x);
     if (k == 0) {
@@ -86,19 +86,21 @@
           false));
     }
     subtractor.Process(*render_delay_buffer->GetRenderBuffer(), y,
-                       render_signal_analyzer, aec_state, &output);
+                       render_signal_analyzer, aec_state, output);
 
     aec_state.HandleEchoPathChange(EchoPathVariability(
         false, EchoPathVariability::DelayAdjustment::kNone, false));
     aec_state.Update(delay_estimate, subtractor.FilterFrequencyResponse(),
                      subtractor.FilterImpulseResponse(),
                      *render_delay_buffer->GetRenderBuffer(), E2_main, Y2,
-                     output, y);
+                     output[0], y[0]);
   }
 
-  const float output_power = std::inner_product(
-      output.e_main.begin(), output.e_main.end(), output.e_main.begin(), 0.f);
-  const float y_power = std::inner_product(y.begin(), y.end(), y.begin(), 0.f);
+  const float output_power =
+      std::inner_product(output[0].e_main.begin(), output[0].e_main.end(),
+                         output[0].e_main.begin(), 0.f);
+  const float y_power =
+      std::inner_product(y[0].begin(), y[0].end(), y[0].begin(), 0.f);
   if (y_power == 0.f) {
     ADD_FAILURE();
     return -1.0;
@@ -124,24 +126,6 @@
       "");
 }
 
-// Verifies the check for null subtractor output.
-// TODO(peah): Re-enable the test once the issue with memory leaks during DEATH
-// tests on test bots has been fixed.
-TEST(Subtractor, DISABLED_NullOutput) {
-  ApmDataDumper data_dumper(42);
-  EchoCanceller3Config config;
-  Subtractor subtractor(config, 1, 1, &data_dumper, DetectOptimization());
-  std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(config, 48000, 1));
-  RenderSignalAnalyzer render_signal_analyzer(config);
-  std::vector<float> y(kBlockSize, 0.f);
-
-  EXPECT_DEATH(
-      subtractor.Process(*render_delay_buffer->GetRenderBuffer(), y,
-                         render_signal_analyzer, AecState(config), nullptr),
-      "");
-}
-
 // Verifies the check for the capture signal size.
 TEST(Subtractor, WrongCaptureSize) {
   ApmDataDumper data_dumper(42);
@@ -150,12 +134,12 @@
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
       RenderDelayBuffer::Create(config, 48000, 1));
   RenderSignalAnalyzer render_signal_analyzer(config);
-  std::vector<float> y(kBlockSize - 1, 0.f);
-  SubtractorOutput output;
+  std::vector<std::vector<float>> y(1, std::vector<float>(kBlockSize - 1, 0.f));
+  std::array<SubtractorOutput, 1> output;
 
   EXPECT_DEATH(
       subtractor.Process(*render_delay_buffer->GetRenderBuffer(), y,
-                         render_signal_analyzer, AecState(config), &output),
+                         render_signal_analyzer, AecState(config), output),
       "");
 }