Add core multi-channel pipeline in AEC3
This CL adds basic the basic pipeline to support multi-channel
processing in AEC3.

Apart from that, it removes the 8 kHz processing support in several
places of the AEC3 code.

Bug: webrtc:10913
Change-Id: If5b75fa325ed0071deea94a7546cb4a7adf22137
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/150332
Commit-Queue: Per Åhgren <peah@webrtc.org>
Reviewed-by: Sam Zackrisson <saza@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#29017}
diff --git a/api/audio/echo_canceller3_factory.cc b/api/audio/echo_canceller3_factory.cc
index e83e552..d8d39bc 100644
--- a/api/audio/echo_canceller3_factory.cc
+++ b/api/audio/echo_canceller3_factory.cc
@@ -22,6 +22,17 @@
     : config_(config) {}
 
 std::unique_ptr<EchoControl> EchoCanceller3Factory::Create(int sample_rate_hz) {
-  return absl::make_unique<EchoCanceller3>(config_, sample_rate_hz);
+  return absl::make_unique<EchoCanceller3>(config_, sample_rate_hz,
+                                           /*num_render_channels=*/1,
+                                           /*num_capture_channels=*/1);
 }
+
+std::unique_ptr<EchoControl> EchoCanceller3Factory::Create(
+    int sample_rate_hz,
+    size_t num_render_channels,
+    size_t num_capture_channels) {
+  return absl::make_unique<EchoCanceller3>(
+      config_, sample_rate_hz, num_render_channels, num_capture_channels);
+}
+
 }  // namespace webrtc
diff --git a/api/audio/echo_canceller3_factory.h b/api/audio/echo_canceller3_factory.h
index 9052d99..4637c45 100644
--- a/api/audio/echo_canceller3_factory.h
+++ b/api/audio/echo_canceller3_factory.h
@@ -28,9 +28,16 @@
   // configuration.
   explicit EchoCanceller3Factory(const EchoCanceller3Config& config);
 
-  // Creates an EchoCanceller3 running at the specified sampling rate.
+  // Creates an EchoCanceller3 running at the specified sampling rate using a
+  // mono setup
   std::unique_ptr<EchoControl> Create(int sample_rate_hz) override;
 
+  // Creates an EchoCanceller3 running at the specified sampling rate and a
+  // specified number of channels.
+  std::unique_ptr<EchoControl> Create(int sample_rate_hz,
+                                      size_t num_render_channels,
+                                      size_t num_capture_channels) override;
+
  private:
   const EchoCanceller3Config config_;
 };
diff --git a/api/audio/echo_control.h b/api/audio/echo_control.h
index f549f40..4496049 100644
--- a/api/audio/echo_control.h
+++ b/api/audio/echo_control.h
@@ -48,6 +48,11 @@
 class EchoControlFactory {
  public:
   virtual std::unique_ptr<EchoControl> Create(int sample_rate_hz) = 0;
+  virtual std::unique_ptr<EchoControl> Create(int sample_rate_hz,
+                                              size_t num_render_channels,
+                                              size_t num_capture_channels) {
+    return Create(sample_rate_hz);
+  }
   virtual ~EchoControlFactory() = default;
 };
 }  // namespace webrtc
diff --git a/modules/audio_processing/aec3/adaptive_fir_filter_unittest.cc b/modules/audio_processing/aec3/adaptive_fir_filter_unittest.cc
index 8215736..64d84cd 100644
--- a/modules/audio_processing/aec3/adaptive_fir_filter_unittest.cc
+++ b/modules/audio_processing/aec3/adaptive_fir_filter_unittest.cc
@@ -53,10 +53,17 @@
 // Verifies that the optimized methods for filter adaptation are similar to
 // their reference counterparts.
 TEST(AdaptiveFirFilter, FilterAdaptationNeonOptimizations) {
+  constexpr size_t kNumRenderChannels = 1;
+  constexpr int kSampleRateHz = 48000;
+  constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz);
+
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(EchoCanceller3Config(), 48000));
+      RenderDelayBuffer::Create(EchoCanceller3Config(), kSampleRateHz,
+                                kNumRenderChannels));
   Random random_generator(42U);
-  std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
+  std::vector<std::vector<std::vector<float>>> x(
+      kNumBands, std::vector<std::vector<float>>(
+                     kNumRenderChannels, std::vector<float>(kBlockSize, 0.f)));
   FftData S_C;
   FftData S_NEON;
   FftData G;
@@ -71,7 +78,11 @@
   }
 
   for (size_t k = 0; k < 30; ++k) {
-    RandomizeSampleVector(&random_generator, x[0]);
+    for (size_t band = 0; band < x.size(); ++band) {
+      for (size_t channel = 0; channel < x[band].size(); ++channel) {
+        RandomizeSampleVector(&random_generator, x[band][channel]);
+      }
+    }
     render_delay_buffer->Insert(x);
     if (k == 0) {
       render_delay_buffer->Reset();
@@ -162,12 +173,20 @@
 // Verifies that the optimized methods for filter adaptation are bitexact to
 // their reference counterparts.
 TEST(AdaptiveFirFilter, FilterAdaptationSse2Optimizations) {
+  constexpr size_t kNumRenderChannels = 1;
+  constexpr int kSampleRateHz = 48000;
+  constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz);
+
   bool use_sse2 = (WebRtc_GetCPUInfo(kSSE2) != 0);
   if (use_sse2) {
     std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-        RenderDelayBuffer::Create(EchoCanceller3Config(), 48000));
+        RenderDelayBuffer::Create(EchoCanceller3Config(), kSampleRateHz,
+                                  kNumRenderChannels));
     Random random_generator(42U);
-    std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
+    std::vector<std::vector<std::vector<float>>> x(
+        kNumBands,
+        std::vector<std::vector<float>>(kNumRenderChannels,
+                                        std::vector<float>(kBlockSize, 0.f)));
     FftData S_C;
     FftData S_SSE2;
     FftData G;
@@ -182,7 +201,11 @@
     }
 
     for (size_t k = 0; k < 500; ++k) {
-      RandomizeSampleVector(&random_generator, x[0]);
+      for (size_t band = 0; band < x.size(); ++band) {
+        for (size_t channel = 0; channel < x[band].size(); ++channel) {
+          RandomizeSampleVector(&random_generator, x[band][channel]);
+        }
+      }
       render_delay_buffer->Insert(x);
       if (k == 0) {
         render_delay_buffer->Reset();
@@ -281,7 +304,7 @@
   ApmDataDumper data_dumper(42);
   AdaptiveFirFilter filter(9, 9, 250, DetectOptimization(), &data_dumper);
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(EchoCanceller3Config(), 48000));
+      RenderDelayBuffer::Create(EchoCanceller3Config(), 48000, 1));
   EXPECT_DEATH(filter.Filter(*render_delay_buffer->GetRenderBuffer(), nullptr),
                "");
 }
@@ -310,6 +333,10 @@
 // Verifies that the filter is being able to properly filter a signal and to
 // adapt its coefficients.
 TEST(AdaptiveFirFilter, FilterAndAdapt) {
+  constexpr size_t kNumRenderChannels = 1;
+  constexpr int kSampleRateHz = 48000;
+  constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz);
+
   constexpr size_t kNumBlocksToProcess = 1000;
   ApmDataDumper data_dumper(42);
   EchoCanceller3Config config;
@@ -320,11 +347,13 @@
   Aec3Fft fft;
   config.delay.default_delay = 1;
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(config, 48000));
+      RenderDelayBuffer::Create(config, kSampleRateHz, kNumRenderChannels));
   ShadowFilterUpdateGain gain(config.filter.shadow,
                               config.filter.config_change_duration_blocks);
   Random random_generator(42U);
-  std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
+  std::vector<std::vector<std::vector<float>>> x(
+      kNumBands, std::vector<std::vector<float>>(
+                     kNumRenderChannels, std::vector<float>(kBlockSize, 0.f)));
   std::vector<float> n(kBlockSize, 0.f);
   std::vector<float> y(kBlockSize, 0.f);
   AecState aec_state(EchoCanceller3Config{});
@@ -357,15 +386,15 @@
 
     SCOPED_TRACE(ProduceDebugText(delay_samples));
     for (size_t j = 0; j < kNumBlocksToProcess; ++j) {
-      RandomizeSampleVector(&random_generator, x[0]);
-      delay_buffer.Delay(x[0], y);
+      RandomizeSampleVector(&random_generator, x[0][0]);
+      delay_buffer.Delay(x[0][0], y);
 
       RandomizeSampleVector(&random_generator, n);
       static constexpr float kNoiseScaling = 1.f / 100.f;
       std::transform(y.begin(), y.end(), n.begin(), y.begin(),
                      [](float a, float b) { return a + b * kNoiseScaling; });
 
-      x_hp_filter.Process(x[0]);
+      x_hp_filter.Process(x[0][0]);
       y_hp_filter.Process(y);
 
       render_delay_buffer->Insert(x);
diff --git a/modules/audio_processing/aec3/aec3_common.h b/modules/audio_processing/aec3/aec3_common.h
index 56c7a90..bf554e3 100644
--- a/modules/audio_processing/aec3/aec3_common.h
+++ b/modules/audio_processing/aec3/aec3_common.h
@@ -54,16 +54,12 @@
 
 // TODO(peah): Integrate this with how it is done inside audio_processing_impl.
 constexpr size_t NumBandsForRate(int sample_rate_hz) {
-  return static_cast<size_t>(sample_rate_hz == 8000 ? 1
-                                                    : sample_rate_hz / 16000);
-}
-constexpr int LowestBandRate(int sample_rate_hz) {
-  return sample_rate_hz == 8000 ? sample_rate_hz : 16000;
+  return static_cast<size_t>(sample_rate_hz / 16000);
 }
 
 constexpr bool ValidFullBandRate(int sample_rate_hz) {
-  return sample_rate_hz == 8000 || sample_rate_hz == 16000 ||
-         sample_rate_hz == 32000 || sample_rate_hz == 48000;
+  return sample_rate_hz == 16000 || sample_rate_hz == 32000 ||
+         sample_rate_hz == 48000;
 }
 
 constexpr int GetTimeDomainLength(int filter_length_blocks) {
@@ -100,21 +96,10 @@
 static_assert(1 << kFftLengthBy2Log2 == kFftLengthBy2,
               "Proper number of shifts for the fft length");
 
-static_assert(1 == NumBandsForRate(8000), "Number of bands for 8 kHz");
 static_assert(1 == NumBandsForRate(16000), "Number of bands for 16 kHz");
 static_assert(2 == NumBandsForRate(32000), "Number of bands for 32 kHz");
 static_assert(3 == NumBandsForRate(48000), "Number of bands for 48 kHz");
 
-static_assert(8000 == LowestBandRate(8000), "Sample rate of band 0 for 8 kHz");
-static_assert(16000 == LowestBandRate(16000),
-              "Sample rate of band 0 for 16 kHz");
-static_assert(16000 == LowestBandRate(32000),
-              "Sample rate of band 0 for 32 kHz");
-static_assert(16000 == LowestBandRate(48000),
-              "Sample rate of band 0 for 48 kHz");
-
-static_assert(ValidFullBandRate(8000),
-              "Test that 8 kHz is a valid sample rate");
 static_assert(ValidFullBandRate(16000),
               "Test that 16 kHz is a valid sample rate");
 static_assert(ValidFullBandRate(32000),
diff --git a/modules/audio_processing/aec3/aec_state.cc b/modules/audio_processing/aec3/aec_state.cc
index eab0094..566c62f 100644
--- a/modules/audio_processing/aec3/aec_state.cc
+++ b/modules/audio_processing/aec3/aec_state.cc
@@ -121,7 +121,7 @@
   }
 
   const std::vector<float>& aligned_render_block =
-      render_buffer.Block(-delay_state_.DirectPathFilterDelay())[0];
+      render_buffer.Block(-delay_state_.DirectPathFilterDelay())[0][0];
 
   // Update render counters.
   const float render_energy = std::inner_product(
diff --git a/modules/audio_processing/aec3/aec_state_unittest.cc b/modules/audio_processing/aec3/aec_state_unittest.cc
index bf47a05..4631eac 100644
--- a/modules/audio_processing/aec3/aec_state_unittest.cc
+++ b/modules/audio_processing/aec3/aec_state_unittest.cc
@@ -19,16 +19,21 @@
 
 // Verify the general functionality of AecState
 TEST(AecState, NormalUsage) {
+  constexpr size_t kNumChannels = 1;
+  constexpr int kSampleRateHz = 48000;
+  constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz);
   ApmDataDumper data_dumper(42);
   EchoCanceller3Config config;
   AecState state(config);
   absl::optional<DelayEstimate> delay_estimate =
       DelayEstimate(DelayEstimate::Quality::kRefined, 10);
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(config, 48000));
+      RenderDelayBuffer::Create(config, kSampleRateHz, kNumChannels));
   std::array<float, kFftLengthBy2Plus1> E2_main = {};
   std::array<float, kFftLengthBy2Plus1> Y2 = {};
-  std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
+  std::vector<std::vector<std::vector<float>>> x(
+      kNumBands, std::vector<std::vector<float>>(
+                     kNumChannels, std::vector<float>(kBlockSize, 0.f)));
   EchoPathVariability echo_path_variability(
       false, EchoPathVariability::DelayAdjustment::kNone, false);
   SubtractorOutput output;
@@ -53,7 +58,11 @@
       GetTimeDomainLength(config.filter.main.length_blocks), 0.f);
 
   // Verify that linear AEC usability is true when the filter is converged
-  std::fill(x[0].begin(), x[0].end(), 101.f);
+  for (size_t band = 0; band < kNumBands; ++band) {
+    for (size_t channel = 0; channel < kNumChannels; ++channel) {
+      std::fill(x[band][channel].begin(), x[band][channel].end(), 101.f);
+    }
+  }
   for (int k = 0; k < 3000; ++k) {
     render_delay_buffer->Insert(x);
     output.ComputeMetrics(y);
@@ -74,7 +83,7 @@
   EXPECT_FALSE(state.UsableLinearEstimate());
 
   // Verify that the active render detection works as intended.
-  std::fill(x[0].begin(), x[0].end(), 101.f);
+  std::fill(x[0][0].begin(), x[0][0].end(), 101.f);
   render_delay_buffer->Insert(x);
   output.ComputeMetrics(y);
   state.HandleEchoPathChange(EchoPathVariability(
@@ -94,11 +103,13 @@
   EXPECT_TRUE(state.ActiveRender());
 
   // Verify that the ERL is properly estimated
-  for (auto& x_k : x) {
-    x_k = std::vector<float>(kBlockSize, 0.f);
+  for (auto& band : x) {
+    for (auto& channel : band) {
+      channel = std::vector<float>(kBlockSize, 0.f);
+    }
   }
 
-  x[0][0] = 5000.f;
+  x[0][0][0] = 5000.f;
   for (size_t k = 0;
        k < render_delay_buffer->GetRenderBuffer()->GetFftBuffer().size(); ++k) {
     render_delay_buffer->Insert(x);
@@ -179,7 +190,7 @@
   EchoCanceller3Config config;
   AecState state(config);
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(config, 48000));
+      RenderDelayBuffer::Create(config, 48000, 1));
   absl::optional<DelayEstimate> delay_estimate;
   std::array<float, kFftLengthBy2Plus1> E2_main;
   std::array<float, kFftLengthBy2Plus1> Y2;
diff --git a/modules/audio_processing/aec3/block_delay_buffer_unittest.cc b/modules/audio_processing/aec3/block_delay_buffer_unittest.cc
index ec825ba..bda1821 100644
--- a/modules/audio_processing/aec3/block_delay_buffer_unittest.cc
+++ b/modules/audio_processing/aec3/block_delay_buffer_unittest.cc
@@ -50,10 +50,10 @@
 // Verifies that the correct signal delay is achived.
 TEST(BlockDelayBuffer, CorrectDelayApplied) {
   for (size_t delay : {0, 1, 27, 160, 4321, 7021}) {
-    for (auto rate : {8000, 16000, 32000, 48000}) {
+    for (auto rate : {16000, 32000, 48000}) {
       SCOPED_TRACE(ProduceDebugText(rate, delay));
       size_t num_bands = NumBandsForRate(rate);
-      size_t subband_frame_length = rate == 8000 ? 80 : 160;
+      size_t subband_frame_length = 160;
 
       BlockDelayBuffer delay_buffer(num_bands, subband_frame_length, delay);
 
diff --git a/modules/audio_processing/aec3/block_framer.cc b/modules/audio_processing/aec3/block_framer.cc
index ca7667c..8241ce6 100644
--- a/modules/audio_processing/aec3/block_framer.cc
+++ b/modules/audio_processing/aec3/block_framer.cc
@@ -17,9 +17,16 @@
 
 namespace webrtc {
 
-BlockFramer::BlockFramer(size_t num_bands)
+BlockFramer::BlockFramer(size_t num_bands, size_t num_channels)
     : num_bands_(num_bands),
-      buffer_(num_bands_, std::vector<float>(kBlockSize, 0.f)) {}
+      num_channels_(num_channels),
+      buffer_(num_bands_,
+              std::vector<std::vector<float>>(
+                  num_channels,
+                  std::vector<float>(kBlockSize, 0.f))) {
+  RTC_DCHECK_LT(0, num_bands);
+  RTC_DCHECK_LT(0, num_channels);
+}
 
 BlockFramer::~BlockFramer() = default;
 
@@ -27,33 +34,52 @@
 // samples for InsertBlockAndExtractSubFrame to produce a frame. In order to
 // achieve this, the InsertBlockAndExtractSubFrame and InsertBlock methods need
 // to be called in the correct order.
-void BlockFramer::InsertBlock(const std::vector<std::vector<float>>& block) {
+void BlockFramer::InsertBlock(
+    const std::vector<std::vector<std::vector<float>>>& block) {
   RTC_DCHECK_EQ(num_bands_, block.size());
-  for (size_t i = 0; i < num_bands_; ++i) {
-    RTC_DCHECK_EQ(kBlockSize, block[i].size());
-    RTC_DCHECK_EQ(0, buffer_[i].size());
-    buffer_[i].insert(buffer_[i].begin(), block[i].begin(), block[i].end());
+  for (size_t band = 0; band < num_bands_; ++band) {
+    RTC_DCHECK_EQ(num_channels_, block[band].size());
+    for (size_t channel = 0; channel < num_channels_; ++channel) {
+      RTC_DCHECK_EQ(kBlockSize, block[band][channel].size());
+      RTC_DCHECK_EQ(0, buffer_[band][channel].size());
+
+      buffer_[band][channel].insert(buffer_[band][channel].begin(),
+                                    block[band][channel].begin(),
+                                    block[band][channel].end());
+    }
   }
 }
 
 void BlockFramer::InsertBlockAndExtractSubFrame(
-    const std::vector<std::vector<float>>& block,
-    std::vector<rtc::ArrayView<float>>* sub_frame) {
+    const std::vector<std::vector<std::vector<float>>>& block,
+    std::vector<std::vector<rtc::ArrayView<float>>>* sub_frame) {
   RTC_DCHECK(sub_frame);
   RTC_DCHECK_EQ(num_bands_, block.size());
   RTC_DCHECK_EQ(num_bands_, sub_frame->size());
-  for (size_t i = 0; i < num_bands_; ++i) {
-    RTC_DCHECK_LE(kSubFrameLength, buffer_[i].size() + kBlockSize);
-    RTC_DCHECK_EQ(kBlockSize, block[i].size());
-    RTC_DCHECK_GE(kBlockSize, buffer_[i].size());
-    RTC_DCHECK_EQ(kSubFrameLength, (*sub_frame)[i].size());
-    const int samples_to_frame = kSubFrameLength - buffer_[i].size();
-    std::copy(buffer_[i].begin(), buffer_[i].end(), (*sub_frame)[i].begin());
-    std::copy(block[i].begin(), block[i].begin() + samples_to_frame,
-              (*sub_frame)[i].begin() + buffer_[i].size());
-    buffer_[i].clear();
-    buffer_[i].insert(buffer_[i].begin(), block[i].begin() + samples_to_frame,
-                      block[i].end());
+  for (size_t band = 0; band < num_bands_; ++band) {
+    RTC_DCHECK_EQ(num_channels_, block[band].size());
+    RTC_DCHECK_EQ(num_channels_, (*sub_frame)[0].size());
+    for (size_t channel = 0; channel < num_channels_; ++channel) {
+      RTC_DCHECK_LE(kSubFrameLength,
+                    buffer_[band][channel].size() + kBlockSize);
+      RTC_DCHECK_EQ(kBlockSize, block[band][channel].size());
+      RTC_DCHECK_GE(kBlockSize, buffer_[band][channel].size());
+      RTC_DCHECK_EQ(kSubFrameLength, (*sub_frame)[band][channel].size());
+
+      const int samples_to_frame =
+          kSubFrameLength - buffer_[band][channel].size();
+      std::copy(buffer_[band][channel].begin(), buffer_[band][channel].end(),
+                (*sub_frame)[band][channel].begin());
+      std::copy(
+          block[band][channel].begin(),
+          block[band][channel].begin() + samples_to_frame,
+          (*sub_frame)[band][channel].begin() + buffer_[band][channel].size());
+      buffer_[band][channel].clear();
+      buffer_[band][channel].insert(
+          buffer_[band][channel].begin(),
+          block[band][channel].begin() + samples_to_frame,
+          block[band][channel].end());
+    }
   }
 }
 
diff --git a/modules/audio_processing/aec3/block_framer.h b/modules/audio_processing/aec3/block_framer.h
index fae4b29..1d37866 100644
--- a/modules/audio_processing/aec3/block_framer.h
+++ b/modules/audio_processing/aec3/block_framer.h
@@ -15,11 +15,10 @@
 
 #include "api/array_view.h"
 #include "modules/audio_processing/aec3/aec3_common.h"
-#include "rtc_base/constructor_magic.h"
 
 namespace webrtc {
 
-// Class for producing frames consisting of 1 or 2 subframes of 80 samples each
+// Class for producing frames consisting of 2 subframes of 80 samples each
 // from 64 sample blocks. The class is designed to work together with the
 // FrameBlocker class which performs the reverse conversion. Used together with
 // that, this class produces output frames are the same rate as frames are
@@ -27,20 +26,22 @@
 // overrun if any other rate of packets insertion is used.
 class BlockFramer {
  public:
-  explicit BlockFramer(size_t num_bands);
+  BlockFramer(size_t num_bands, size_t num_channels);
   ~BlockFramer();
+  BlockFramer(const BlockFramer&) = delete;
+  BlockFramer& operator=(const BlockFramer&) = delete;
+
   // Adds a 64 sample block into the data that will form the next output frame.
-  void InsertBlock(const std::vector<std::vector<float>>& block);
+  void InsertBlock(const std::vector<std::vector<std::vector<float>>>& block);
   // Adds a 64 sample block and extracts an 80 sample subframe.
   void InsertBlockAndExtractSubFrame(
-      const std::vector<std::vector<float>>& block,
-      std::vector<rtc::ArrayView<float>>* sub_frame);
+      const std::vector<std::vector<std::vector<float>>>& block,
+      std::vector<std::vector<rtc::ArrayView<float>>>* sub_frame);
 
  private:
   const size_t num_bands_;
-  std::vector<std::vector<float>> buffer_;
-
-  RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(BlockFramer);
+  const size_t num_channels_;
+  std::vector<std::vector<std::vector<float>>> buffer_;
 };
 }  // namespace webrtc
 
diff --git a/modules/audio_processing/aec3/block_framer_unittest.cc b/modules/audio_processing/aec3/block_framer_unittest.cc
index 9baade9..e9a16d0 100644
--- a/modules/audio_processing/aec3/block_framer_unittest.cc
+++ b/modules/audio_processing/aec3/block_framer_unittest.cc
@@ -20,66 +20,87 @@
 namespace webrtc {
 namespace {
 
-void SetupSubFrameView(std::vector<std::vector<float>>* sub_frame,
-                       std::vector<rtc::ArrayView<float>>* sub_frame_view) {
-  for (size_t k = 0; k < sub_frame_view->size(); ++k) {
-    (*sub_frame_view)[k] =
-        rtc::ArrayView<float>((*sub_frame)[k].data(), (*sub_frame)[k].size());
+void SetupSubFrameView(
+    std::vector<std::vector<std::vector<float>>>* sub_frame,
+    std::vector<std::vector<rtc::ArrayView<float>>>* sub_frame_view) {
+  for (size_t band = 0; band < sub_frame_view->size(); ++band) {
+    for (size_t channel = 0; channel < (*sub_frame_view)[band].size();
+         ++channel) {
+      (*sub_frame_view)[band][channel] =
+          rtc::ArrayView<float>((*sub_frame)[band][channel].data(),
+                                (*sub_frame)[band][channel].size());
+    }
   }
 }
 
 float ComputeSampleValue(size_t chunk_counter,
                          size_t chunk_size,
                          size_t band,
+                         size_t channel,
                          size_t sample_index,
                          int offset) {
-  float value =
-      static_cast<int>(chunk_counter * chunk_size + sample_index) + offset;
-  return value > 0 ? 5000 * band + value : 0;
+  float value = static_cast<int>(100 + chunk_counter * chunk_size +
+                                 sample_index + channel) +
+                offset;
+  return 5000 * band + value;
 }
 
-bool VerifySubFrame(size_t sub_frame_counter,
-                    int offset,
-                    const std::vector<rtc::ArrayView<float>>& sub_frame_view) {
-  for (size_t k = 0; k < sub_frame_view.size(); ++k) {
-    for (size_t i = 0; i < sub_frame_view[k].size(); ++i) {
-      const float reference_value =
-          ComputeSampleValue(sub_frame_counter, kSubFrameLength, k, i, offset);
-      if (reference_value != sub_frame_view[k][i]) {
-        return false;
+bool VerifySubFrame(
+    size_t sub_frame_counter,
+    int offset,
+    const std::vector<std::vector<rtc::ArrayView<float>>>& sub_frame_view) {
+  for (size_t band = 0; band < sub_frame_view.size(); ++band) {
+    for (size_t channel = 0; channel < sub_frame_view[band].size(); ++channel) {
+      for (size_t sample = 0; sample < sub_frame_view[band][channel].size();
+           ++sample) {
+        const float reference_value = ComputeSampleValue(
+            sub_frame_counter, kSubFrameLength, band, channel, sample, offset);
+        if (reference_value != sub_frame_view[band][channel][sample]) {
+          return false;
+        }
       }
     }
   }
   return true;
 }
 
-void FillBlock(size_t block_counter, std::vector<std::vector<float>>* block) {
-  for (size_t k = 0; k < block->size(); ++k) {
-    for (size_t i = 0; i < (*block)[0].size(); ++i) {
-      (*block)[k][i] = ComputeSampleValue(block_counter, kBlockSize, k, i, 0);
+void FillBlock(size_t block_counter,
+               std::vector<std::vector<std::vector<float>>>* block) {
+  for (size_t band = 0; band < block->size(); ++band) {
+    for (size_t channel = 0; channel < (*block)[band].size(); ++channel) {
+      for (size_t sample = 0; sample < (*block)[band][channel].size();
+           ++sample) {
+        (*block)[band][channel][sample] = ComputeSampleValue(
+            block_counter, kBlockSize, band, channel, sample, 0);
+      }
     }
   }
 }
 
 // Verifies that the BlockFramer is able to produce the expected frame content.
-void RunFramerTest(int sample_rate_hz) {
-  constexpr size_t kNumSubFramesToProcess = 2;
+void RunFramerTest(int sample_rate_hz, size_t num_channels) {
+  constexpr size_t kNumSubFramesToProcess = 10;
   const size_t num_bands = NumBandsForRate(sample_rate_hz);
 
-  std::vector<std::vector<float>> block(num_bands,
-                                        std::vector<float>(kBlockSize, 0.f));
-  std::vector<std::vector<float>> output_sub_frame(
-      num_bands, std::vector<float>(kSubFrameLength, 0.f));
-  std::vector<rtc::ArrayView<float>> output_sub_frame_view(num_bands);
+  std::vector<std::vector<std::vector<float>>> block(
+      num_bands, std::vector<std::vector<float>>(
+                     num_channels, std::vector<float>(kBlockSize, 0.f)));
+  std::vector<std::vector<std::vector<float>>> output_sub_frame(
+      num_bands, std::vector<std::vector<float>>(
+                     num_channels, std::vector<float>(kSubFrameLength, 0.f)));
+  std::vector<std::vector<rtc::ArrayView<float>>> output_sub_frame_view(
+      num_bands, std::vector<rtc::ArrayView<float>>(num_channels));
   SetupSubFrameView(&output_sub_frame, &output_sub_frame_view);
-  BlockFramer framer(num_bands);
+  BlockFramer framer(num_bands, num_channels);
 
   size_t block_index = 0;
   for (size_t sub_frame_index = 0; sub_frame_index < kNumSubFramesToProcess;
        ++sub_frame_index) {
     FillBlock(block_index++, &block);
     framer.InsertBlockAndExtractSubFrame(block, &output_sub_frame_view);
-    EXPECT_TRUE(VerifySubFrame(sub_frame_index, -64, output_sub_frame_view));
+    if (sub_frame_index > 1) {
+      EXPECT_TRUE(VerifySubFrame(sub_frame_index, -64, output_sub_frame_view));
+    }
 
     if ((sub_frame_index + 1) % 4 == 0) {
       FillBlock(block_index++, &block);
@@ -91,21 +112,30 @@
 #if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
 // Verifies that the BlockFramer crashes if the InsertBlockAndExtractSubFrame
 // method is called for inputs with the wrong number of bands or band lengths.
-void RunWronglySizedInsertAndExtractParametersTest(int sample_rate_hz,
-                                                   size_t num_block_bands,
-                                                   size_t block_length,
-                                                   size_t num_sub_frame_bands,
-                                                   size_t sub_frame_length) {
+void RunWronglySizedInsertAndExtractParametersTest(
+    int sample_rate_hz,
+    size_t correct_num_channels,
+    size_t num_block_bands,
+    size_t num_block_channels,
+    size_t block_length,
+    size_t num_sub_frame_bands,
+    size_t num_sub_frame_channels,
+    size_t sub_frame_length) {
   const size_t correct_num_bands = NumBandsForRate(sample_rate_hz);
 
-  std::vector<std::vector<float>> block(num_block_bands,
-                                        std::vector<float>(block_length, 0.f));
-  std::vector<std::vector<float>> output_sub_frame(
-      num_sub_frame_bands, std::vector<float>(sub_frame_length, 0.f));
-  std::vector<rtc::ArrayView<float>> output_sub_frame_view(
-      output_sub_frame.size());
+  std::vector<std::vector<std::vector<float>>> block(
+      num_block_bands,
+      std::vector<std::vector<float>>(num_block_channels,
+                                      std::vector<float>(block_length, 0.f)));
+  std::vector<std::vector<std::vector<float>>> output_sub_frame(
+      num_sub_frame_bands,
+      std::vector<std::vector<float>>(
+          num_sub_frame_channels, std::vector<float>(sub_frame_length, 0.f)));
+  std::vector<std::vector<rtc::ArrayView<float>>> output_sub_frame_view(
+      output_sub_frame.size(),
+      std::vector<rtc::ArrayView<float>>(num_sub_frame_channels));
   SetupSubFrameView(&output_sub_frame, &output_sub_frame_view);
-  BlockFramer framer(correct_num_bands);
+  BlockFramer framer(correct_num_bands, correct_num_channels);
   EXPECT_DEATH(
       framer.InsertBlockAndExtractSubFrame(block, &output_sub_frame_view), "");
 }
@@ -113,20 +143,29 @@
 // Verifies that the BlockFramer crashes if the InsertBlock method is called for
 // inputs with the wrong number of bands or band lengths.
 void RunWronglySizedInsertParameterTest(int sample_rate_hz,
+                                        size_t correct_num_channels,
                                         size_t num_block_bands,
+                                        size_t num_block_channels,
                                         size_t block_length) {
   const size_t correct_num_bands = NumBandsForRate(sample_rate_hz);
 
-  std::vector<std::vector<float>> correct_block(
-      correct_num_bands, std::vector<float>(kBlockSize, 0.f));
-  std::vector<std::vector<float>> wrong_block(
-      num_block_bands, std::vector<float>(block_length, 0.f));
-  std::vector<std::vector<float>> output_sub_frame(
-      correct_num_bands, std::vector<float>(kSubFrameLength, 0.f));
-  std::vector<rtc::ArrayView<float>> output_sub_frame_view(
-      output_sub_frame.size());
+  std::vector<std::vector<std::vector<float>>> correct_block(
+      correct_num_bands,
+      std::vector<std::vector<float>>(correct_num_channels,
+                                      std::vector<float>(kBlockSize, 0.f)));
+  std::vector<std::vector<std::vector<float>>> wrong_block(
+      num_block_bands,
+      std::vector<std::vector<float>>(num_block_channels,
+                                      std::vector<float>(block_length, 0.f)));
+  std::vector<std::vector<std::vector<float>>> output_sub_frame(
+      correct_num_bands,
+      std::vector<std::vector<float>>(
+          correct_num_channels, std::vector<float>(kSubFrameLength, 0.f)));
+  std::vector<std::vector<rtc::ArrayView<float>>> output_sub_frame_view(
+      output_sub_frame.size(),
+      std::vector<rtc::ArrayView<float>>(correct_num_channels));
   SetupSubFrameView(&output_sub_frame, &output_sub_frame_view);
-  BlockFramer framer(correct_num_bands);
+  BlockFramer framer(correct_num_bands, correct_num_channels);
   framer.InsertBlockAndExtractSubFrame(correct_block, &output_sub_frame_view);
   framer.InsertBlockAndExtractSubFrame(correct_block, &output_sub_frame_view);
   framer.InsertBlockAndExtractSubFrame(correct_block, &output_sub_frame_view);
@@ -138,18 +177,25 @@
 // Verifies that the BlockFramer crashes if the InsertBlock method is called
 // after a wrong number of previous InsertBlockAndExtractSubFrame method calls
 // have been made.
+
 void RunWronglyInsertOrderTest(int sample_rate_hz,
+                               size_t num_channels,
                                size_t num_preceeding_api_calls) {
   const size_t correct_num_bands = NumBandsForRate(sample_rate_hz);
 
-  std::vector<std::vector<float>> block(correct_num_bands,
-                                        std::vector<float>(kBlockSize, 0.f));
-  std::vector<std::vector<float>> output_sub_frame(
-      correct_num_bands, std::vector<float>(kSubFrameLength, 0.f));
-  std::vector<rtc::ArrayView<float>> output_sub_frame_view(
-      output_sub_frame.size());
+  std::vector<std::vector<std::vector<float>>> block(
+      correct_num_bands,
+      std::vector<std::vector<float>>(num_channels,
+                                      std::vector<float>(kBlockSize, 0.f)));
+  std::vector<std::vector<std::vector<float>>> output_sub_frame(
+      correct_num_bands,
+      std::vector<std::vector<float>>(
+          num_channels, std::vector<float>(kSubFrameLength, 0.f)));
+  std::vector<std::vector<rtc::ArrayView<float>>> output_sub_frame_view(
+      output_sub_frame.size(),
+      std::vector<rtc::ArrayView<float>>(num_channels));
   SetupSubFrameView(&output_sub_frame, &output_sub_frame_view);
-  BlockFramer framer(correct_num_bands);
+  BlockFramer framer(correct_num_bands, num_channels);
   for (size_t k = 0; k < num_preceeding_api_calls; ++k) {
     framer.InsertBlockAndExtractSubFrame(block, &output_sub_frame_view);
   }
@@ -158,9 +204,10 @@
 }
 #endif
 
-std::string ProduceDebugText(int sample_rate_hz) {
+std::string ProduceDebugText(int sample_rate_hz, size_t num_channels) {
   rtc::StringBuilder ss;
   ss << "Sample rate: " << sample_rate_hz;
+  ss << ", number of channels: " << num_channels;
   return ss.Release();
 }
 
@@ -168,83 +215,157 @@
 
 #if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
 TEST(BlockFramer, WrongNumberOfBandsInBlockForInsertBlockAndExtractSubFrame) {
-  for (auto rate : {8000, 16000, 32000, 48000}) {
-    SCOPED_TRACE(ProduceDebugText(rate));
-    const size_t correct_num_bands = NumBandsForRate(rate);
-    const size_t wrong_num_bands = (correct_num_bands % 3) + 1;
-    RunWronglySizedInsertAndExtractParametersTest(
-        rate, wrong_num_bands, kBlockSize, correct_num_bands, kSubFrameLength);
+  for (auto rate : {16000, 32000, 48000}) {
+    for (auto correct_num_channels : {1, 2, 8}) {
+      SCOPED_TRACE(ProduceDebugText(rate, correct_num_channels));
+      const size_t correct_num_bands = NumBandsForRate(rate);
+      const size_t wrong_num_bands = (correct_num_bands % 3) + 1;
+      RunWronglySizedInsertAndExtractParametersTest(
+          rate, correct_num_channels, wrong_num_bands, correct_num_channels,
+          kBlockSize, correct_num_bands, correct_num_channels, kSubFrameLength);
+    }
+  }
+}
+
+TEST(BlockFramer,
+     WrongNumberOfChannelsInBlockForInsertBlockAndExtractSubFrame) {
+  for (auto rate : {16000, 32000, 48000}) {
+    for (auto correct_num_channels : {1, 2, 8}) {
+      SCOPED_TRACE(ProduceDebugText(rate, correct_num_channels));
+      const size_t correct_num_bands = NumBandsForRate(rate);
+      const size_t wrong_num_channels = correct_num_channels + 1;
+      RunWronglySizedInsertAndExtractParametersTest(
+          rate, correct_num_channels, correct_num_bands, wrong_num_channels,
+          kBlockSize, correct_num_bands, correct_num_channels, kSubFrameLength);
+    }
   }
 }
 
 TEST(BlockFramer,
      WrongNumberOfBandsInSubFrameForInsertBlockAndExtractSubFrame) {
-  for (auto rate : {8000, 16000, 32000, 48000}) {
-    SCOPED_TRACE(ProduceDebugText(rate));
-    const size_t correct_num_bands = NumBandsForRate(rate);
-    const size_t wrong_num_bands = (correct_num_bands % 3) + 1;
-    RunWronglySizedInsertAndExtractParametersTest(
-        rate, correct_num_bands, kBlockSize, wrong_num_bands, kSubFrameLength);
+  for (auto rate : {16000, 32000, 48000}) {
+    for (auto correct_num_channels : {1, 2, 8}) {
+      SCOPED_TRACE(ProduceDebugText(rate, correct_num_channels));
+      const size_t correct_num_bands = NumBandsForRate(rate);
+      const size_t wrong_num_bands = (correct_num_bands % 3) + 1;
+      RunWronglySizedInsertAndExtractParametersTest(
+          rate, correct_num_channels, correct_num_bands, correct_num_channels,
+          kBlockSize, wrong_num_bands, correct_num_channels, kSubFrameLength);
+    }
+  }
+}
+
+TEST(BlockFramer,
+     WrongNumberOfChannelsInSubFrameForInsertBlockAndExtractSubFrame) {
+  for (auto rate : {16000, 32000, 48000}) {
+    for (auto correct_num_channels : {1, 2, 8}) {
+      SCOPED_TRACE(ProduceDebugText(rate, correct_num_channels));
+      const size_t correct_num_bands = NumBandsForRate(rate);
+      const size_t wrong_num_channels = correct_num_channels + 1;
+      RunWronglySizedInsertAndExtractParametersTest(
+          rate, correct_num_channels, correct_num_bands, correct_num_channels,
+          kBlockSize, correct_num_bands, wrong_num_channels, kSubFrameLength);
+    }
   }
 }
 
 TEST(BlockFramer, WrongNumberOfSamplesInBlockForInsertBlockAndExtractSubFrame) {
-  for (auto rate : {8000, 16000, 32000, 48000}) {
-    SCOPED_TRACE(ProduceDebugText(rate));
-    const size_t correct_num_bands = NumBandsForRate(rate);
-    RunWronglySizedInsertAndExtractParametersTest(
-        rate, correct_num_bands, kBlockSize - 1, correct_num_bands,
-        kSubFrameLength);
+  for (auto rate : {16000, 32000, 48000}) {
+    for (auto correct_num_channels : {1, 2, 8}) {
+      SCOPED_TRACE(ProduceDebugText(rate, correct_num_channels));
+      const size_t correct_num_bands = NumBandsForRate(rate);
+      RunWronglySizedInsertAndExtractParametersTest(
+          rate, correct_num_channels, correct_num_bands, correct_num_channels,
+          kBlockSize - 1, correct_num_bands, correct_num_channels,
+          kSubFrameLength);
+    }
   }
 }
 
 TEST(BlockFramer,
      WrongNumberOfSamplesInSubFrameForInsertBlockAndExtractSubFrame) {
-  for (auto rate : {8000, 16000, 32000, 48000}) {
-    SCOPED_TRACE(ProduceDebugText(rate));
+  const size_t correct_num_channels = 1;
+  for (auto rate : {16000, 32000, 48000}) {
+    SCOPED_TRACE(ProduceDebugText(rate, correct_num_channels));
     const size_t correct_num_bands = NumBandsForRate(rate);
-    RunWronglySizedInsertAndExtractParametersTest(rate, correct_num_bands,
-                                                  kBlockSize, correct_num_bands,
-                                                  kSubFrameLength - 1);
+    RunWronglySizedInsertAndExtractParametersTest(
+        rate, correct_num_channels, correct_num_bands, correct_num_channels,
+        kBlockSize, correct_num_bands, correct_num_channels,
+        kSubFrameLength - 1);
   }
 }
 
 TEST(BlockFramer, WrongNumberOfBandsInBlockForInsertBlock) {
-  for (auto rate : {8000, 16000, 32000, 48000}) {
-    SCOPED_TRACE(ProduceDebugText(rate));
-    const size_t correct_num_bands = NumBandsForRate(rate);
-    const size_t wrong_num_bands = (correct_num_bands % 3) + 1;
-    RunWronglySizedInsertParameterTest(rate, wrong_num_bands, kBlockSize);
-  }
-}
-
-TEST(BlockFramer, WrongNumberOfSamplesInBlockForInsertBlock) {
-  for (auto rate : {8000, 16000, 32000, 48000}) {
-    SCOPED_TRACE(ProduceDebugText(rate));
-    const size_t correct_num_bands = NumBandsForRate(rate);
-    RunWronglySizedInsertParameterTest(rate, correct_num_bands, kBlockSize - 1);
-  }
-}
-
-TEST(BlockFramer, WrongNumberOfPreceedingApiCallsForInsertBlock) {
-  for (auto rate : {8000, 16000, 32000, 48000}) {
-    for (size_t num_calls = 0; num_calls < 4; ++num_calls) {
-      rtc::StringBuilder ss;
-      ss << "Sample rate: " << rate;
-      ss << ", Num preceeding InsertBlockAndExtractSubFrame calls: "
-         << num_calls;
-
-      SCOPED_TRACE(ss.str());
-      RunWronglyInsertOrderTest(rate, num_calls);
+  for (auto rate : {16000, 32000, 48000}) {
+    for (auto correct_num_channels : {1, 2, 8}) {
+      SCOPED_TRACE(ProduceDebugText(rate, correct_num_channels));
+      const size_t correct_num_bands = NumBandsForRate(rate);
+      const size_t wrong_num_bands = (correct_num_bands % 3) + 1;
+      RunWronglySizedInsertParameterTest(rate, correct_num_channels,
+                                         wrong_num_bands, correct_num_channels,
+                                         kBlockSize);
     }
   }
 }
 
-// Verifiers that the verification for null sub_frame pointer works.
+TEST(BlockFramer, WrongNumberOfChannelsInBlockForInsertBlock) {
+  for (auto rate : {16000, 32000, 48000}) {
+    for (auto correct_num_channels : {1, 2, 8}) {
+      SCOPED_TRACE(ProduceDebugText(rate, correct_num_channels));
+      const size_t correct_num_bands = NumBandsForRate(rate);
+      const size_t wrong_num_channels = correct_num_channels + 1;
+      RunWronglySizedInsertParameterTest(rate, correct_num_channels,
+                                         correct_num_bands, wrong_num_channels,
+                                         kBlockSize);
+    }
+  }
+}
+
+TEST(BlockFramer, WrongNumberOfSamplesInBlockForInsertBlock) {
+  for (auto rate : {16000, 32000, 48000}) {
+    for (auto correct_num_channels : {1, 2, 8}) {
+      SCOPED_TRACE(ProduceDebugText(rate, correct_num_channels));
+      const size_t correct_num_bands = NumBandsForRate(rate);
+      RunWronglySizedInsertParameterTest(rate, correct_num_channels,
+                                         correct_num_bands,
+                                         correct_num_channels, kBlockSize - 1);
+    }
+  }
+}
+
+TEST(BlockFramer, WrongNumberOfPreceedingApiCallsForInsertBlock) {
+  for (size_t num_channels : {1, 2, 8}) {
+    for (auto rate : {16000, 32000, 48000}) {
+      for (size_t num_calls = 0; num_calls < 4; ++num_calls) {
+        rtc::StringBuilder ss;
+        ss << "Sample rate: " << rate;
+        ss << ", Num channels: " << num_channels;
+        ss << ", Num preceeding InsertBlockAndExtractSubFrame calls: "
+           << num_calls;
+
+        SCOPED_TRACE(ss.str());
+        RunWronglyInsertOrderTest(rate, num_channels, num_calls);
+      }
+    }
+  }
+}
+
+// Verifies that the verification for 0 number of channels works.
+TEST(BlockFramer, ZeroNumberOfChannelsParameter) {
+  EXPECT_DEATH(BlockFramer(16000, 0), "");
+}
+
+// Verifies that the verification for 0 number of bands works.
+TEST(BlockFramer, ZeroNumberOfBandsParameter) {
+  EXPECT_DEATH(BlockFramer(0, 1), "");
+}
+
+// Verifies that the verification for null sub_frame pointer works.
 TEST(BlockFramer, NullSubFrameParameter) {
-  EXPECT_DEATH(BlockFramer(1).InsertBlockAndExtractSubFrame(
-                   std::vector<std::vector<float>>(
-                       1, std::vector<float>(kBlockSize, 0.f)),
+  EXPECT_DEATH(BlockFramer(1, 1).InsertBlockAndExtractSubFrame(
+                   std::vector<std::vector<std::vector<float>>>(
+                       1, std::vector<std::vector<float>>(
+                              1, std::vector<float>(kBlockSize, 0.f))),
                    nullptr),
                "");
 }
@@ -252,9 +373,11 @@
 #endif
 
 TEST(BlockFramer, FrameBitexactness) {
-  for (auto rate : {8000, 16000, 32000, 48000}) {
-    SCOPED_TRACE(ProduceDebugText(rate));
-    RunFramerTest(rate);
+  for (auto rate : {16000, 32000, 48000}) {
+    for (auto num_channels : {1, 2, 4, 8}) {
+      SCOPED_TRACE(ProduceDebugText(rate, num_channels));
+      RunFramerTest(rate, num_channels);
+    }
   }
 }
 
diff --git a/modules/audio_processing/aec3/block_processor.cc b/modules/audio_processing/aec3/block_processor.cc
index 184248f..33b6b9b 100644
--- a/modules/audio_processing/aec3/block_processor.cc
+++ b/modules/audio_processing/aec3/block_processor.cc
@@ -39,6 +39,8 @@
  public:
   BlockProcessorImpl(const EchoCanceller3Config& config,
                      int sample_rate_hz,
+                     size_t num_render_channels,
+                     size_t num_capture_channels,
                      std::unique_ptr<RenderDelayBuffer> render_buffer,
                      std::unique_ptr<RenderDelayController> delay_controller,
                      std::unique_ptr<EchoRemover> echo_remover);
@@ -47,11 +49,13 @@
 
   ~BlockProcessorImpl() override;
 
-  void ProcessCapture(bool echo_path_gain_change,
-                      bool capture_signal_saturation,
-                      std::vector<std::vector<float>>* capture_block) override;
+  void ProcessCapture(
+      bool echo_path_gain_change,
+      bool capture_signal_saturation,
+      std::vector<std::vector<std::vector<float>>>* capture_block) override;
 
-  void BufferRender(const std::vector<std::vector<float>>& block) override;
+  void BufferRender(
+      const std::vector<std::vector<std::vector<float>>>& block) override;
 
   void UpdateEchoLeakageStatus(bool leakage_detected) override;
 
@@ -80,6 +84,8 @@
 BlockProcessorImpl::BlockProcessorImpl(
     const EchoCanceller3Config& config,
     int sample_rate_hz,
+    size_t num_render_channels,
+    size_t num_capture_channels,
     std::unique_ptr<RenderDelayBuffer> render_buffer,
     std::unique_ptr<RenderDelayController> delay_controller,
     std::unique_ptr<EchoRemover> echo_remover)
@@ -99,18 +105,17 @@
 void BlockProcessorImpl::ProcessCapture(
     bool echo_path_gain_change,
     bool capture_signal_saturation,
-    std::vector<std::vector<float>>* capture_block) {
+    std::vector<std::vector<std::vector<float>>>* capture_block) {
   RTC_DCHECK(capture_block);
   RTC_DCHECK_EQ(NumBandsForRate(sample_rate_hz_), capture_block->size());
-  RTC_DCHECK_EQ(kBlockSize, (*capture_block)[0].size());
+  RTC_DCHECK_EQ(kBlockSize, (*capture_block)[0][0].size());
 
   capture_call_counter_++;
 
   data_dumper_->DumpRaw("aec3_processblock_call_order",
                         static_cast<int>(BlockProcessorApiCall::kCapture));
   data_dumper_->DumpWav("aec3_processblock_capture_input", kBlockSize,
-                        &(*capture_block)[0][0],
-                        LowestBandRate(sample_rate_hz_), 1);
+                        &(*capture_block)[0][0][0], 16000, 1);
 
   if (render_properly_started_) {
     if (!capture_properly_started_) {
@@ -151,8 +156,7 @@
   }
 
   data_dumper_->DumpWav("aec3_processblock_capture_input2", kBlockSize,
-                        &(*capture_block)[0][0],
-                        LowestBandRate(sample_rate_hz_), 1);
+                        &(*capture_block)[0][0][0], 16000, 1);
 
   bool has_delay_estimator = !config_.delay.use_external_delay_estimator;
   if (has_delay_estimator) {
@@ -161,7 +165,7 @@
     // alignment.
     estimated_delay_ = delay_controller_->GetDelay(
         render_buffer_->GetDownsampledRenderBuffer(), render_buffer_->Delay(),
-        (*capture_block)[0]);
+        (*capture_block)[0][0]);
 
     if (estimated_delay_) {
       bool delay_change =
@@ -192,15 +196,15 @@
 }
 
 void BlockProcessorImpl::BufferRender(
-    const std::vector<std::vector<float>>& block) {
+    const std::vector<std::vector<std::vector<float>>>& block) {
   RTC_DCHECK_EQ(NumBandsForRate(sample_rate_hz_), block.size());
-  RTC_DCHECK_EQ(kBlockSize, block[0].size());
+  RTC_DCHECK_EQ(kBlockSize, block[0][0].size());
   data_dumper_->DumpRaw("aec3_processblock_call_order",
                         static_cast<int>(BlockProcessorApiCall::kRender));
   data_dumper_->DumpWav("aec3_processblock_render_input", kBlockSize,
-                        &block[0][0], LowestBandRate(sample_rate_hz_), 1);
+                        &block[0][0][0], 16000, 1);
   data_dumper_->DumpWav("aec3_processblock_render_input2", kBlockSize,
-                        &block[0][0], LowestBandRate(sample_rate_hz_), 1);
+                        &block[0][0][0], 16000, 1);
 
   render_event_ = render_buffer_->Insert(block);
 
@@ -218,7 +222,7 @@
 
 void BlockProcessorImpl::GetMetrics(EchoControl::Metrics* metrics) const {
   echo_remover_->GetMetrics(metrics);
-  const int block_size_ms = sample_rate_hz_ == 8000 ? 8 : 4;
+  constexpr int block_size_ms = 4;
   absl::optional<size_t> delay = render_buffer_->Delay();
   metrics->delay_ms = delay ? static_cast<int>(*delay) * block_size_ms : 0;
 }
@@ -230,44 +234,53 @@
 }  // namespace
 
 BlockProcessor* BlockProcessor::Create(const EchoCanceller3Config& config,
-                                       int sample_rate_hz) {
+                                       int sample_rate_hz,
+                                       size_t num_render_channels,
+                                       size_t num_capture_channels) {
   std::unique_ptr<RenderDelayBuffer> render_buffer(
-      RenderDelayBuffer::Create(config, sample_rate_hz));
+      RenderDelayBuffer::Create(config, sample_rate_hz, num_render_channels));
   std::unique_ptr<RenderDelayController> delay_controller;
   if (!config.delay.use_external_delay_estimator) {
     delay_controller.reset(
         RenderDelayController::Create(config, sample_rate_hz));
   }
-  std::unique_ptr<EchoRemover> echo_remover(
-      EchoRemover::Create(config, sample_rate_hz));
-  return Create(config, sample_rate_hz, std::move(render_buffer),
+  std::unique_ptr<EchoRemover> echo_remover(EchoRemover::Create(
+      config, sample_rate_hz, num_render_channels, num_capture_channels));
+  return Create(config, sample_rate_hz, num_render_channels,
+                num_capture_channels, std::move(render_buffer),
                 std::move(delay_controller), std::move(echo_remover));
 }
 
 BlockProcessor* BlockProcessor::Create(
     const EchoCanceller3Config& config,
     int sample_rate_hz,
+    size_t num_render_channels,
+    size_t num_capture_channels,
     std::unique_ptr<RenderDelayBuffer> render_buffer) {
   std::unique_ptr<RenderDelayController> delay_controller;
   if (!config.delay.use_external_delay_estimator) {
     delay_controller.reset(
         RenderDelayController::Create(config, sample_rate_hz));
   }
-  std::unique_ptr<EchoRemover> echo_remover(
-      EchoRemover::Create(config, sample_rate_hz));
-  return Create(config, sample_rate_hz, std::move(render_buffer),
+  std::unique_ptr<EchoRemover> echo_remover(EchoRemover::Create(
+      config, sample_rate_hz, num_render_channels, num_capture_channels));
+  return Create(config, sample_rate_hz, num_render_channels,
+                num_capture_channels, std::move(render_buffer),
                 std::move(delay_controller), std::move(echo_remover));
 }
 
 BlockProcessor* BlockProcessor::Create(
     const EchoCanceller3Config& config,
     int sample_rate_hz,
+    size_t num_render_channels,
+    size_t num_capture_channels,
     std::unique_ptr<RenderDelayBuffer> render_buffer,
     std::unique_ptr<RenderDelayController> delay_controller,
     std::unique_ptr<EchoRemover> echo_remover) {
-  return new BlockProcessorImpl(
-      config, sample_rate_hz, std::move(render_buffer),
-      std::move(delay_controller), std::move(echo_remover));
+  return new BlockProcessorImpl(config, sample_rate_hz, num_render_channels,
+                                num_capture_channels, std::move(render_buffer),
+                                std::move(delay_controller),
+                                std::move(echo_remover));
 }
 
 }  // namespace webrtc
diff --git a/modules/audio_processing/aec3/block_processor.h b/modules/audio_processing/aec3/block_processor.h
index 8b1bb90..3ae5a75 100644
--- a/modules/audio_processing/aec3/block_processor.h
+++ b/modules/audio_processing/aec3/block_processor.h
@@ -28,15 +28,21 @@
 class BlockProcessor {
  public:
   static BlockProcessor* Create(const EchoCanceller3Config& config,
-                                int sample_rate_hz);
+                                int sample_rate_hz,
+                                size_t num_render_channels,
+                                size_t num_capture_channels);
   // Only used for testing purposes.
   static BlockProcessor* Create(
       const EchoCanceller3Config& config,
       int sample_rate_hz,
+      size_t num_render_channels,
+      size_t num_capture_channels,
       std::unique_ptr<RenderDelayBuffer> render_buffer);
   static BlockProcessor* Create(
       const EchoCanceller3Config& config,
       int sample_rate_hz,
+      size_t num_render_channels,
+      size_t num_capture_channels,
       std::unique_ptr<RenderDelayBuffer> render_buffer,
       std::unique_ptr<RenderDelayController> delay_controller,
       std::unique_ptr<EchoRemover> echo_remover);
@@ -53,11 +59,11 @@
   virtual void ProcessCapture(
       bool echo_path_gain_change,
       bool capture_signal_saturation,
-      std::vector<std::vector<float>>* capture_block) = 0;
+      std::vector<std::vector<std::vector<float>>>* capture_block) = 0;
 
   // Buffers a block of render data supplied by a FrameBlocker object.
   virtual void BufferRender(
-      const std::vector<std::vector<float>>& render_block) = 0;
+      const std::vector<std::vector<std::vector<float>>>& render_block) = 0;
 
   // Reports whether echo leakage has been detected in the echo canceller
   // output.
diff --git a/modules/audio_processing/aec3/block_processor_unittest.cc b/modules/audio_processing/aec3/block_processor_unittest.cc
index bd085da..9c315e1 100644
--- a/modules/audio_processing/aec3/block_processor_unittest.cc
+++ b/modules/audio_processing/aec3/block_processor_unittest.cc
@@ -36,11 +36,16 @@
 // Verifies that the basic BlockProcessor functionality works and that the API
 // methods are callable.
 void RunBasicSetupAndApiCallTest(int sample_rate_hz, int num_iterations) {
-  std::unique_ptr<BlockProcessor> block_processor(
-      BlockProcessor::Create(EchoCanceller3Config(), sample_rate_hz));
-  std::vector<std::vector<float>> block(NumBandsForRate(sample_rate_hz),
-                                        std::vector<float>(kBlockSize, 1000.f));
+  constexpr size_t kNumRenderChannels = 1;
+  constexpr size_t kNumCaptureChannels = 1;
 
+  std::unique_ptr<BlockProcessor> block_processor(
+      BlockProcessor::Create(EchoCanceller3Config(), sample_rate_hz,
+                             kNumRenderChannels, kNumCaptureChannels));
+  std::vector<std::vector<std::vector<float>>> block(
+      NumBandsForRate(sample_rate_hz),
+      std::vector<std::vector<float>>(kNumRenderChannels,
+                                      std::vector<float>(kBlockSize, 1000.f)));
   for (int k = 0; k < num_iterations; ++k) {
     block_processor->BufferRender(block);
     block_processor->ProcessCapture(false, false, &block);
@@ -50,43 +55,67 @@
 
 #if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
 void RunRenderBlockSizeVerificationTest(int sample_rate_hz) {
+  constexpr size_t kNumRenderChannels = 1;
+  constexpr size_t kNumCaptureChannels = 1;
+
   std::unique_ptr<BlockProcessor> block_processor(
-      BlockProcessor::Create(EchoCanceller3Config(), sample_rate_hz));
-  std::vector<std::vector<float>> block(
-      NumBandsForRate(sample_rate_hz), std::vector<float>(kBlockSize - 1, 0.f));
+      BlockProcessor::Create(EchoCanceller3Config(), sample_rate_hz,
+                             kNumRenderChannels, kNumCaptureChannels));
+  std::vector<std::vector<std::vector<float>>> block(
+      NumBandsForRate(sample_rate_hz),
+      std::vector<std::vector<float>>(kNumRenderChannels,
+                                      std::vector<float>(kBlockSize - 1, 0.f)));
 
   EXPECT_DEATH(block_processor->BufferRender(block), "");
 }
 
 void RunCaptureBlockSizeVerificationTest(int sample_rate_hz) {
+  constexpr size_t kNumRenderChannels = 1;
+  constexpr size_t kNumCaptureChannels = 1;
+
   std::unique_ptr<BlockProcessor> block_processor(
-      BlockProcessor::Create(EchoCanceller3Config(), sample_rate_hz));
-  std::vector<std::vector<float>> block(
-      NumBandsForRate(sample_rate_hz), std::vector<float>(kBlockSize - 1, 0.f));
+      BlockProcessor::Create(EchoCanceller3Config(), sample_rate_hz,
+                             kNumRenderChannels, kNumCaptureChannels));
+  std::vector<std::vector<std::vector<float>>> block(
+      NumBandsForRate(sample_rate_hz),
+      std::vector<std::vector<float>>(kNumRenderChannels,
+                                      std::vector<float>(kBlockSize - 1, 0.f)));
 
   EXPECT_DEATH(block_processor->ProcessCapture(false, false, &block), "");
 }
 
 void RunRenderNumBandsVerificationTest(int sample_rate_hz) {
+  constexpr size_t kNumRenderChannels = 1;
+  constexpr size_t kNumCaptureChannels = 1;
+
   const size_t wrong_num_bands = NumBandsForRate(sample_rate_hz) < 3
                                      ? NumBandsForRate(sample_rate_hz) + 1
                                      : 1;
   std::unique_ptr<BlockProcessor> block_processor(
-      BlockProcessor::Create(EchoCanceller3Config(), sample_rate_hz));
-  std::vector<std::vector<float>> block(wrong_num_bands,
-                                        std::vector<float>(kBlockSize, 0.f));
+      BlockProcessor::Create(EchoCanceller3Config(), sample_rate_hz,
+                             kNumRenderChannels, kNumCaptureChannels));
+  std::vector<std::vector<std::vector<float>>> block(
+      wrong_num_bands,
+      std::vector<std::vector<float>>(kNumRenderChannels,
+                                      std::vector<float>(kBlockSize, 0.f)));
 
   EXPECT_DEATH(block_processor->BufferRender(block), "");
 }
 
 void RunCaptureNumBandsVerificationTest(int sample_rate_hz) {
+  constexpr size_t kNumRenderChannels = 1;
+  constexpr size_t kNumCaptureChannels = 1;
+
   const size_t wrong_num_bands = NumBandsForRate(sample_rate_hz) < 3
                                      ? NumBandsForRate(sample_rate_hz) + 1
                                      : 1;
   std::unique_ptr<BlockProcessor> block_processor(
-      BlockProcessor::Create(EchoCanceller3Config(), sample_rate_hz));
-  std::vector<std::vector<float>> block(wrong_num_bands,
-                                        std::vector<float>(kBlockSize, 0.f));
+      BlockProcessor::Create(EchoCanceller3Config(), sample_rate_hz,
+                             kNumRenderChannels, kNumCaptureChannels));
+  std::vector<std::vector<std::vector<float>>> block(
+      wrong_num_bands,
+      std::vector<std::vector<float>>(kNumRenderChannels,
+                                      std::vector<float>(kBlockSize, 0.f)));
 
   EXPECT_DEATH(block_processor->ProcessCapture(false, false, &block), "");
 }
@@ -104,17 +133,19 @@
 // the render delay buffer inside block processor.
 // TODO(peah): Activate the unittest once the required code has been landed.
 TEST(BlockProcessor, DISABLED_DelayControllerIntegration) {
+  constexpr size_t kNumRenderChannels = 1;
+  constexpr size_t kNumCaptureChannels = 1;
   constexpr size_t kNumBlocks = 310;
   constexpr size_t kDelayInSamples = 640;
   constexpr size_t kDelayHeadroom = 1;
   constexpr size_t kDelayInBlocks =
       kDelayInSamples / kBlockSize - kDelayHeadroom;
   Random random_generator(42U);
-  for (auto rate : {8000, 16000, 32000, 48000}) {
+  for (auto rate : {16000, 32000, 48000}) {
     SCOPED_TRACE(ProduceDebugText(rate));
     std::unique_ptr<testing::StrictMock<webrtc::test::MockRenderDelayBuffer>>
         render_delay_buffer_mock(
-            new StrictMock<webrtc::test::MockRenderDelayBuffer>(rate));
+            new StrictMock<webrtc::test::MockRenderDelayBuffer>(rate, 1));
     EXPECT_CALL(*render_delay_buffer_mock, Insert(_))
         .Times(kNumBlocks)
         .WillRepeatedly(Return(RenderDelayBuffer::BufferingEvent::kNone));
@@ -125,16 +156,21 @@
         .Times(kNumBlocks + 1)
         .WillRepeatedly(Return(0));
     std::unique_ptr<BlockProcessor> block_processor(BlockProcessor::Create(
-        EchoCanceller3Config(), rate, std::move(render_delay_buffer_mock)));
+        EchoCanceller3Config(), rate, kNumRenderChannels, kNumCaptureChannels,
+        std::move(render_delay_buffer_mock)));
 
-    std::vector<std::vector<float>> render_block(
-        NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
-    std::vector<std::vector<float>> capture_block(
-        NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
+    std::vector<std::vector<std::vector<float>>> render_block(
+        NumBandsForRate(rate),
+        std::vector<std::vector<float>>(kNumRenderChannels,
+                                        std::vector<float>(kBlockSize, 0.f)));
+    std::vector<std::vector<std::vector<float>>> capture_block(
+        NumBandsForRate(rate),
+        std::vector<std::vector<float>>(kNumCaptureChannels,
+                                        std::vector<float>(kBlockSize, 0.f)));
     DelayBuffer<float> signal_delay_buffer(kDelayInSamples);
     for (size_t k = 0; k < kNumBlocks; ++k) {
-      RandomizeSampleVector(&random_generator, render_block[0]);
-      signal_delay_buffer.Delay(render_block[0], capture_block[0]);
+      RandomizeSampleVector(&random_generator, render_block[0][0]);
+      signal_delay_buffer.Delay(render_block[0][0], capture_block[0][0]);
       block_processor->BufferRender(render_block);
       block_processor->ProcessCapture(false, false, &capture_block);
     }
@@ -144,12 +180,15 @@
 // Verifies that BlockProcessor submodules are called in a proper manner.
 TEST(BlockProcessor, DISABLED_SubmoduleIntegration) {
   constexpr size_t kNumBlocks = 310;
+  constexpr size_t kNumRenderChannels = 1;
+  constexpr size_t kNumCaptureChannels = 1;
+
   Random random_generator(42U);
-  for (auto rate : {8000, 16000, 32000, 48000}) {
+  for (auto rate : {16000, 32000, 48000}) {
     SCOPED_TRACE(ProduceDebugText(rate));
     std::unique_ptr<testing::StrictMock<webrtc::test::MockRenderDelayBuffer>>
         render_delay_buffer_mock(
-            new StrictMock<webrtc::test::MockRenderDelayBuffer>(rate));
+            new StrictMock<webrtc::test::MockRenderDelayBuffer>(rate, 1));
     std::unique_ptr<
         ::testing::StrictMock<webrtc::test::MockRenderDelayController>>
         render_delay_controller_mock(
@@ -174,17 +213,22 @@
         .Times(kNumBlocks);
 
     std::unique_ptr<BlockProcessor> block_processor(BlockProcessor::Create(
-        EchoCanceller3Config(), rate, std::move(render_delay_buffer_mock),
+        EchoCanceller3Config(), rate, kNumRenderChannels, kNumCaptureChannels,
+        std::move(render_delay_buffer_mock),
         std::move(render_delay_controller_mock), std::move(echo_remover_mock)));
 
-    std::vector<std::vector<float>> render_block(
-        NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
-    std::vector<std::vector<float>> capture_block(
-        NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
+    std::vector<std::vector<std::vector<float>>> render_block(
+        NumBandsForRate(rate),
+        std::vector<std::vector<float>>(kNumRenderChannels,
+                                        std::vector<float>(kBlockSize, 0.f)));
+    std::vector<std::vector<std::vector<float>>> capture_block(
+        NumBandsForRate(rate),
+        std::vector<std::vector<float>>(kNumCaptureChannels,
+                                        std::vector<float>(kBlockSize, 0.f)));
     DelayBuffer<float> signal_delay_buffer(640);
     for (size_t k = 0; k < kNumBlocks; ++k) {
-      RandomizeSampleVector(&random_generator, render_block[0]);
-      signal_delay_buffer.Delay(render_block[0], capture_block[0]);
+      RandomizeSampleVector(&random_generator, render_block[0][0]);
+      signal_delay_buffer.Delay(render_block[0][0], capture_block[0][0]);
       block_processor->BufferRender(render_block);
       block_processor->ProcessCapture(false, false, &capture_block);
       block_processor->UpdateEchoLeakageStatus(false);
@@ -193,7 +237,7 @@
 }
 
 TEST(BlockProcessor, BasicSetupAndApiCalls) {
-  for (auto rate : {8000, 16000, 32000, 48000}) {
+  for (auto rate : {16000, 32000, 48000}) {
     SCOPED_TRACE(ProduceDebugText(rate));
     RunBasicSetupAndApiCallTest(rate, 1);
   }
@@ -207,21 +251,21 @@
 // TODO(gustaf): Re-enable the test once the issue with memory leaks during
 // DEATH tests on test bots has been fixed.
 TEST(BlockProcessor, DISABLED_VerifyRenderBlockSizeCheck) {
-  for (auto rate : {8000, 16000, 32000, 48000}) {
+  for (auto rate : {16000, 32000, 48000}) {
     SCOPED_TRACE(ProduceDebugText(rate));
     RunRenderBlockSizeVerificationTest(rate);
   }
 }
 
 TEST(BlockProcessor, VerifyCaptureBlockSizeCheck) {
-  for (auto rate : {8000, 16000, 32000, 48000}) {
+  for (auto rate : {16000, 32000, 48000}) {
     SCOPED_TRACE(ProduceDebugText(rate));
     RunCaptureBlockSizeVerificationTest(rate);
   }
 }
 
 TEST(BlockProcessor, VerifyRenderNumBandsCheck) {
-  for (auto rate : {8000, 16000, 32000, 48000}) {
+  for (auto rate : {16000, 32000, 48000}) {
     SCOPED_TRACE(ProduceDebugText(rate));
     RunRenderNumBandsVerificationTest(rate);
   }
@@ -230,7 +274,7 @@
 // TODO(peah): Verify the check for correct number of bands in the capture
 // signal.
 TEST(BlockProcessor, VerifyCaptureNumBandsCheck) {
-  for (auto rate : {8000, 16000, 32000, 48000}) {
+  for (auto rate : {16000, 32000, 48000}) {
     SCOPED_TRACE(ProduceDebugText(rate));
     RunCaptureNumBandsVerificationTest(rate);
   }
@@ -239,7 +283,7 @@
 // Verifiers that the verification for null ProcessCapture input works.
 TEST(BlockProcessor, NullProcessCaptureParameter) {
   EXPECT_DEATH(std::unique_ptr<BlockProcessor>(
-                   BlockProcessor::Create(EchoCanceller3Config(), 8000))
+                   BlockProcessor::Create(EchoCanceller3Config(), 16000, 1, 1))
                    ->ProcessCapture(false, false, nullptr),
                "");
 }
@@ -249,7 +293,7 @@
 // tests on test bots has been fixed.
 TEST(BlockProcessor, DISABLED_WrongSampleRate) {
   EXPECT_DEATH(std::unique_ptr<BlockProcessor>(
-                   BlockProcessor::Create(EchoCanceller3Config(), 8001)),
+                   BlockProcessor::Create(EchoCanceller3Config(), 8001, 1, 1)),
                "");
 }
 
diff --git a/modules/audio_processing/aec3/decimator_unittest.cc b/modules/audio_processing/aec3/decimator_unittest.cc
index cf8de84..946089ab 100644
--- a/modules/audio_processing/aec3/decimator_unittest.cc
+++ b/modules/audio_processing/aec3/decimator_unittest.cc
@@ -90,7 +90,7 @@
 TEST(Decimator, NoLeakageFromUpperFrequencies) {
   float input_power;
   float output_power;
-  for (auto rate : {8000, 16000, 32000, 48000}) {
+  for (auto rate : {16000, 32000, 48000}) {
     for (auto down_sampling_factor : kDownSamplingFactors) {
       ProduceDebugText(rate);
       ProduceDecimatedSinusoidalOutputPower(rate, down_sampling_factor,
diff --git a/modules/audio_processing/aec3/echo_audibility.cc b/modules/audio_processing/aec3/echo_audibility.cc
index e857a7e..4154e539 100644
--- a/modules/audio_processing/aec3/echo_audibility.cc
+++ b/modules/audio_processing/aec3/echo_audibility.cc
@@ -97,7 +97,7 @@
   } else {
     for (int idx = render_block_write_prev_; idx != render_block_write_current;
          idx = block_buffer.IncIndex(idx)) {
-      auto block = block_buffer.buffer[idx][0];
+      auto block = block_buffer.buffer[idx][0][0];
       auto r = std::minmax_element(block.cbegin(), block.cend());
       float max_abs = std::max(std::fabs(*r.first), std::fabs(*r.second));
       if (max_abs < 10) {
diff --git a/modules/audio_processing/aec3/echo_canceller3.cc b/modules/audio_processing/aec3/echo_canceller3.cc
index c2ad56b..cf953ae 100644
--- a/modules/audio_processing/aec3/echo_canceller3.cc
+++ b/modules/audio_processing/aec3/echo_canceller3.cc
@@ -45,27 +45,36 @@
   return adjusted_cfg;
 }
 
-void FillSubFrameView(AudioBuffer* frame,
-                      size_t sub_frame_index,
-                      std::vector<rtc::ArrayView<float>>* sub_frame_view) {
+void FillSubFrameView(
+    AudioBuffer* frame,
+    size_t sub_frame_index,
+    std::vector<std::vector<rtc::ArrayView<float>>>* sub_frame_view) {
   RTC_DCHECK_GE(1, sub_frame_index);
   RTC_DCHECK_LE(0, sub_frame_index);
   RTC_DCHECK_EQ(frame->num_bands(), sub_frame_view->size());
-  for (size_t k = 0; k < sub_frame_view->size(); ++k) {
-    (*sub_frame_view)[k] = rtc::ArrayView<float>(
-        &frame->split_bands(0)[k][sub_frame_index * kSubFrameLength],
-        kSubFrameLength);
+  RTC_DCHECK_EQ(frame->num_channels(), (*sub_frame_view)[0].size());
+  for (size_t band = 0; band < sub_frame_view->size(); ++band) {
+    for (size_t channel = 0; channel < (*sub_frame_view)[0].size(); ++channel) {
+      (*sub_frame_view)[band][channel] = rtc::ArrayView<float>(
+          &frame->split_bands(channel)[band][sub_frame_index * kSubFrameLength],
+          kSubFrameLength);
+    }
   }
 }
 
-void FillSubFrameView(std::vector<std::vector<float>>* frame,
-                      size_t sub_frame_index,
-                      std::vector<rtc::ArrayView<float>>* sub_frame_view) {
+void FillSubFrameView(
+    std::vector<std::vector<std::vector<float>>>* frame,
+    size_t sub_frame_index,
+    std::vector<std::vector<rtc::ArrayView<float>>>* sub_frame_view) {
   RTC_DCHECK_GE(1, sub_frame_index);
   RTC_DCHECK_EQ(frame->size(), sub_frame_view->size());
-  for (size_t k = 0; k < frame->size(); ++k) {
-    (*sub_frame_view)[k] = rtc::ArrayView<float>(
-        &(*frame)[k][sub_frame_index * kSubFrameLength], kSubFrameLength);
+  RTC_DCHECK_EQ((*frame)[0].size(), (*sub_frame_view)[0].size());
+  for (size_t band = 0; band < frame->size(); ++band) {
+    for (size_t channel = 0; channel < (*frame)[band].size(); ++channel) {
+      (*sub_frame_view)[band][channel] = rtc::ArrayView<float>(
+          &(*frame)[band][channel][sub_frame_index * kSubFrameLength],
+          kSubFrameLength);
+    }
   }
 }
 
@@ -77,8 +86,8 @@
     FrameBlocker* capture_blocker,
     BlockFramer* output_framer,
     BlockProcessor* block_processor,
-    std::vector<std::vector<float>>* block,
-    std::vector<rtc::ArrayView<float>>* sub_frame_view) {
+    std::vector<std::vector<std::vector<float>>>* block,
+    std::vector<std::vector<rtc::ArrayView<float>>>* sub_frame_view) {
   FillSubFrameView(capture, sub_frame_index, sub_frame_view);
   capture_blocker->InsertSubFrameAndExtractBlock(*sub_frame_view, block);
   block_processor->ProcessCapture(level_change, saturated_microphone_signal,
@@ -92,7 +101,7 @@
     FrameBlocker* capture_blocker,
     BlockFramer* output_framer,
     BlockProcessor* block_processor,
-    std::vector<std::vector<float>>* block) {
+    std::vector<std::vector<std::vector<float>>>* block) {
   if (!capture_blocker->IsBlockAvailable()) {
     return;
   }
@@ -104,20 +113,21 @@
 }
 
 void BufferRenderFrameContent(
-    std::vector<std::vector<float>>* render_frame,
+    std::vector<std::vector<std::vector<float>>>* render_frame,
     size_t sub_frame_index,
     FrameBlocker* render_blocker,
     BlockProcessor* block_processor,
-    std::vector<std::vector<float>>* block,
-    std::vector<rtc::ArrayView<float>>* sub_frame_view) {
+    std::vector<std::vector<std::vector<float>>>* block,
+    std::vector<std::vector<rtc::ArrayView<float>>>* sub_frame_view) {
   FillSubFrameView(render_frame, sub_frame_index, sub_frame_view);
   render_blocker->InsertSubFrameAndExtractBlock(*sub_frame_view, block);
   block_processor->BufferRender(*block);
 }
 
-void BufferRemainingRenderFrameContent(FrameBlocker* render_blocker,
-                                       BlockProcessor* block_processor,
-                                       std::vector<std::vector<float>>* block) {
+void BufferRemainingRenderFrameContent(
+    FrameBlocker* render_blocker,
+    BlockProcessor* block_processor,
+    std::vector<std::vector<std::vector<float>>>* block) {
   if (!render_blocker->IsBlockAvailable()) {
     return;
   }
@@ -127,14 +137,19 @@
 
 void CopyBufferIntoFrame(const AudioBuffer& buffer,
                          size_t num_bands,
-                         size_t frame_length,
-                         std::vector<std::vector<float>>* frame) {
+                         size_t num_channels,
+                         std::vector<std::vector<std::vector<float>>>* frame) {
   RTC_DCHECK_EQ(num_bands, frame->size());
-  RTC_DCHECK_EQ(frame_length, (*frame)[0].size());
-  for (size_t k = 0; k < num_bands; ++k) {
-    rtc::ArrayView<const float> buffer_view(&buffer.split_bands_const(0)[k][0],
-                                            frame_length);
-    std::copy(buffer_view.begin(), buffer_view.end(), (*frame)[k].begin());
+  RTC_DCHECK_EQ(num_channels, (*frame)[0].size());
+  RTC_DCHECK_EQ(AudioBuffer::kSplitBandSize, (*frame)[0][0].size());
+  for (size_t band = 0; band < num_bands; ++band) {
+    for (size_t channel = 0; channel < num_channels; ++channel) {
+      rtc::ArrayView<const float> buffer_view(
+          &buffer.split_bands_const(channel)[band][0],
+          AudioBuffer::kSplitBandSize);
+      std::copy(buffer_view.begin(), buffer_view.end(),
+                (*frame)[band][channel].begin());
+    }
   }
 }
 
@@ -143,40 +158,39 @@
 class EchoCanceller3::RenderWriter {
  public:
   RenderWriter(ApmDataDumper* data_dumper,
-               SwapQueue<std::vector<std::vector<float>>,
+               SwapQueue<std::vector<std::vector<std::vector<float>>>,
                          Aec3RenderQueueItemVerifier>* render_transfer_queue,
-               int sample_rate_hz,
-               int frame_length,
-               int num_bands);
+               size_t num_bands,
+               size_t num_channels);
   ~RenderWriter();
   void Insert(const AudioBuffer& input);
 
  private:
   ApmDataDumper* data_dumper_;
-  const int sample_rate_hz_;
-  const size_t frame_length_;
-  const int num_bands_;
+  const size_t num_bands_;
+  const size_t num_channels_;
   HighPassFilter high_pass_filter_;
-  std::vector<std::vector<float>> render_queue_input_frame_;
-  SwapQueue<std::vector<std::vector<float>>, Aec3RenderQueueItemVerifier>*
-      render_transfer_queue_;
+  std::vector<std::vector<std::vector<float>>> render_queue_input_frame_;
+  SwapQueue<std::vector<std::vector<std::vector<float>>>,
+            Aec3RenderQueueItemVerifier>* render_transfer_queue_;
   RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RenderWriter);
 };
 
 EchoCanceller3::RenderWriter::RenderWriter(
     ApmDataDumper* data_dumper,
-    SwapQueue<std::vector<std::vector<float>>, Aec3RenderQueueItemVerifier>*
-        render_transfer_queue,
-    int sample_rate_hz,
-    int frame_length,
-    int num_bands)
+    SwapQueue<std::vector<std::vector<std::vector<float>>>,
+              Aec3RenderQueueItemVerifier>* render_transfer_queue,
+    size_t num_bands,
+    size_t num_channels)
     : data_dumper_(data_dumper),
-      sample_rate_hz_(sample_rate_hz),
-      frame_length_(frame_length),
       num_bands_(num_bands),
-      high_pass_filter_(1),
-      render_queue_input_frame_(num_bands_,
-                                std::vector<float>(frame_length_, 0.f)),
+      num_channels_(num_channels),
+      high_pass_filter_(num_channels),
+      render_queue_input_frame_(
+          num_bands_,
+          std::vector<std::vector<float>>(
+              num_channels_,
+              std::vector<float>(AudioBuffer::kSplitBandSize, 0.f))),
       render_transfer_queue_(render_transfer_queue) {
   RTC_DCHECK(data_dumper);
 }
@@ -185,21 +199,21 @@
 
 void EchoCanceller3::RenderWriter::Insert(const AudioBuffer& input) {
   RTC_DCHECK_EQ(1, input.num_channels());
-  RTC_DCHECK_EQ(frame_length_, input.num_frames_per_band());
+  RTC_DCHECK_EQ(AudioBuffer::kSplitBandSize, input.num_frames_per_band());
   RTC_DCHECK_EQ(num_bands_, input.num_bands());
 
   // TODO(bugs.webrtc.org/8759) Temporary work-around.
-  if (num_bands_ != static_cast<int>(input.num_bands()))
+  if (num_bands_ != input.num_bands())
     return;
 
-  data_dumper_->DumpWav("aec3_render_input", frame_length_,
-                        &input.split_bands_const(0)[0][0],
-                        LowestBandRate(sample_rate_hz_), 1);
+  data_dumper_->DumpWav("aec3_render_input", AudioBuffer::kSplitBandSize,
+                        &input.split_bands_const(0)[0][0], 16000, 1);
 
-  CopyBufferIntoFrame(input, num_bands_, frame_length_,
+  CopyBufferIntoFrame(input, num_bands_, num_channels_,
                       &render_queue_input_frame_);
-
-  high_pass_filter_.Process(render_queue_input_frame_[0]);
+  for (size_t channel = 0; channel < num_channels_; ++channel) {
+    high_pass_filter_.Process(render_queue_input_frame_[0][channel]);
+  }
 
   static_cast<void>(render_transfer_queue_->Insert(&render_queue_input_frame_));
 }
@@ -207,43 +221,71 @@
 int EchoCanceller3::instance_count_ = 0;
 
 EchoCanceller3::EchoCanceller3(const EchoCanceller3Config& config,
-                               int sample_rate_hz)
-    : EchoCanceller3(
-          AdjustConfig(config),
-          sample_rate_hz,
-          std::unique_ptr<BlockProcessor>(
-              BlockProcessor::Create(AdjustConfig(config), sample_rate_hz))) {}
+                               int sample_rate_hz,
+                               size_t num_render_channels,
+                               size_t num_capture_channels)
+    : EchoCanceller3(AdjustConfig(config),
+                     sample_rate_hz,
+                     num_render_channels,
+                     num_capture_channels,
+                     std::unique_ptr<BlockProcessor>(
+                         BlockProcessor::Create(AdjustConfig(config),
+                                                sample_rate_hz,
+                                                num_render_channels,
+                                                num_capture_channels))) {}
 EchoCanceller3::EchoCanceller3(const EchoCanceller3Config& config,
                                int sample_rate_hz,
+                               size_t num_render_channels,
+                               size_t num_capture_channels,
                                std::unique_ptr<BlockProcessor> block_processor)
     : data_dumper_(
           new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
       config_(config),
       sample_rate_hz_(sample_rate_hz),
       num_bands_(NumBandsForRate(sample_rate_hz_)),
-      frame_length_(rtc::CheckedDivExact(LowestBandRate(sample_rate_hz_), 100)),
-      output_framer_(num_bands_),
-      capture_blocker_(num_bands_),
-      render_blocker_(num_bands_),
+      num_render_channels_(num_render_channels),
+      num_capture_channels_(num_capture_channels),
+      output_framer_(num_bands_, num_capture_channels_),
+      capture_blocker_(num_bands_, num_capture_channels_),
+      render_blocker_(num_bands_, num_render_channels_),
       render_transfer_queue_(
           kRenderTransferQueueSizeFrames,
-          std::vector<std::vector<float>>(
+          std::vector<std::vector<std::vector<float>>>(
               num_bands_,
-              std::vector<float>(frame_length_, 0.f)),
-          Aec3RenderQueueItemVerifier(num_bands_, frame_length_)),
+              std::vector<std::vector<float>>(
+                  num_render_channels_,
+                  std::vector<float>(AudioBuffer::kSplitBandSize, 0.f))),
+          Aec3RenderQueueItemVerifier(num_bands_,
+                                      num_render_channels_,
+                                      AudioBuffer::kSplitBandSize)),
       block_processor_(std::move(block_processor)),
-      render_queue_output_frame_(num_bands_,
-                                 std::vector<float>(frame_length_, 0.f)),
-      block_(num_bands_, std::vector<float>(kBlockSize, 0.f)),
-      sub_frame_view_(num_bands_),
+      render_queue_output_frame_(
+          num_bands_,
+          std::vector<std::vector<float>>(
+              num_render_channels_,
+              std::vector<float>(AudioBuffer::kSplitBandSize, 0.f))),
+      render_block_(
+          num_bands_,
+          std::vector<std::vector<float>>(num_render_channels_,
+                                          std::vector<float>(kBlockSize, 0.f))),
+      capture_block_(
+          num_bands_,
+          std::vector<std::vector<float>>(num_capture_channels_,
+                                          std::vector<float>(kBlockSize, 0.f))),
+      render_sub_frame_view_(
+          num_bands_,
+          std::vector<rtc::ArrayView<float>>(num_render_channels_)),
+      capture_sub_frame_view_(
+          num_bands_,
+          std::vector<rtc::ArrayView<float>>(num_capture_channels_)),
       block_delay_buffer_(num_bands_,
-                          frame_length_,
+                          AudioBuffer::kSplitBandSize,
                           config_.delay.fixed_capture_delay_samples) {
   RTC_DCHECK(ValidFullBandRate(sample_rate_hz_));
 
-  render_writer_.reset(
-      new RenderWriter(data_dumper_.get(), &render_transfer_queue_,
-                       sample_rate_hz_, frame_length_, num_bands_));
+  render_writer_.reset(new RenderWriter(data_dumper_.get(),
+                                        &render_transfer_queue_, num_bands_,
+                                        num_render_channels_));
 
   RTC_DCHECK_EQ(num_bands_, std::max(sample_rate_hz_, 16000) / 16000);
   RTC_DCHECK_GE(kMaxNumBands, num_bands_);
@@ -253,6 +295,7 @@
 
 void EchoCanceller3::AnalyzeRender(const AudioBuffer& render) {
   RTC_DCHECK_RUNS_SERIALIZED(&render_race_checker_);
+  RTC_DCHECK_EQ(render.num_channels(), num_render_channels_);
   data_dumper_->DumpRaw("aec3_call_order",
                         static_cast<int>(EchoCanceller3ApiCall::kRender));
 
@@ -265,10 +308,10 @@
                         capture.channels_const()[0], sample_rate_hz_, 1);
 
   saturated_microphone_signal_ = false;
-  for (size_t k = 0; k < capture.num_channels(); ++k) {
+  for (size_t channel = 0; channel < capture.num_channels(); ++channel) {
     saturated_microphone_signal_ |=
         DetectSaturation(rtc::ArrayView<const float>(
-            capture.channels_const()[k], capture.num_frames()));
+            capture.channels_const()[channel], capture.num_frames()));
     if (saturated_microphone_signal_) {
       break;
     }
@@ -280,7 +323,8 @@
   RTC_DCHECK(capture);
   RTC_DCHECK_EQ(1u, capture->num_channels());
   RTC_DCHECK_EQ(num_bands_, capture->num_bands());
-  RTC_DCHECK_EQ(frame_length_, capture->num_frames_per_band());
+  RTC_DCHECK_EQ(AudioBuffer::kSplitBandSize, capture->num_frames_per_band());
+  RTC_DCHECK_EQ(capture->num_channels(), num_capture_channels_);
   data_dumper_->DumpRaw("aec3_call_order",
                         static_cast<int>(EchoCanceller3ApiCall::kCapture));
 
@@ -293,32 +337,29 @@
     block_delay_buffer_.DelaySignal(capture);
   }
 
-  rtc::ArrayView<float> capture_lower_band =
-      rtc::ArrayView<float>(&capture->split_bands(0)[0][0], frame_length_);
+  rtc::ArrayView<float> capture_lower_band = rtc::ArrayView<float>(
+      &capture->split_bands(0)[0][0], AudioBuffer::kSplitBandSize);
 
-  data_dumper_->DumpWav("aec3_capture_input", capture_lower_band,
-                        LowestBandRate(sample_rate_hz_), 1);
+  data_dumper_->DumpWav("aec3_capture_input", capture_lower_band, 16000, 1);
 
   EmptyRenderQueue();
 
-  ProcessCaptureFrameContent(
-      capture, level_change, saturated_microphone_signal_, 0, &capture_blocker_,
-      &output_framer_, block_processor_.get(), &block_, &sub_frame_view_);
+  ProcessCaptureFrameContent(capture, level_change,
+                             saturated_microphone_signal_, 0, &capture_blocker_,
+                             &output_framer_, block_processor_.get(),
+                             &capture_block_, &capture_sub_frame_view_);
 
-  if (sample_rate_hz_ != 8000) {
-    ProcessCaptureFrameContent(
-        capture, level_change, saturated_microphone_signal_, 1,
-        &capture_blocker_, &output_framer_, block_processor_.get(), &block_,
-        &sub_frame_view_);
-  }
+  ProcessCaptureFrameContent(capture, level_change,
+                             saturated_microphone_signal_, 1, &capture_blocker_,
+                             &output_framer_, block_processor_.get(),
+                             &capture_block_, &capture_sub_frame_view_);
 
   ProcessRemainingCaptureFrameContent(
       level_change, saturated_microphone_signal_, &capture_blocker_,
-      &output_framer_, block_processor_.get(), &block_);
+      &output_framer_, block_processor_.get(), &capture_block_);
 
-  data_dumper_->DumpWav("aec3_capture_output", frame_length_,
-                        &capture->split_bands(0)[0][0],
-                        LowestBandRate(sample_rate_hz_), 1);
+  data_dumper_->DumpWav("aec3_capture_output", AudioBuffer::kSplitBandSize,
+                        &capture->split_bands(0)[0][0], 16000, 1);
 }
 
 EchoControl::Metrics EchoCanceller3::GetMetrics() const {
@@ -342,16 +383,15 @@
     api_call_metrics_.ReportRenderCall();
 
     BufferRenderFrameContent(&render_queue_output_frame_, 0, &render_blocker_,
-                             block_processor_.get(), &block_, &sub_frame_view_);
+                             block_processor_.get(), &render_block_,
+                             &render_sub_frame_view_);
 
-    if (sample_rate_hz_ != 8000) {
-      BufferRenderFrameContent(&render_queue_output_frame_, 1, &render_blocker_,
-                               block_processor_.get(), &block_,
-                               &sub_frame_view_);
-    }
+    BufferRenderFrameContent(&render_queue_output_frame_, 1, &render_blocker_,
+                             block_processor_.get(), &render_block_,
+                             &render_sub_frame_view_);
 
     BufferRemainingRenderFrameContent(&render_blocker_, block_processor_.get(),
-                                      &block_);
+                                      &render_block_);
 
     frame_to_buffer =
         render_transfer_queue_.Remove(&render_queue_output_frame_);
diff --git a/modules/audio_processing/aec3/echo_canceller3.h b/modules/audio_processing/aec3/echo_canceller3.h
index d7dea80..5b59674 100644
--- a/modules/audio_processing/aec3/echo_canceller3.h
+++ b/modules/audio_processing/aec3/echo_canceller3.h
@@ -27,7 +27,6 @@
 #include "modules/audio_processing/audio_buffer.h"
 #include "modules/audio_processing/logging/apm_data_dumper.h"
 #include "rtc_base/checks.h"
-#include "rtc_base/constructor_magic.h"
 #include "rtc_base/race_checker.h"
 #include "rtc_base/swap_queue.h"
 #include "rtc_base/thread_annotations.h"
@@ -38,23 +37,33 @@
 // queue.
 class Aec3RenderQueueItemVerifier {
  public:
-  explicit Aec3RenderQueueItemVerifier(size_t num_bands, size_t frame_length)
-      : num_bands_(num_bands), frame_length_(frame_length) {}
+  Aec3RenderQueueItemVerifier(size_t num_bands,
+                              size_t num_channels,
+                              size_t frame_length)
+      : num_bands_(num_bands),
+        num_channels_(num_channels),
+        frame_length_(frame_length) {}
 
-  bool operator()(const std::vector<std::vector<float>>& v) const {
+  bool operator()(const std::vector<std::vector<std::vector<float>>>& v) const {
     if (v.size() != num_bands_) {
       return false;
     }
-    for (const auto& v_k : v) {
-      if (v_k.size() != frame_length_) {
+    for (const auto& band : v) {
+      if (band.size() != num_channels_) {
         return false;
       }
+      for (const auto& channel : band) {
+        if (channel.size() != frame_length_) {
+          return false;
+        }
+      }
     }
     return true;
   }
 
  private:
   const size_t num_bands_;
+  const size_t num_channels_;
   const size_t frame_length_;
 };
 
@@ -73,12 +82,20 @@
 class EchoCanceller3 : public EchoControl {
  public:
   // Normal c-tor to use.
-  EchoCanceller3(const EchoCanceller3Config& config, int sample_rate_hz);
+  EchoCanceller3(const EchoCanceller3Config& config,
+                 int sample_rate_hz,
+                 size_t num_render_channels,
+                 size_t num_capture_channels);
   // Testing c-tor that is used only for testing purposes.
   EchoCanceller3(const EchoCanceller3Config& config,
                  int sample_rate_hz,
+                 size_t num_render_channels,
+                 size_t num_capture_channels,
                  std::unique_ptr<BlockProcessor> block_processor);
   ~EchoCanceller3() override;
+  EchoCanceller3(const EchoCanceller3&) = delete;
+  EchoCanceller3& operator=(const EchoCanceller3&) = delete;
+
   // Analyzes and stores an internal copy of the split-band domain render
   // signal.
   void AnalyzeRender(AudioBuffer* render) override { AnalyzeRender(*render); }
@@ -128,25 +145,30 @@
   const EchoCanceller3Config config_;
   const int sample_rate_hz_;
   const int num_bands_;
-  const size_t frame_length_;
+  const size_t num_render_channels_;
+  const size_t num_capture_channels_;
   BlockFramer output_framer_ RTC_GUARDED_BY(capture_race_checker_);
   FrameBlocker capture_blocker_ RTC_GUARDED_BY(capture_race_checker_);
   FrameBlocker render_blocker_ RTC_GUARDED_BY(capture_race_checker_);
-  SwapQueue<std::vector<std::vector<float>>, Aec3RenderQueueItemVerifier>
+  SwapQueue<std::vector<std::vector<std::vector<float>>>,
+            Aec3RenderQueueItemVerifier>
       render_transfer_queue_;
   std::unique_ptr<BlockProcessor> block_processor_
       RTC_GUARDED_BY(capture_race_checker_);
-  std::vector<std::vector<float>> render_queue_output_frame_
+  std::vector<std::vector<std::vector<float>>> render_queue_output_frame_
       RTC_GUARDED_BY(capture_race_checker_);
   bool saturated_microphone_signal_ RTC_GUARDED_BY(capture_race_checker_) =
       false;
-  std::vector<std::vector<float>> block_ RTC_GUARDED_BY(capture_race_checker_);
-  std::vector<rtc::ArrayView<float>> sub_frame_view_
+  std::vector<std::vector<std::vector<float>>> render_block_
+      RTC_GUARDED_BY(capture_race_checker_);
+  std::vector<std::vector<std::vector<float>>> capture_block_
+      RTC_GUARDED_BY(capture_race_checker_);
+  std::vector<std::vector<rtc::ArrayView<float>>> render_sub_frame_view_
+      RTC_GUARDED_BY(capture_race_checker_);
+  std::vector<std::vector<rtc::ArrayView<float>>> capture_sub_frame_view_
       RTC_GUARDED_BY(capture_race_checker_);
   BlockDelayBuffer block_delay_buffer_ RTC_GUARDED_BY(capture_race_checker_);
   ApiCallJitterMetrics api_call_metrics_ RTC_GUARDED_BY(capture_race_checker_);
-
-  RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(EchoCanceller3);
 };
 }  // namespace webrtc
 
diff --git a/modules/audio_processing/aec3/echo_canceller3_unittest.cc b/modules/audio_processing/aec3/echo_canceller3_unittest.cc
index a29b779..a2f3367 100644
--- a/modules/audio_processing/aec3/echo_canceller3_unittest.cc
+++ b/modules/audio_processing/aec3/echo_canceller3_unittest.cc
@@ -109,12 +109,13 @@
   explicit CaptureTransportVerificationProcessor(size_t num_bands) {}
   ~CaptureTransportVerificationProcessor() override = default;
 
-  void ProcessCapture(bool level_change,
-                      bool saturated_microphone_signal,
-                      std::vector<std::vector<float>>* capture_block) override {
-  }
+  void ProcessCapture(
+      bool level_change,
+      bool saturated_microphone_signal,
+      std::vector<std::vector<std::vector<float>>>* capture_block) override {}
 
-  void BufferRender(const std::vector<std::vector<float>>& block) override {}
+  void BufferRender(
+      const std::vector<std::vector<std::vector<float>>>& block) override {}
 
   void UpdateEchoLeakageStatus(bool leakage_detected) override {}
 
@@ -133,16 +134,18 @@
   explicit RenderTransportVerificationProcessor(size_t num_bands) {}
   ~RenderTransportVerificationProcessor() override = default;
 
-  void ProcessCapture(bool level_change,
-                      bool saturated_microphone_signal,
-                      std::vector<std::vector<float>>* capture_block) override {
-    std::vector<std::vector<float>> render_block =
+  void ProcessCapture(
+      bool level_change,
+      bool saturated_microphone_signal,
+      std::vector<std::vector<std::vector<float>>>* capture_block) override {
+    std::vector<std::vector<std::vector<float>>> render_block =
         received_render_blocks_.front();
     received_render_blocks_.pop_front();
     capture_block->swap(render_block);
   }
 
-  void BufferRender(const std::vector<std::vector<float>>& block) override {
+  void BufferRender(
+      const std::vector<std::vector<std::vector<float>>>& block) override {
     received_render_blocks_.push_back(block);
   }
 
@@ -153,7 +156,8 @@
   void SetAudioBufferDelay(size_t delay_ms) override {}
 
  private:
-  std::deque<std::vector<std::vector<float>>> received_render_blocks_;
+  std::deque<std::vector<std::vector<std::vector<float>>>>
+      received_render_blocks_;
   RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RenderTransportVerificationProcessor);
 };
 
@@ -162,7 +166,7 @@
   explicit EchoCanceller3Tester(int sample_rate_hz)
       : sample_rate_hz_(sample_rate_hz),
         num_bands_(NumBandsForRate(sample_rate_hz_)),
-        frame_length_(sample_rate_hz_ == 8000 ? 80 : 160),
+        frame_length_(160),
         fullband_frame_length_(rtc::CheckedDivExact(sample_rate_hz_, 100)),
         capture_buffer_(fullband_frame_length_ * 100,
                         1,
@@ -182,7 +186,7 @@
   // output.
   void RunCaptureTransportVerificationTest() {
     EchoCanceller3 aec3(
-        EchoCanceller3Config(), sample_rate_hz_,
+        EchoCanceller3Config(), sample_rate_hz_, 1, 1,
         std::unique_ptr<BlockProcessor>(
             new CaptureTransportVerificationProcessor(num_bands_)));
 
@@ -207,7 +211,7 @@
   // block processor.
   void RunRenderTransportVerificationTest() {
     EchoCanceller3 aec3(
-        EchoCanceller3Config(), sample_rate_hz_,
+        EchoCanceller3Config(), sample_rate_hz_, 1, 1,
         std::unique_ptr<BlockProcessor>(
             new RenderTransportVerificationProcessor(num_bands_)));
 
@@ -251,37 +255,34 @@
 
   void RunEchoPathChangeVerificationTest(
       EchoPathChangeTestVariant echo_path_change_test_variant) {
-    const size_t num_full_blocks_per_frame =
-        rtc::CheckedDivExact(LowestBandRate(sample_rate_hz_), 100) / kBlockSize;
-    const size_t expected_num_block_to_process =
-        (kNumFramesToProcess *
-         rtc::CheckedDivExact(LowestBandRate(sample_rate_hz_), 100)) /
-        kBlockSize;
+    constexpr size_t kNumFullBlocksPerFrame = 160 / kBlockSize;
+    constexpr size_t kExpectedNumBlocksToProcess =
+        (kNumFramesToProcess * 160) / kBlockSize;
     std::unique_ptr<testing::StrictMock<webrtc::test::MockBlockProcessor>>
         block_processor_mock(
             new StrictMock<webrtc::test::MockBlockProcessor>());
     EXPECT_CALL(*block_processor_mock, BufferRender(_))
-        .Times(expected_num_block_to_process);
+        .Times(kExpectedNumBlocksToProcess);
     EXPECT_CALL(*block_processor_mock, UpdateEchoLeakageStatus(_)).Times(0);
 
     switch (echo_path_change_test_variant) {
       case EchoPathChangeTestVariant::kNone:
         EXPECT_CALL(*block_processor_mock, ProcessCapture(false, _, _))
-            .Times(expected_num_block_to_process);
+            .Times(kExpectedNumBlocksToProcess);
         break;
       case EchoPathChangeTestVariant::kOneSticky:
         EXPECT_CALL(*block_processor_mock, ProcessCapture(true, _, _))
-            .Times(expected_num_block_to_process);
+            .Times(kExpectedNumBlocksToProcess);
         break;
       case EchoPathChangeTestVariant::kOneNonSticky:
         EXPECT_CALL(*block_processor_mock, ProcessCapture(true, _, _))
-            .Times(num_full_blocks_per_frame);
+            .Times(kNumFullBlocksPerFrame);
         EXPECT_CALL(*block_processor_mock, ProcessCapture(false, _, _))
-            .Times(expected_num_block_to_process - num_full_blocks_per_frame);
+            .Times(kExpectedNumBlocksToProcess - kNumFullBlocksPerFrame);
         break;
     }
 
-    EchoCanceller3 aec3(EchoCanceller3Config(), sample_rate_hz_,
+    EchoCanceller3 aec3(EchoCanceller3Config(), sample_rate_hz_, 1, 1,
                         std::move(block_processor_mock));
 
     for (size_t frame_index = 0; frame_index < kNumFramesToProcess;
@@ -330,17 +331,15 @@
 
   void RunEchoLeakageVerificationTest(
       EchoLeakageTestVariant leakage_report_variant) {
-    const size_t expected_num_block_to_process =
-        (kNumFramesToProcess *
-         rtc::CheckedDivExact(LowestBandRate(sample_rate_hz_), 100)) /
-        kBlockSize;
+    constexpr size_t kExpectedNumBlocksToProcess =
+        (kNumFramesToProcess * 160) / kBlockSize;
     std::unique_ptr<testing::StrictMock<webrtc::test::MockBlockProcessor>>
         block_processor_mock(
             new StrictMock<webrtc::test::MockBlockProcessor>());
     EXPECT_CALL(*block_processor_mock, BufferRender(_))
-        .Times(expected_num_block_to_process);
+        .Times(kExpectedNumBlocksToProcess);
     EXPECT_CALL(*block_processor_mock, ProcessCapture(_, _, _))
-        .Times(expected_num_block_to_process);
+        .Times(kExpectedNumBlocksToProcess);
 
     switch (leakage_report_variant) {
       case EchoLeakageTestVariant::kNone:
@@ -363,7 +362,7 @@
       } break;
     }
 
-    EchoCanceller3 aec3(EchoCanceller3Config(), sample_rate_hz_,
+    EchoCanceller3 aec3(EchoCanceller3Config(), sample_rate_hz_, 1, 1,
                         std::move(block_processor_mock));
 
     for (size_t frame_index = 0; frame_index < kNumFramesToProcess;
@@ -418,41 +417,38 @@
 
   void RunCaptureSaturationVerificationTest(
       SaturationTestVariant saturation_variant) {
-    const size_t num_full_blocks_per_frame =
-        rtc::CheckedDivExact(LowestBandRate(sample_rate_hz_), 100) / kBlockSize;
-    const size_t expected_num_block_to_process =
-        (kNumFramesToProcess *
-         rtc::CheckedDivExact(LowestBandRate(sample_rate_hz_), 100)) /
-        kBlockSize;
+    const size_t kNumFullBlocksPerFrame = 160 / kBlockSize;
+    const size_t kExpectedNumBlocksToProcess =
+        (kNumFramesToProcess * 160) / kBlockSize;
     std::unique_ptr<testing::StrictMock<webrtc::test::MockBlockProcessor>>
         block_processor_mock(
             new StrictMock<webrtc::test::MockBlockProcessor>());
     EXPECT_CALL(*block_processor_mock, BufferRender(_))
-        .Times(expected_num_block_to_process);
+        .Times(kExpectedNumBlocksToProcess);
     EXPECT_CALL(*block_processor_mock, UpdateEchoLeakageStatus(_)).Times(0);
 
     switch (saturation_variant) {
       case SaturationTestVariant::kNone:
         EXPECT_CALL(*block_processor_mock, ProcessCapture(_, false, _))
-            .Times(expected_num_block_to_process);
+            .Times(kExpectedNumBlocksToProcess);
         break;
       case SaturationTestVariant::kOneNegative: {
         ::testing::InSequence s;
         EXPECT_CALL(*block_processor_mock, ProcessCapture(_, true, _))
-            .Times(num_full_blocks_per_frame);
+            .Times(kNumFullBlocksPerFrame);
         EXPECT_CALL(*block_processor_mock, ProcessCapture(_, false, _))
-            .Times(expected_num_block_to_process - num_full_blocks_per_frame);
+            .Times(kExpectedNumBlocksToProcess - kNumFullBlocksPerFrame);
       } break;
       case SaturationTestVariant::kOnePositive: {
         ::testing::InSequence s;
         EXPECT_CALL(*block_processor_mock, ProcessCapture(_, true, _))
-            .Times(num_full_blocks_per_frame);
+            .Times(kNumFullBlocksPerFrame);
         EXPECT_CALL(*block_processor_mock, ProcessCapture(_, false, _))
-            .Times(expected_num_block_to_process - num_full_blocks_per_frame);
+            .Times(kExpectedNumBlocksToProcess - kNumFullBlocksPerFrame);
       } break;
     }
 
-    EchoCanceller3 aec3(EchoCanceller3Config(), sample_rate_hz_,
+    EchoCanceller3 aec3(EchoCanceller3Config(), sample_rate_hz_, 1, 1,
                         std::move(block_processor_mock));
     for (size_t frame_index = 0; frame_index < kNumFramesToProcess;
          ++frame_index) {
@@ -492,7 +488,7 @@
   void RunRenderSwapQueueVerificationTest() {
     const EchoCanceller3Config config;
     EchoCanceller3 aec3(
-        config, sample_rate_hz_,
+        config, sample_rate_hz_, 1, 1,
         std::unique_ptr<BlockProcessor>(
             new RenderTransportVerificationProcessor(num_bands_)));
 
@@ -542,7 +538,7 @@
   // This test verifies that a buffer overrun in the render swapqueue is
   // properly reported.
   void RunRenderPipelineSwapQueueOverrunReturnValueTest() {
-    EchoCanceller3 aec3(EchoCanceller3Config(), sample_rate_hz_);
+    EchoCanceller3 aec3(EchoCanceller3Config(), sample_rate_hz_, 1, 1);
 
     constexpr size_t kRenderTransferQueueSize = 30;
     for (size_t k = 0; k < 2; ++k) {
@@ -567,7 +563,7 @@
     // Set aec3_sample_rate_hz to be different from sample_rate_hz_ in such a
     // way that the number of bands for the rates are different.
     const int aec3_sample_rate_hz = sample_rate_hz_ == 48000 ? 32000 : 48000;
-    EchoCanceller3 aec3(EchoCanceller3Config(), aec3_sample_rate_hz);
+    EchoCanceller3 aec3(EchoCanceller3Config(), aec3_sample_rate_hz, 1, 1);
     PopulateInputFrame(frame_length_, 0, &render_buffer_.channels_f()[0][0], 0);
 
     EXPECT_DEATH(aec3.AnalyzeRender(&render_buffer_), "");
@@ -580,43 +576,12 @@
     // Set aec3_sample_rate_hz to be different from sample_rate_hz_ in such a
     // way that the number of bands for the rates are different.
     const int aec3_sample_rate_hz = sample_rate_hz_ == 48000 ? 32000 : 48000;
-    EchoCanceller3 aec3(EchoCanceller3Config(), aec3_sample_rate_hz);
+    EchoCanceller3 aec3(EchoCanceller3Config(), aec3_sample_rate_hz, 1, 1);
     PopulateInputFrame(frame_length_, num_bands_, 0,
                        &capture_buffer_.split_bands_f(0)[0], 100);
     EXPECT_DEATH(aec3.ProcessCapture(&capture_buffer_, false), "");
   }
 
-  // Verifies the that the check for the frame length in the AnalyzeRender input
-  // is correct by adjusting the sample rates of EchoCanceller3 and the input
-  // AudioBuffer to have a different frame lengths.
-  void RunAnalyzeRenderFrameLengthCheckVerification() {
-    // Set aec3_sample_rate_hz to be different from sample_rate_hz_ in such a
-    // way that the band frame lengths are different.
-    const int aec3_sample_rate_hz = sample_rate_hz_ == 8000 ? 16000 : 8000;
-    EchoCanceller3 aec3(EchoCanceller3Config(), aec3_sample_rate_hz);
-
-    OptionalBandSplit();
-    PopulateInputFrame(frame_length_, 0, &render_buffer_.channels_f()[0][0], 0);
-
-    EXPECT_DEATH(aec3.AnalyzeRender(&render_buffer_), "");
-  }
-
-  // Verifies the that the check for the frame length in the AnalyzeRender input
-  // is correct by adjusting the sample rates of EchoCanceller3 and the input
-  // AudioBuffer to have a different frame lengths.
-  void RunProcessCaptureFrameLengthCheckVerification() {
-    // Set aec3_sample_rate_hz to be different from sample_rate_hz_ in such a
-    // way that the band frame lengths are different.
-    const int aec3_sample_rate_hz = sample_rate_hz_ == 8000 ? 16000 : 8000;
-    EchoCanceller3 aec3(EchoCanceller3Config(), aec3_sample_rate_hz);
-
-    OptionalBandSplit();
-    PopulateInputFrame(frame_length_, num_bands_, 0,
-                       &capture_buffer_.split_bands_f(0)[0], 100);
-
-    EXPECT_DEATH(aec3.ProcessCapture(&capture_buffer_, false), "");
-  }
-
 #endif
 
  private:
@@ -653,28 +618,25 @@
 }  // namespace
 
 TEST(EchoCanceller3Buffering, CaptureBitexactness) {
-  for (auto rate : {8000, 16000, 32000, 48000}) {
+  for (auto rate : {16000, 32000, 48000}) {
     SCOPED_TRACE(ProduceDebugText(rate));
     EchoCanceller3Tester(rate).RunCaptureTransportVerificationTest();
   }
 }
 
 TEST(EchoCanceller3Buffering, RenderBitexactness) {
-  for (auto rate : {8000, 16000, 32000, 48000}) {
+  for (auto rate : {16000, 32000, 48000}) {
     SCOPED_TRACE(ProduceDebugText(rate));
     EchoCanceller3Tester(rate).RunRenderTransportVerificationTest();
   }
 }
 
 TEST(EchoCanceller3Buffering, RenderSwapQueue) {
-  for (auto rate : {8000, 16000}) {
-    SCOPED_TRACE(ProduceDebugText(rate));
-    EchoCanceller3Tester(rate).RunRenderSwapQueueVerificationTest();
-  }
+  EchoCanceller3Tester(16000).RunRenderSwapQueueVerificationTest();
 }
 
 TEST(EchoCanceller3Buffering, RenderSwapQueueOverrunReturnValue) {
-  for (auto rate : {8000, 16000, 32000, 48000}) {
+  for (auto rate : {16000, 32000, 48000}) {
     SCOPED_TRACE(ProduceDebugText(rate));
     EchoCanceller3Tester(rate)
         .RunRenderPipelineSwapQueueOverrunReturnValueTest();
@@ -685,7 +647,7 @@
   auto variants = {EchoCanceller3Tester::SaturationTestVariant::kNone,
                    EchoCanceller3Tester::SaturationTestVariant::kOneNegative,
                    EchoCanceller3Tester::SaturationTestVariant::kOnePositive};
-  for (auto rate : {8000, 16000, 32000, 48000}) {
+  for (auto rate : {16000, 32000, 48000}) {
     for (auto variant : variants) {
       SCOPED_TRACE(ProduceDebugText(rate, static_cast<int>(variant)));
       EchoCanceller3Tester(rate).RunCaptureSaturationVerificationTest(variant);
@@ -698,7 +660,7 @@
       EchoCanceller3Tester::EchoPathChangeTestVariant::kNone,
       EchoCanceller3Tester::EchoPathChangeTestVariant::kOneSticky,
       EchoCanceller3Tester::EchoPathChangeTestVariant::kOneNonSticky};
-  for (auto rate : {8000, 16000, 32000, 48000}) {
+  for (auto rate : {16000, 32000, 48000}) {
     for (auto variant : variants) {
       SCOPED_TRACE(ProduceDebugText(rate, static_cast<int>(variant)));
       EchoCanceller3Tester(rate).RunEchoPathChangeVerificationTest(variant);
@@ -712,7 +674,7 @@
       EchoCanceller3Tester::EchoLeakageTestVariant::kFalseSticky,
       EchoCanceller3Tester::EchoLeakageTestVariant::kTrueSticky,
       EchoCanceller3Tester::EchoLeakageTestVariant::kTrueNonSticky};
-  for (auto rate : {8000, 16000, 32000, 48000}) {
+  for (auto rate : {16000, 32000, 48000}) {
     for (auto variant : variants) {
       SCOPED_TRACE(ProduceDebugText(rate, static_cast<int>(variant)));
       EchoCanceller3Tester(rate).RunEchoLeakageVerificationTest(variant);
@@ -723,33 +685,16 @@
 #if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
 
 TEST(EchoCanceller3InputCheck, WrongCaptureNumBandsCheckVerification) {
-  for (auto rate : {8000, 16000, 32000, 48000}) {
+  for (auto rate : {16000, 32000, 48000}) {
     SCOPED_TRACE(ProduceDebugText(rate));
     EchoCanceller3Tester(rate).RunProcessCaptureNumBandsCheckVerification();
   }
 }
 
-// TODO(peah): Re-enable the test once the issue with memory leaks during DEATH
-// tests on test bots has been fixed.
-TEST(EchoCanceller3InputCheck,
-     DISABLED_WrongRenderFrameLengthCheckVerification) {
-  for (auto rate : {8000, 16000}) {
-    SCOPED_TRACE(ProduceDebugText(rate));
-    EchoCanceller3Tester(rate).RunAnalyzeRenderFrameLengthCheckVerification();
-  }
-}
-
-TEST(EchoCanceller3InputCheck, WrongCaptureFrameLengthCheckVerification) {
-  for (auto rate : {8000, 16000}) {
-    SCOPED_TRACE(ProduceDebugText(rate));
-    EchoCanceller3Tester(rate).RunProcessCaptureFrameLengthCheckVerification();
-  }
-}
-
 // Verifiers that the verification for null input to the capture processing api
 // call works.
 TEST(EchoCanceller3InputCheck, NullCaptureProcessingParameter) {
-  EXPECT_DEATH(EchoCanceller3(EchoCanceller3Config(), 16000)
+  EXPECT_DEATH(EchoCanceller3(EchoCanceller3Config(), 16000, 1, 1)
                    .ProcessCapture(nullptr, false),
                "");
 }
@@ -759,7 +704,7 @@
 // tests on test bots has been fixed.
 TEST(EchoCanceller3InputCheck, DISABLED_WrongSampleRate) {
   ApmDataDumper data_dumper(0);
-  EXPECT_DEATH(EchoCanceller3(EchoCanceller3Config(), 8001), "");
+  EXPECT_DEATH(EchoCanceller3(EchoCanceller3Config(), 8001, 1, 1), "");
 }
 
 #endif
diff --git a/modules/audio_processing/aec3/echo_path_delay_estimator_unittest.cc b/modules/audio_processing/aec3/echo_path_delay_estimator_unittest.cc
index ddf6bc5..9a1bf44 100644
--- a/modules/audio_processing/aec3/echo_path_delay_estimator_unittest.cc
+++ b/modules/audio_processing/aec3/echo_path_delay_estimator_unittest.cc
@@ -36,12 +36,17 @@
 
 // Verifies that the basic API calls work.
 TEST(EchoPathDelayEstimator, BasicApiCalls) {
+  constexpr size_t kNumChannels = 1;
+  constexpr int kSampleRateHz = 48000;
+  constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz);
   ApmDataDumper data_dumper(0);
   EchoCanceller3Config config;
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(config, 48000));
+      RenderDelayBuffer::Create(config, kSampleRateHz, kNumChannels));
   EchoPathDelayEstimator estimator(&data_dumper, config);
-  std::vector<std::vector<float>> render(3, std::vector<float>(kBlockSize));
+  std::vector<std::vector<std::vector<float>>> render(
+      kNumBands, std::vector<std::vector<float>>(
+                     kNumChannels, std::vector<float>(kBlockSize)));
   std::vector<float> capture(kBlockSize);
   for (size_t k = 0; k < 100; ++k) {
     render_delay_buffer->Insert(render);
@@ -53,8 +58,14 @@
 // Verifies that the delay estimator produces correct delay for artificially
 // delayed signals.
 TEST(EchoPathDelayEstimator, DelayEstimation) {
+  constexpr size_t kNumChannels = 1;
+  constexpr int kSampleRateHz = 48000;
+  constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz);
+
   Random random_generator(42U);
-  std::vector<std::vector<float>> render(3, std::vector<float>(kBlockSize));
+  std::vector<std::vector<std::vector<float>>> render(
+      kNumBands, std::vector<std::vector<float>>(
+                     kNumChannels, std::vector<float>(kBlockSize)));
   std::vector<float> capture(kBlockSize);
   ApmDataDumper data_dumper(0);
   constexpr size_t kDownSamplingFactors[] = {2, 4, 8};
@@ -65,14 +76,14 @@
     for (size_t delay_samples : {30, 64, 150, 200, 800, 4000}) {
       SCOPED_TRACE(ProduceDebugText(delay_samples, down_sampling_factor));
       std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-          RenderDelayBuffer::Create(config, 48000));
+          RenderDelayBuffer::Create(config, kSampleRateHz, kNumChannels));
       DelayBuffer<float> signal_delay_buffer(delay_samples);
       EchoPathDelayEstimator estimator(&data_dumper, config);
 
       absl::optional<DelayEstimate> estimated_delay_samples;
       for (size_t k = 0; k < (500 + (delay_samples) / kBlockSize); ++k) {
-        RandomizeSampleVector(&random_generator, render[0]);
-        signal_delay_buffer.Delay(render[0], capture);
+        RandomizeSampleVector(&random_generator, render[0][0]);
+        signal_delay_buffer.Delay(render[0][0], capture);
         render_delay_buffer->Insert(render);
 
         if (k == 0) {
@@ -106,20 +117,26 @@
 // Verifies that the delay estimator does not produce delay estimates for render
 // signals of low level.
 TEST(EchoPathDelayEstimator, NoDelayEstimatesForLowLevelRenderSignals) {
+  constexpr size_t kNumChannels = 1;
+  constexpr int kSampleRateHz = 48000;
+  constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz);
   Random random_generator(42U);
   EchoCanceller3Config config;
-  std::vector<std::vector<float>> render(3, std::vector<float>(kBlockSize));
+  std::vector<std::vector<std::vector<float>>> render(
+      kNumBands, std::vector<std::vector<float>>(
+                     kNumChannels, std::vector<float>(kBlockSize)));
   std::vector<float> capture(kBlockSize);
   ApmDataDumper data_dumper(0);
   EchoPathDelayEstimator estimator(&data_dumper, config);
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(EchoCanceller3Config(), 48000));
+      RenderDelayBuffer::Create(EchoCanceller3Config(), kSampleRateHz,
+                                kNumChannels));
   for (size_t k = 0; k < 100; ++k) {
-    RandomizeSampleVector(&random_generator, render[0]);
-    for (auto& render_k : render[0]) {
+    RandomizeSampleVector(&random_generator, render[0][0]);
+    for (auto& render_k : render[0][0]) {
       render_k *= 100.f / 32767.f;
     }
-    std::copy(render[0].begin(), render[0].end(), capture.begin());
+    std::copy(render[0][0].begin(), render[0][0].end(), capture.begin());
     render_delay_buffer->Insert(render);
     render_delay_buffer->PrepareCaptureProcessing();
     EXPECT_FALSE(estimator.EstimateDelay(
@@ -137,7 +154,7 @@
   EchoCanceller3Config config;
   EchoPathDelayEstimator estimator(&data_dumper, config);
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(config, 48000));
+      RenderDelayBuffer::Create(config, 48000, 1));
   std::vector<float> capture(kBlockSize);
   EXPECT_DEATH(estimator.EstimateDelay(
                    render_delay_buffer->GetDownsampledRenderBuffer(), capture),
@@ -152,7 +169,7 @@
   EchoCanceller3Config config;
   EchoPathDelayEstimator estimator(&data_dumper, config);
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(config, 48000));
+      RenderDelayBuffer::Create(config, 48000, 1));
   std::vector<float> capture(std::vector<float>(kBlockSize - 1));
   EXPECT_DEATH(estimator.EstimateDelay(
                    render_delay_buffer->GetDownsampledRenderBuffer(), capture),
diff --git a/modules/audio_processing/aec3/echo_remover.cc b/modules/audio_processing/aec3/echo_remover.cc
index c7e7f7c..60538d6 100644
--- a/modules/audio_processing/aec3/echo_remover.cc
+++ b/modules/audio_processing/aec3/echo_remover.cc
@@ -84,7 +84,10 @@
 // Class for removing the echo from the capture signal.
 class EchoRemoverImpl final : public EchoRemover {
  public:
-  EchoRemoverImpl(const EchoCanceller3Config& config, int sample_rate_hz);
+  EchoRemoverImpl(const EchoCanceller3Config& config,
+                  int sample_rate_hz,
+                  size_t num_render_channels,
+                  size_t num_capture_channels);
   ~EchoRemoverImpl() override;
 
   void GetMetrics(EchoControl::Metrics* metrics) const override;
@@ -92,11 +95,12 @@
   // Removes the echo from a block of samples from the capture signal. The
   // supplied render signal is assumed to be pre-aligned with the capture
   // signal.
-  void ProcessCapture(EchoPathVariability echo_path_variability,
-                      bool capture_signal_saturation,
-                      const absl::optional<DelayEstimate>& external_delay,
-                      RenderBuffer* render_buffer,
-                      std::vector<std::vector<float>>* capture) override;
+  void ProcessCapture(
+      EchoPathVariability echo_path_variability,
+      bool capture_signal_saturation,
+      const absl::optional<DelayEstimate>& external_delay,
+      RenderBuffer* render_buffer,
+      std::vector<std::vector<std::vector<float>>>* capture) override;
 
   // Updates the status on whether echo leakage is detected in the output of the
   // echo remover.
@@ -117,6 +121,8 @@
   std::unique_ptr<ApmDataDumper> data_dumper_;
   const Aec3Optimization optimization_;
   const int sample_rate_hz_;
+  const size_t num_render_channels_;
+  const size_t num_capture_channels_;
   const bool use_shadow_filter_output_;
   Subtractor subtractor_;
   SuppressionGain suppression_gain_;
@@ -141,13 +147,17 @@
 int EchoRemoverImpl::instance_count_ = 0;
 
 EchoRemoverImpl::EchoRemoverImpl(const EchoCanceller3Config& config,
-                                 int sample_rate_hz)
+                                 int sample_rate_hz,
+                                 size_t num_render_channels,
+                                 size_t num_capture_channels)
     : config_(config),
       fft_(),
       data_dumper_(
           new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
       optimization_(DetectOptimization()),
       sample_rate_hz_(sample_rate_hz),
+      num_render_channels_(num_render_channels),
+      num_capture_channels_(num_capture_channels),
       use_shadow_filter_output_(
           config_.filter.enable_shadow_filter_output_usage),
       subtractor_(config, data_dumper_.get(), optimization_),
@@ -161,6 +171,8 @@
   x_old_.fill(0.f);
   y_old_.fill(0.f);
   e_old_.fill(0.f);
+  (void)num_render_channels_;
+  (void)num_capture_channels_;
 }
 
 EchoRemoverImpl::~EchoRemoverImpl() = default;
@@ -177,23 +189,26 @@
     bool capture_signal_saturation,
     const absl::optional<DelayEstimate>& external_delay,
     RenderBuffer* render_buffer,
-    std::vector<std::vector<float>>* capture) {
+    std::vector<std::vector<std::vector<float>>>* capture) {
   ++block_counter_;
-  const std::vector<std::vector<float>>& x = render_buffer->Block(0);
-  std::vector<std::vector<float>>* y = capture;
+  const std::vector<std::vector<std::vector<float>>>& x =
+      render_buffer->Block(0);
+  std::vector<std::vector<std::vector<float>>>* y = capture;
   RTC_DCHECK(render_buffer);
   RTC_DCHECK(y);
   RTC_DCHECK_EQ(x.size(), NumBandsForRate(sample_rate_hz_));
   RTC_DCHECK_EQ(y->size(), NumBandsForRate(sample_rate_hz_));
-  RTC_DCHECK_EQ(x[0].size(), kBlockSize);
-  RTC_DCHECK_EQ((*y)[0].size(), kBlockSize);
-  const std::vector<float>& x0 = x[0];
-  std::vector<float>& y0 = (*y)[0];
+  RTC_DCHECK_EQ(x[0].size(), num_render_channels_);
+  RTC_DCHECK_EQ((*y)[0].size(), num_capture_channels_);
+  RTC_DCHECK_EQ(x[0][0].size(), kBlockSize);
+  RTC_DCHECK_EQ((*y)[0][0].size(), kBlockSize);
+  const std::vector<float>& x0 = x[0][0];
+  std::vector<float>& y0 = (*y)[0][0];
 
   data_dumper_->DumpWav("aec3_echo_remover_capture_input", kBlockSize, &y0[0],
-                        LowestBandRate(sample_rate_hz_), 1);
+                        16000, 1);
   data_dumper_->DumpWav("aec3_echo_remover_render_input", kBlockSize, &x0[0],
-                        LowestBandRate(sample_rate_hz_), 1);
+                        16000, 1);
   data_dumper_->DumpRaw("aec3_echo_remover_capture_input", y0);
   data_dumper_->DumpRaw("aec3_echo_remover_render_input", x0);
 
@@ -264,8 +279,7 @@
                     subtractor_output, y0);
 
   // Choose the linear output.
-  data_dumper_->DumpWav("aec3_output_linear2", kBlockSize, &e[0],
-                        LowestBandRate(sample_rate_hz_), 1);
+  data_dumper_->DumpWav("aec3_output_linear2", kBlockSize, &e[0], 16000, 1);
   if (aec_state_.UseLinearFilterOutput()) {
     if (!linear_filter_output_last_selected_) {
       SignalTransition(y0, e, y0);
@@ -280,8 +294,7 @@
   linear_filter_output_last_selected_ = aec_state_.UseLinearFilterOutput();
   const auto& Y_fft = aec_state_.UseLinearFilterOutput() ? E : Y;
 
-  data_dumper_->DumpWav("aec3_output_linear", kBlockSize, &y0[0],
-                        LowestBandRate(sample_rate_hz_), 1);
+  data_dumper_->DumpWav("aec3_output_linear", kBlockSize, &y0[0], 16000, 1);
 
   // Estimate the residual echo power.
   residual_echo_estimator_.Estimate(aec_state_, *render_buffer, S2_linear, Y2,
@@ -317,16 +330,14 @@
 
   // Debug outputs for the purpose of development and analysis.
   data_dumper_->DumpWav("aec3_echo_estimate", kBlockSize,
-                        &subtractor_output.s_main[0],
-                        LowestBandRate(sample_rate_hz_), 1);
+                        &subtractor_output.s_main[0], 16000, 1);
   data_dumper_->DumpRaw("aec3_output", y0);
   data_dumper_->DumpRaw("aec3_narrow_render",
                         render_signal_analyzer_.NarrowPeakBand() ? 1 : 0);
   data_dumper_->DumpRaw("aec3_N2", cng_.NoiseSpectrum());
   data_dumper_->DumpRaw("aec3_suppressor_gain", G);
-  data_dumper_->DumpWav("aec3_output",
-                        rtc::ArrayView<const float>(&y0[0], kBlockSize),
-                        LowestBandRate(sample_rate_hz_), 1);
+  data_dumper_->DumpWav(
+      "aec3_output", rtc::ArrayView<const float>(&y0[0], kBlockSize), 16000, 1);
   data_dumper_->DumpRaw("aec3_using_subtractor_output",
                         aec_state_.UseLinearFilterOutput() ? 1 : 0);
   data_dumper_->DumpRaw("aec3_E2", E2);
@@ -390,8 +401,11 @@
 }  // namespace
 
 EchoRemover* EchoRemover::Create(const EchoCanceller3Config& config,
-                                 int sample_rate_hz) {
-  return new EchoRemoverImpl(config, sample_rate_hz);
+                                 int sample_rate_hz,
+                                 size_t num_render_channels,
+                                 size_t num_capture_channels) {
+  return new EchoRemoverImpl(config, sample_rate_hz, num_render_channels,
+                             num_capture_channels);
 }
 
 }  // namespace webrtc
diff --git a/modules/audio_processing/aec3/echo_remover.h b/modules/audio_processing/aec3/echo_remover.h
index 357f67d..6098a68 100644
--- a/modules/audio_processing/aec3/echo_remover.h
+++ b/modules/audio_processing/aec3/echo_remover.h
@@ -26,7 +26,9 @@
 class EchoRemover {
  public:
   static EchoRemover* Create(const EchoCanceller3Config& config,
-                             int sample_rate_hz);
+                             int sample_rate_hz,
+                             size_t num_render_channels,
+                             size_t num_capture_channels);
   virtual ~EchoRemover() = default;
 
   // Get current metrics.
@@ -40,7 +42,7 @@
       bool capture_signal_saturation,
       const absl::optional<DelayEstimate>& external_delay,
       RenderBuffer* render_buffer,
-      std::vector<std::vector<float>>* capture) = 0;
+      std::vector<std::vector<std::vector<float>>>* capture) = 0;
 
   // Updates the status on whether echo leakage is detected in the output of the
   // echo remover.
diff --git a/modules/audio_processing/aec3/echo_remover_unittest.cc b/modules/audio_processing/aec3/echo_remover_unittest.cc
index abe43ae..15d0913 100644
--- a/modules/audio_processing/aec3/echo_remover_unittest.cc
+++ b/modules/audio_processing/aec3/echo_remover_unittest.cc
@@ -44,29 +44,40 @@
 // Verifies the basic API call sequence
 TEST(EchoRemover, BasicApiCalls) {
   absl::optional<DelayEstimate> delay_estimate;
-  for (auto rate : {8000, 16000, 32000, 48000}) {
-    SCOPED_TRACE(ProduceDebugText(rate));
-    std::unique_ptr<EchoRemover> remover(
-        EchoRemover::Create(EchoCanceller3Config(), rate));
-    std::unique_ptr<RenderDelayBuffer> render_buffer(
-        RenderDelayBuffer::Create(EchoCanceller3Config(), rate));
+  for (auto rate : {16000, 32000, 48000}) {
+    for (size_t num_render_channels : {1, 2, 8}) {
+      for (size_t num_capture_channels : {1, 2, 8}) {
+        SCOPED_TRACE(ProduceDebugText(rate));
+        std::unique_ptr<EchoRemover> remover(
+            EchoRemover::Create(EchoCanceller3Config(), rate,
+                                num_render_channels, num_capture_channels));
+        std::unique_ptr<RenderDelayBuffer> render_buffer(
+            RenderDelayBuffer::Create(EchoCanceller3Config(), rate,
+                                      num_render_channels));
 
-    std::vector<std::vector<float>> render(NumBandsForRate(rate),
-                                           std::vector<float>(kBlockSize, 0.f));
-    std::vector<std::vector<float>> capture(
-        NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
-    for (size_t k = 0; k < 100; ++k) {
-      EchoPathVariability echo_path_variability(
-          k % 3 == 0 ? true : false,
-          k % 5 == 0 ? EchoPathVariability::DelayAdjustment::kNewDetectedDelay
-                     : EchoPathVariability::DelayAdjustment::kNone,
-          false);
-      render_buffer->Insert(render);
-      render_buffer->PrepareCaptureProcessing();
+        std::vector<std::vector<std::vector<float>>> render(
+            NumBandsForRate(rate),
+            std::vector<std::vector<float>>(
+                num_render_channels, std::vector<float>(kBlockSize, 0.f)));
+        std::vector<std::vector<std::vector<float>>> capture(
+            NumBandsForRate(rate),
+            std::vector<std::vector<float>>(
+                num_capture_channels, std::vector<float>(kBlockSize, 0.f)));
+        for (size_t k = 0; k < 100; ++k) {
+          EchoPathVariability echo_path_variability(
+              k % 3 == 0 ? true : false,
+              k % 5 == 0
+                  ? EchoPathVariability::DelayAdjustment::kNewDetectedDelay
+                  : EchoPathVariability::DelayAdjustment::kNone,
+              false);
+          render_buffer->Insert(render);
+          render_buffer->PrepareCaptureProcessing();
 
-      remover->ProcessCapture(echo_path_variability, k % 2 == 0 ? true : false,
-                              delay_estimate, render_buffer->GetRenderBuffer(),
-                              &capture);
+          remover->ProcessCapture(echo_path_variability,
+                                  k % 2 == 0 ? true : false, delay_estimate,
+                                  render_buffer->GetRenderBuffer(), &capture);
+        }
+      }
     }
   }
 }
@@ -78,21 +89,22 @@
 // tests on test bots has been fixed.
 TEST(EchoRemover, DISABLED_WrongSampleRate) {
   EXPECT_DEATH(std::unique_ptr<EchoRemover>(
-                   EchoRemover::Create(EchoCanceller3Config(), 8001)),
+                   EchoRemover::Create(EchoCanceller3Config(), 8001, 1, 1)),
                "");
 }
 
 // Verifies the check for the capture block size.
 TEST(EchoRemover, WrongCaptureBlockSize) {
   absl::optional<DelayEstimate> delay_estimate;
-  for (auto rate : {8000, 16000, 32000, 48000}) {
+  for (auto rate : {16000, 32000, 48000}) {
     SCOPED_TRACE(ProduceDebugText(rate));
     std::unique_ptr<EchoRemover> remover(
-        EchoRemover::Create(EchoCanceller3Config(), rate));
+        EchoRemover::Create(EchoCanceller3Config(), rate, 1, 1));
     std::unique_ptr<RenderDelayBuffer> render_buffer(
-        RenderDelayBuffer::Create(EchoCanceller3Config(), rate));
-    std::vector<std::vector<float>> capture(
-        NumBandsForRate(rate), std::vector<float>(kBlockSize - 1, 0.f));
+        RenderDelayBuffer::Create(EchoCanceller3Config(), rate, 1));
+    std::vector<std::vector<std::vector<float>>> capture(
+        NumBandsForRate(rate), std::vector<std::vector<float>>(
+                                   1, std::vector<float>(kBlockSize - 1, 0.f)));
     EchoPathVariability echo_path_variability(
         false, EchoPathVariability::DelayAdjustment::kNone, false);
     EXPECT_DEATH(
@@ -110,12 +122,13 @@
   for (auto rate : {16000, 32000, 48000}) {
     SCOPED_TRACE(ProduceDebugText(rate));
     std::unique_ptr<EchoRemover> remover(
-        EchoRemover::Create(EchoCanceller3Config(), rate));
+        EchoRemover::Create(EchoCanceller3Config(), rate, 1, 1));
     std::unique_ptr<RenderDelayBuffer> render_buffer(
-        RenderDelayBuffer::Create(EchoCanceller3Config(), rate));
-    std::vector<std::vector<float>> capture(
+        RenderDelayBuffer::Create(EchoCanceller3Config(), rate, 1));
+    std::vector<std::vector<std::vector<float>>> capture(
         NumBandsForRate(rate == 48000 ? 16000 : rate + 16000),
-        std::vector<float>(kBlockSize, 0.f));
+        std::vector<std::vector<float>>(1,
+                                        std::vector<float>(kBlockSize, 0.f)));
     EchoPathVariability echo_path_variability(
         false, EchoPathVariability::DelayAdjustment::kNone, false);
     EXPECT_DEATH(
@@ -129,9 +142,9 @@
 TEST(EchoRemover, NullCapture) {
   absl::optional<DelayEstimate> delay_estimate;
   std::unique_ptr<EchoRemover> remover(
-      EchoRemover::Create(EchoCanceller3Config(), 8000));
+      EchoRemover::Create(EchoCanceller3Config(), 16000, 1, 1));
   std::unique_ptr<RenderDelayBuffer> render_buffer(
-      RenderDelayBuffer::Create(EchoCanceller3Config(), 8000));
+      RenderDelayBuffer::Create(EchoCanceller3Config(), 16000, 1));
   EchoPathVariability echo_path_variability(
       false, EchoPathVariability::DelayAdjustment::kNone, false);
   EXPECT_DEATH(
@@ -148,61 +161,76 @@
   constexpr int kNumBlocksToProcess = 500;
   Random random_generator(42U);
   absl::optional<DelayEstimate> delay_estimate;
-  for (auto rate : {8000, 16000, 32000, 48000}) {
-    std::vector<std::vector<float>> x(NumBandsForRate(rate),
-                                      std::vector<float>(kBlockSize, 0.f));
-    std::vector<std::vector<float>> y(NumBandsForRate(rate),
-                                      std::vector<float>(kBlockSize, 0.f));
-    EchoPathVariability echo_path_variability(
-        false, EchoPathVariability::DelayAdjustment::kNone, false);
-    for (size_t delay_samples : {0, 64, 150, 200, 301}) {
-      SCOPED_TRACE(ProduceDebugText(rate, delay_samples));
-      EchoCanceller3Config config;
-      std::unique_ptr<EchoRemover> remover(EchoRemover::Create(config, rate));
-      std::unique_ptr<RenderDelayBuffer> render_buffer(
-          RenderDelayBuffer::Create(config, rate));
-      render_buffer->AlignFromDelay(delay_samples / kBlockSize);
+  for (size_t num_channels : {1, 2, 4}) {
+    for (auto rate : {16000, 32000, 48000}) {
+      std::vector<std::vector<std::vector<float>>> x(
+          NumBandsForRate(rate),
+          std::vector<std::vector<float>>(num_channels,
+                                          std::vector<float>(kBlockSize, 0.f)));
+      std::vector<std::vector<std::vector<float>>> y(
+          NumBandsForRate(rate),
+          std::vector<std::vector<float>>(num_channels,
+                                          std::vector<float>(kBlockSize, 0.f)));
+      EchoPathVariability echo_path_variability(
+          false, EchoPathVariability::DelayAdjustment::kNone, false);
+      for (size_t delay_samples : {0, 64, 150, 200, 301}) {
+        SCOPED_TRACE(ProduceDebugText(rate, delay_samples));
+        EchoCanceller3Config config;
+        std::unique_ptr<EchoRemover> remover(
+            EchoRemover::Create(config, rate, num_channels, num_channels));
+        std::unique_ptr<RenderDelayBuffer> render_buffer(
+            RenderDelayBuffer::Create(config, rate, num_channels));
+        render_buffer->AlignFromDelay(delay_samples / kBlockSize);
 
-      std::vector<std::unique_ptr<DelayBuffer<float>>> delay_buffers(x.size());
-      for (size_t j = 0; j < x.size(); ++j) {
-        delay_buffers[j].reset(new DelayBuffer<float>(delay_samples));
+        std::vector<std::vector<std::unique_ptr<DelayBuffer<float>>>>
+            delay_buffers(x.size());
+        for (size_t band = 0; band < delay_buffers.size(); ++band) {
+          delay_buffers[band].resize(x[0].size());
+        }
+
+        for (size_t band = 0; band < x.size(); ++band) {
+          for (size_t channel = 0; channel < x[0].size(); ++channel) {
+            delay_buffers[band][channel].reset(
+                new DelayBuffer<float>(delay_samples));
+          }
+        }
+
+        float input_energy = 0.f;
+        float output_energy = 0.f;
+        for (int k = 0; k < kNumBlocksToProcess; ++k) {
+          const bool silence = k < 100 || (k % 100 >= 10);
+
+          for (size_t band = 0; band < x.size(); ++band) {
+            for (size_t channel = 0; channel < x[0].size(); ++channel) {
+              if (silence) {
+                std::fill(x[band][channel].begin(), x[band][channel].end(),
+                          0.f);
+              } else {
+                RandomizeSampleVector(&random_generator, x[band][channel]);
+              }
+              delay_buffers[band][channel]->Delay(x[band][channel],
+                                                  y[band][channel]);
+            }
+          }
+
+          if (k > kNumBlocksToProcess / 2) {
+            input_energy = std::inner_product(y[0][0].begin(), y[0][0].end(),
+                                              y[0][0].begin(), input_energy);
+          }
+
+          render_buffer->Insert(x);
+          render_buffer->PrepareCaptureProcessing();
+
+          remover->ProcessCapture(echo_path_variability, false, delay_estimate,
+                                  render_buffer->GetRenderBuffer(), &y);
+
+          if (k > kNumBlocksToProcess / 2) {
+            output_energy = std::inner_product(y[0][0].begin(), y[0][0].end(),
+                                               y[0][0].begin(), output_energy);
+          }
+        }
+        EXPECT_GT(input_energy, 10.f * output_energy);
       }
-
-      float input_energy = 0.f;
-      float output_energy = 0.f;
-      for (int k = 0; k < kNumBlocksToProcess; ++k) {
-        const bool silence = k < 100 || (k % 100 >= 10);
-
-        for (size_t j = 0; j < x.size(); ++j) {
-          if (silence) {
-            std::fill(x[j].begin(), x[j].end(), 0.f);
-          } else {
-            RandomizeSampleVector(&random_generator, x[j]);
-          }
-          delay_buffers[j]->Delay(x[j], y[j]);
-        }
-
-        if (k > kNumBlocksToProcess / 2) {
-          for (size_t j = 0; j < x.size(); ++j) {
-            input_energy = std::inner_product(y[j].begin(), y[j].end(),
-                                              y[j].begin(), input_energy);
-          }
-        }
-
-        render_buffer->Insert(x);
-        render_buffer->PrepareCaptureProcessing();
-
-        remover->ProcessCapture(echo_path_variability, false, delay_estimate,
-                                render_buffer->GetRenderBuffer(), &y);
-
-        if (k > kNumBlocksToProcess / 2) {
-          for (size_t j = 0; j < x.size(); ++j) {
-            output_energy = std::inner_product(y[j].begin(), y[j].end(),
-                                               y[j].begin(), output_energy);
-          }
-        }
-      }
-      EXPECT_GT(input_energy, 10.f * output_energy);
     }
   }
 }
diff --git a/modules/audio_processing/aec3/erle_estimator_unittest.cc b/modules/audio_processing/aec3/erle_estimator_unittest.cc
index 31c550c..18ba25a 100644
--- a/modules/audio_processing/aec3/erle_estimator_unittest.cc
+++ b/modules/audio_processing/aec3/erle_estimator_unittest.cc
@@ -46,7 +46,7 @@
   EXPECT_NEAR(reference_lf, erle_time_domain, 0.5);
 }
 
-void FormFarendTimeFrame(rtc::ArrayView<float> x) {
+void FormFarendTimeFrame(std::vector<std::vector<std::vector<float>>>* x) {
   const std::array<float, kBlockSize> frame = {
       7459.88, 17209.6, 17383,   20768.9, 16816.7, 18386.3, 4492.83, 9675.85,
       6665.52, 14808.6, 9342.3,  7483.28, 19261.7, 4145.98, 1622.18, 13475.2,
@@ -56,8 +56,12 @@
       11405,   15031.4, 14541.6, 19765.5, 18346.3, 19350.2, 3157.47, 18095.8,
       1743.68, 21328.2, 19727.5, 7295.16, 10332.4, 11055.5, 20107.4, 14708.4,
       12416.2, 16434,   2454.69, 9840.8,  6867.23, 1615.75, 6059.9,  8394.19};
-  RTC_DCHECK_GE(x.size(), frame.size());
-  std::copy(frame.begin(), frame.end(), x.begin());
+  for (size_t band = 0; band < x->size(); ++band) {
+    for (size_t channel = 0; channel < (*x)[band].size(); ++channel) {
+      RTC_DCHECK_GE((*x)[band][channel].size(), frame.size());
+      std::copy(frame.begin(), frame.end(), (*x)[band][channel].begin());
+    }
+  }
 }
 
 void FormFarendFrame(const RenderBuffer& render_buffer,
@@ -75,14 +79,18 @@
 
 }  // namespace
 
-void FormNearendFrame(rtc::ArrayView<float> x,
+void FormNearendFrame(std::vector<std::vector<std::vector<float>>>* x,
                       std::array<float, kFftLengthBy2Plus1>* X2,
                       std::array<float, kFftLengthBy2Plus1>* E2,
                       std::array<float, kFftLengthBy2Plus1>* Y2) {
-  x[0] = 0.f;
-  X2->fill(0.f);
-  Y2->fill(500.f * 1000.f * 1000.f);
-  E2->fill((*Y2)[0]);
+  for (size_t band = 0; band < x->size(); ++band) {
+    for (size_t channel = 0; channel < (*x)[band].size(); ++channel) {
+      std::fill((*x)[band][channel].begin(), (*x)[band][channel].end(), 0.f);
+      X2->fill(0.f);
+      Y2->fill(500.f * 1000.f * 1000.f);
+      E2->fill((*Y2)[0]);
+    }
+  }
 }
 
 void GetFilterFreq(std::vector<std::array<float, kFftLengthBy2Plus1>>&
@@ -104,18 +112,24 @@
   std::array<float, kFftLengthBy2Plus1> X2;
   std::array<float, kFftLengthBy2Plus1> E2;
   std::array<float, kFftLengthBy2Plus1> Y2;
+  constexpr size_t kNumChannels = 1;
+  constexpr int kSampleRateHz = 48000;
+  constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz);
+
   EchoCanceller3Config config;
-  std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
+  std::vector<std::vector<std::vector<float>>> x(
+      kNumBands, std::vector<std::vector<float>>(
+                     kNumChannels, std::vector<float>(kBlockSize, 0.f)));
   std::vector<std::array<float, kFftLengthBy2Plus1>> filter_frequency_response(
       config.filter.main.length_blocks);
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(config, 48000));
+      RenderDelayBuffer::Create(config, kSampleRateHz, kNumChannels));
 
   GetFilterFreq(filter_frequency_response, config.delay.delay_headroom_samples);
 
   ErleEstimator estimator(0, config);
 
-  FormFarendTimeFrame(x[0]);
+  FormFarendTimeFrame(&x);
   render_delay_buffer->Insert(x);
   render_delay_buffer->PrepareCaptureProcessing();
   // Verifies that the ERLE estimate is properly increased to higher values.
@@ -130,7 +144,7 @@
   VerifyErle(estimator.Erle(), std::pow(2.f, estimator.FullbandErleLog2()),
              config.erle.max_l, config.erle.max_h);
 
-  FormNearendFrame(x[0], &X2, &E2, &Y2);
+  FormNearendFrame(&x, &X2, &E2, &Y2);
   // Verifies that the ERLE is not immediately decreased during nearend
   // activity.
   for (size_t k = 0; k < 50; ++k) {
@@ -144,22 +158,27 @@
 }
 
 TEST(ErleEstimator, VerifyErleTrackingOnOnsets) {
+  constexpr size_t kNumChannels = 1;
+  constexpr int kSampleRateHz = 48000;
+  constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz);
   std::array<float, kFftLengthBy2Plus1> X2;
   std::array<float, kFftLengthBy2Plus1> E2;
   std::array<float, kFftLengthBy2Plus1> Y2;
   EchoCanceller3Config config;
-  std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
+  std::vector<std::vector<std::vector<float>>> x(
+      kNumBands, std::vector<std::vector<float>>(
+                     kNumChannels, std::vector<float>(kBlockSize, 0.f)));
   std::vector<std::array<float, kFftLengthBy2Plus1>> filter_frequency_response(
       config.filter.main.length_blocks);
 
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(config, 48000));
+      RenderDelayBuffer::Create(config, kSampleRateHz, kNumChannels));
 
   GetFilterFreq(filter_frequency_response, config.delay.delay_headroom_samples);
 
   ErleEstimator estimator(0, config);
 
-  FormFarendTimeFrame(x[0]);
+  FormFarendTimeFrame(&x);
   render_delay_buffer->Insert(x);
   render_delay_buffer->PrepareCaptureProcessing();
 
@@ -180,7 +199,7 @@
       estimator.Update(*render_delay_buffer->GetRenderBuffer(),
                        filter_frequency_response, X2, Y2, E2, true, true);
     }
-    FormNearendFrame(x[0], &X2, &E2, &Y2);
+    FormNearendFrame(&x, &X2, &E2, &Y2);
     for (size_t k = 0; k < 300; ++k) {
       render_delay_buffer->Insert(x);
       render_delay_buffer->PrepareCaptureProcessing();
@@ -189,7 +208,7 @@
     }
   }
   VerifyErleBands(estimator.ErleOnsets(), config.erle.min, config.erle.min);
-  FormNearendFrame(x[0], &X2, &E2, &Y2);
+  FormNearendFrame(&x, &X2, &E2, &Y2);
   for (size_t k = 0; k < 1000; k++) {
     estimator.Update(*render_delay_buffer->GetRenderBuffer(),
                      filter_frequency_response, X2, Y2, E2, true, true);
diff --git a/modules/audio_processing/aec3/filter_analyzer.cc b/modules/audio_processing/aec3/filter_analyzer.cc
index 06bd4b7..138c188 100644
--- a/modules/audio_processing/aec3/filter_analyzer.cc
+++ b/modules/audio_processing/aec3/filter_analyzer.cc
@@ -96,8 +96,8 @@
   filter_length_blocks_ = filter_time_domain.size() * (1.f / kBlockSize);
 
   consistent_estimate_ = consistent_filter_detector_.Detect(
-      h_highpass_, region_, render_buffer.Block(-delay_blocks_)[0], peak_index_,
-      delay_blocks_);
+      h_highpass_, region_, render_buffer.Block(-delay_blocks_)[0][0],
+      peak_index_, delay_blocks_);
 }
 
 void FilterAnalyzer::UpdateFilterGain(
diff --git a/modules/audio_processing/aec3/frame_blocker.cc b/modules/audio_processing/aec3/frame_blocker.cc
index ca122e5..63aaf09 100644
--- a/modules/audio_processing/aec3/frame_blocker.cc
+++ b/modules/audio_processing/aec3/frame_blocker.cc
@@ -15,55 +15,73 @@
 
 namespace webrtc {
 
-FrameBlocker::FrameBlocker(size_t num_bands)
-    : num_bands_(num_bands), buffer_(num_bands_) {
-  for (auto& b : buffer_) {
-    b.reserve(kBlockSize);
-    RTC_DCHECK(b.empty());
+FrameBlocker::FrameBlocker(size_t num_bands, size_t num_channels)
+    : num_bands_(num_bands),
+      num_channels_(num_channels),
+      buffer_(num_bands_, std::vector<std::vector<float>>(num_channels)) {
+  RTC_DCHECK_LT(0, num_bands);
+  RTC_DCHECK_LT(0, num_channels);
+  for (auto& band : buffer_) {
+    for (auto& channel : band) {
+      channel.reserve(kBlockSize);
+      RTC_DCHECK(channel.empty());
+    }
   }
 }
 
 FrameBlocker::~FrameBlocker() = default;
 
 void FrameBlocker::InsertSubFrameAndExtractBlock(
-    const std::vector<rtc::ArrayView<float>>& sub_frame,
-    std::vector<std::vector<float>>* block) {
+    const std::vector<std::vector<rtc::ArrayView<float>>>& sub_frame,
+    std::vector<std::vector<std::vector<float>>>* block) {
   RTC_DCHECK(block);
   RTC_DCHECK_EQ(num_bands_, block->size());
   RTC_DCHECK_EQ(num_bands_, sub_frame.size());
-  for (size_t i = 0; i < num_bands_; ++i) {
-    RTC_DCHECK_GE(kBlockSize - 16, buffer_[i].size());
-    RTC_DCHECK_EQ(kBlockSize, (*block)[i].size());
-    RTC_DCHECK_EQ(kSubFrameLength, sub_frame[i].size());
-    const int samples_to_block = kBlockSize - buffer_[i].size();
-    (*block)[i].clear();
-    (*block)[i].insert((*block)[i].begin(), buffer_[i].begin(),
-                       buffer_[i].end());
-    (*block)[i].insert((*block)[i].begin() + buffer_[i].size(),
-                       sub_frame[i].begin(),
-                       sub_frame[i].begin() + samples_to_block);
-    buffer_[i].clear();
-    buffer_[i].insert(buffer_[i].begin(),
-                      sub_frame[i].begin() + samples_to_block,
-                      sub_frame[i].end());
+  for (size_t band = 0; band < num_bands_; ++band) {
+    RTC_DCHECK_EQ(num_channels_, (*block)[band].size());
+    RTC_DCHECK_EQ(num_channels_, sub_frame[band].size());
+    for (size_t channel = 0; channel < num_channels_; ++channel) {
+      RTC_DCHECK_GE(kBlockSize - 16, buffer_[band][channel].size());
+      RTC_DCHECK_EQ(kBlockSize, (*block)[band][channel].size());
+      RTC_DCHECK_EQ(kSubFrameLength, sub_frame[band][channel].size());
+      const int samples_to_block = kBlockSize - buffer_[band][channel].size();
+      (*block)[band][channel].clear();
+      (*block)[band][channel].insert((*block)[band][channel].begin(),
+                                     buffer_[band][channel].begin(),
+                                     buffer_[band][channel].end());
+      (*block)[band][channel].insert(
+          (*block)[band][channel].begin() + buffer_[band][channel].size(),
+          sub_frame[band][channel].begin(),
+          sub_frame[band][channel].begin() + samples_to_block);
+      buffer_[band][channel].clear();
+      buffer_[band][channel].insert(
+          buffer_[band][channel].begin(),
+          sub_frame[band][channel].begin() + samples_to_block,
+          sub_frame[band][channel].end());
+    }
   }
 }
 
 bool FrameBlocker::IsBlockAvailable() const {
-  return kBlockSize == buffer_[0].size();
+  return kBlockSize == buffer_[0][0].size();
 }
 
-void FrameBlocker::ExtractBlock(std::vector<std::vector<float>>* block) {
+void FrameBlocker::ExtractBlock(
+    std::vector<std::vector<std::vector<float>>>* block) {
   RTC_DCHECK(block);
   RTC_DCHECK_EQ(num_bands_, block->size());
   RTC_DCHECK(IsBlockAvailable());
-  for (size_t i = 0; i < num_bands_; ++i) {
-    RTC_DCHECK_EQ(kBlockSize, buffer_[i].size());
-    RTC_DCHECK_EQ(kBlockSize, (*block)[i].size());
-    (*block)[i].clear();
-    (*block)[i].insert((*block)[i].begin(), buffer_[i].begin(),
-                       buffer_[i].end());
-    buffer_[i].clear();
+  for (size_t band = 0; band < num_bands_; ++band) {
+    RTC_DCHECK_EQ(num_channels_, (*block)[band].size());
+    for (size_t channel = 0; channel < num_channels_; ++channel) {
+      RTC_DCHECK_EQ(kBlockSize, buffer_[band][channel].size());
+      RTC_DCHECK_EQ(kBlockSize, (*block)[band][channel].size());
+      (*block)[band][channel].clear();
+      (*block)[band][channel].insert((*block)[band][channel].begin(),
+                                     buffer_[band][channel].begin(),
+                                     buffer_[band][channel].end());
+      buffer_[band][channel].clear();
+    }
   }
 }
 
diff --git a/modules/audio_processing/aec3/frame_blocker.h b/modules/audio_processing/aec3/frame_blocker.h
index 759f431..ebd6f77 100644
--- a/modules/audio_processing/aec3/frame_blocker.h
+++ b/modules/audio_processing/aec3/frame_blocker.h
@@ -17,32 +17,33 @@
 
 #include "api/array_view.h"
 #include "modules/audio_processing/aec3/aec3_common.h"
-#include "rtc_base/constructor_magic.h"
 
 namespace webrtc {
 
-// Class for producing 64 sample multiband blocks from frames consisting of 1 or
-// 2 subframes of 80 samples.
+// Class for producing 64 sample multiband blocks from frames consisting of 2
+// subframes of 80 samples.
 class FrameBlocker {
  public:
-  explicit FrameBlocker(size_t num_bands);
+  FrameBlocker(size_t num_bands, size_t num_channels);
   ~FrameBlocker();
+  FrameBlocker(const FrameBlocker&) = delete;
+  FrameBlocker& operator=(const FrameBlocker&) = delete;
+
   // Inserts one 80 sample multiband subframe from the multiband frame and
   // extracts one 64 sample multiband block.
   void InsertSubFrameAndExtractBlock(
-      const std::vector<rtc::ArrayView<float>>& sub_frame,
-      std::vector<std::vector<float>>* block);
+      const std::vector<std::vector<rtc::ArrayView<float>>>& sub_frame,
+      std::vector<std::vector<std::vector<float>>>* block);
   // Reports whether a multiband block of 64 samples is available for
   // extraction.
   bool IsBlockAvailable() const;
   // Extracts a multiband block of 64 samples.
-  void ExtractBlock(std::vector<std::vector<float>>* block);
+  void ExtractBlock(std::vector<std::vector<std::vector<float>>>* block);
 
  private:
   const size_t num_bands_;
-  std::vector<std::vector<float>> buffer_;
-
-  RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(FrameBlocker);
+  const size_t num_channels_;
+  std::vector<std::vector<std::vector<float>>> buffer_;
 };
 }  // namespace webrtc
 
diff --git a/modules/audio_processing/aec3/frame_blocker_unittest.cc b/modules/audio_processing/aec3/frame_blocker_unittest.cc
index 3ec74cc..e907608 100644
--- a/modules/audio_processing/aec3/frame_blocker_unittest.cc
+++ b/modules/audio_processing/aec3/frame_blocker_unittest.cc
@@ -24,45 +24,62 @@
 float ComputeSampleValue(size_t chunk_counter,
                          size_t chunk_size,
                          size_t band,
+                         size_t channel,
                          size_t sample_index,
                          int offset) {
   float value =
-      static_cast<int>(chunk_counter * chunk_size + sample_index) + offset;
+      static_cast<int>(chunk_counter * chunk_size + sample_index + channel) +
+      offset;
   return value > 0 ? 5000 * band + value : 0;
 }
 
 void FillSubFrame(size_t sub_frame_counter,
                   int offset,
-                  std::vector<std::vector<float>>* sub_frame) {
-  for (size_t k = 0; k < sub_frame->size(); ++k) {
-    for (size_t i = 0; i < (*sub_frame)[0].size(); ++i) {
-      (*sub_frame)[k][i] =
-          ComputeSampleValue(sub_frame_counter, kSubFrameLength, k, i, offset);
+                  std::vector<std::vector<std::vector<float>>>* sub_frame) {
+  for (size_t band = 0; band < sub_frame->size(); ++band) {
+    for (size_t channel = 0; channel < (*sub_frame)[band].size(); ++channel) {
+      for (size_t sample = 0; sample < (*sub_frame)[band][channel].size();
+           ++sample) {
+        (*sub_frame)[band][channel][sample] = ComputeSampleValue(
+            sub_frame_counter, kSubFrameLength, band, channel, sample, offset);
+      }
     }
   }
 }
 
-void FillSubFrameView(size_t sub_frame_counter,
-                      int offset,
-                      std::vector<std::vector<float>>* sub_frame,
-                      std::vector<rtc::ArrayView<float>>* sub_frame_view) {
+void FillSubFrameView(
+    size_t sub_frame_counter,
+    int offset,
+    std::vector<std::vector<std::vector<float>>>* sub_frame,
+    std::vector<std::vector<rtc::ArrayView<float>>>* sub_frame_view) {
   FillSubFrame(sub_frame_counter, offset, sub_frame);
-  for (size_t k = 0; k < sub_frame_view->size(); ++k) {
-    (*sub_frame_view)[k] =
-        rtc::ArrayView<float>(&(*sub_frame)[k][0], (*sub_frame)[k].size());
+  for (size_t band = 0; band < sub_frame_view->size(); ++band) {
+    for (size_t channel = 0; channel < (*sub_frame_view)[band].size();
+         ++channel) {
+      (*sub_frame_view)[band][channel] = rtc::ArrayView<float>(
+          &(*sub_frame)[band][channel][0], (*sub_frame)[band][channel].size());
+    }
   }
 }
 
-bool VerifySubFrame(size_t sub_frame_counter,
-                    int offset,
-                    const std::vector<rtc::ArrayView<float>>& sub_frame_view) {
-  std::vector<std::vector<float>> reference_sub_frame(
-      sub_frame_view.size(), std::vector<float>(sub_frame_view[0].size(), 0.f));
+bool VerifySubFrame(
+    size_t sub_frame_counter,
+    int offset,
+    const std::vector<std::vector<rtc::ArrayView<float>>>& sub_frame_view) {
+  std::vector<std::vector<std::vector<float>>> reference_sub_frame(
+      sub_frame_view.size(),
+      std::vector<std::vector<float>>(
+          sub_frame_view[0].size(),
+          std::vector<float>(sub_frame_view[0][0].size(), 0.f)));
   FillSubFrame(sub_frame_counter, offset, &reference_sub_frame);
-  for (size_t k = 0; k < sub_frame_view.size(); ++k) {
-    for (size_t i = 0; i < sub_frame_view[k].size(); ++i) {
-      if (reference_sub_frame[k][i] != sub_frame_view[k][i]) {
-        return false;
+  for (size_t band = 0; band < sub_frame_view.size(); ++band) {
+    for (size_t channel = 0; channel < sub_frame_view[band].size(); ++channel) {
+      for (size_t sample = 0; sample < sub_frame_view[band][channel].size();
+           ++sample) {
+        if (reference_sub_frame[band][channel][sample] !=
+            sub_frame_view[band][channel][sample]) {
+          return false;
+        }
       }
     }
   }
@@ -71,13 +88,15 @@
 
 bool VerifyBlock(size_t block_counter,
                  int offset,
-                 const std::vector<std::vector<float>>& block) {
-  for (size_t k = 0; k < block.size(); ++k) {
-    for (size_t i = 0; i < block[k].size(); ++i) {
-      const float reference_value =
-          ComputeSampleValue(block_counter, kBlockSize, k, i, offset);
-      if (reference_value != block[k][i]) {
-        return false;
+                 const std::vector<std::vector<std::vector<float>>>& block) {
+  for (size_t band = 0; band < block.size(); ++band) {
+    for (size_t channel = 0; channel < block[band].size(); ++channel) {
+      for (size_t sample = 0; sample < block[band][channel].size(); ++sample) {
+        const float reference_value = ComputeSampleValue(
+            block_counter, kBlockSize, band, channel, sample, offset);
+        if (reference_value != block[band][channel][sample]) {
+          return false;
+        }
       }
     }
   }
@@ -85,16 +104,19 @@
 }
 
 // Verifies that the FrameBlocker properly forms blocks out of the frames.
-void RunBlockerTest(int sample_rate_hz) {
+void RunBlockerTest(int sample_rate_hz, size_t num_channels) {
   constexpr size_t kNumSubFramesToProcess = 20;
   const size_t num_bands = NumBandsForRate(sample_rate_hz);
 
-  std::vector<std::vector<float>> block(num_bands,
-                                        std::vector<float>(kBlockSize, 0.f));
-  std::vector<std::vector<float>> input_sub_frame(
-      num_bands, std::vector<float>(kSubFrameLength, 0.f));
-  std::vector<rtc::ArrayView<float>> input_sub_frame_view(num_bands);
-  FrameBlocker blocker(num_bands);
+  std::vector<std::vector<std::vector<float>>> block(
+      num_bands, std::vector<std::vector<float>>(
+                     num_channels, std::vector<float>(kBlockSize, 0.f)));
+  std::vector<std::vector<std::vector<float>>> input_sub_frame(
+      num_bands, std::vector<std::vector<float>>(
+                     num_channels, std::vector<float>(kSubFrameLength, 0.f)));
+  std::vector<std::vector<rtc::ArrayView<float>>> input_sub_frame_view(
+      num_bands, std::vector<rtc::ArrayView<float>>(num_channels));
+  FrameBlocker blocker(num_bands, num_channels);
 
   size_t block_counter = 0;
   for (size_t sub_frame_index = 0; sub_frame_index < kNumSubFramesToProcess;
@@ -119,20 +141,25 @@
 
 // Verifies that the FrameBlocker and BlockFramer work well together and produce
 // the expected output.
-void RunBlockerAndFramerTest(int sample_rate_hz) {
+void RunBlockerAndFramerTest(int sample_rate_hz, size_t num_channels) {
   const size_t kNumSubFramesToProcess = 20;
   const size_t num_bands = NumBandsForRate(sample_rate_hz);
 
-  std::vector<std::vector<float>> block(num_bands,
-                                        std::vector<float>(kBlockSize, 0.f));
-  std::vector<std::vector<float>> input_sub_frame(
-      num_bands, std::vector<float>(kSubFrameLength, 0.f));
-  std::vector<std::vector<float>> output_sub_frame(
-      num_bands, std::vector<float>(kSubFrameLength, 0.f));
-  std::vector<rtc::ArrayView<float>> output_sub_frame_view(num_bands);
-  std::vector<rtc::ArrayView<float>> input_sub_frame_view(num_bands);
-  FrameBlocker blocker(num_bands);
-  BlockFramer framer(num_bands);
+  std::vector<std::vector<std::vector<float>>> block(
+      num_bands, std::vector<std::vector<float>>(
+                     num_channels, std::vector<float>(kBlockSize, 0.f)));
+  std::vector<std::vector<std::vector<float>>> input_sub_frame(
+      num_bands, std::vector<std::vector<float>>(
+                     num_channels, std::vector<float>(kSubFrameLength, 0.f)));
+  std::vector<std::vector<std::vector<float>>> output_sub_frame(
+      num_bands, std::vector<std::vector<float>>(
+                     num_channels, std::vector<float>(kSubFrameLength, 0.f)));
+  std::vector<std::vector<rtc::ArrayView<float>>> output_sub_frame_view(
+      num_bands, std::vector<rtc::ArrayView<float>>(num_channels));
+  std::vector<std::vector<rtc::ArrayView<float>>> input_sub_frame_view(
+      num_bands, std::vector<rtc::ArrayView<float>>(num_channels));
+  FrameBlocker blocker(num_bands, num_channels);
+  BlockFramer framer(num_bands, num_channels);
 
   for (size_t sub_frame_index = 0; sub_frame_index < kNumSubFramesToProcess;
        ++sub_frame_index) {
@@ -153,28 +180,39 @@
       blocker.ExtractBlock(&block);
       framer.InsertBlock(block);
     }
-    EXPECT_TRUE(VerifySubFrame(sub_frame_index, -64, output_sub_frame_view));
+    if (sub_frame_index > 1) {
+      EXPECT_TRUE(VerifySubFrame(sub_frame_index, -64, output_sub_frame_view));
+    }
   }
 }
 
 #if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
 // Verifies that the FrameBlocker crashes if the InsertSubFrameAndExtractBlock
 // method is called for inputs with the wrong number of bands or band lengths.
-void RunWronglySizedInsertAndExtractParametersTest(int sample_rate_hz,
-                                                   size_t num_block_bands,
-                                                   size_t block_length,
-                                                   size_t num_sub_frame_bands,
-                                                   size_t sub_frame_length) {
+void RunWronglySizedInsertAndExtractParametersTest(
+    int sample_rate_hz,
+    size_t correct_num_channels,
+    size_t num_block_bands,
+    size_t num_block_channels,
+    size_t block_length,
+    size_t num_sub_frame_bands,
+    size_t num_sub_frame_channels,
+    size_t sub_frame_length) {
   const size_t correct_num_bands = NumBandsForRate(sample_rate_hz);
 
-  std::vector<std::vector<float>> block(num_block_bands,
-                                        std::vector<float>(block_length, 0.f));
-  std::vector<std::vector<float>> input_sub_frame(
-      num_sub_frame_bands, std::vector<float>(sub_frame_length, 0.f));
-  std::vector<rtc::ArrayView<float>> input_sub_frame_view(
-      input_sub_frame.size());
+  std::vector<std::vector<std::vector<float>>> block(
+      num_block_bands,
+      std::vector<std::vector<float>>(num_block_channels,
+                                      std::vector<float>(block_length, 0.f)));
+  std::vector<std::vector<std::vector<float>>> input_sub_frame(
+      num_sub_frame_bands,
+      std::vector<std::vector<float>>(
+          num_sub_frame_channels, std::vector<float>(sub_frame_length, 0.f)));
+  std::vector<std::vector<rtc::ArrayView<float>>> input_sub_frame_view(
+      input_sub_frame.size(),
+      std::vector<rtc::ArrayView<float>>(num_sub_frame_channels));
   FillSubFrameView(0, 0, &input_sub_frame, &input_sub_frame_view);
-  FrameBlocker blocker(correct_num_bands);
+  FrameBlocker blocker(correct_num_bands, correct_num_channels);
   EXPECT_DEATH(
       blocker.InsertSubFrameAndExtractBlock(input_sub_frame_view, &block), "");
 }
@@ -182,20 +220,29 @@
 // Verifies that the FrameBlocker crashes if the ExtractBlock method is called
 // for inputs with the wrong number of bands or band lengths.
 void RunWronglySizedExtractParameterTest(int sample_rate_hz,
+                                         size_t correct_num_channels,
                                          size_t num_block_bands,
+                                         size_t num_block_channels,
                                          size_t block_length) {
   const size_t correct_num_bands = NumBandsForRate(sample_rate_hz);
 
-  std::vector<std::vector<float>> correct_block(
-      correct_num_bands, std::vector<float>(kBlockSize, 0.f));
-  std::vector<std::vector<float>> wrong_block(
-      num_block_bands, std::vector<float>(block_length, 0.f));
-  std::vector<std::vector<float>> input_sub_frame(
-      correct_num_bands, std::vector<float>(kSubFrameLength, 0.f));
-  std::vector<rtc::ArrayView<float>> input_sub_frame_view(
-      input_sub_frame.size());
+  std::vector<std::vector<std::vector<float>>> correct_block(
+      correct_num_bands,
+      std::vector<std::vector<float>>(correct_num_channels,
+                                      std::vector<float>(kBlockSize, 0.f)));
+  std::vector<std::vector<std::vector<float>>> wrong_block(
+      num_block_bands,
+      std::vector<std::vector<float>>(num_block_channels,
+                                      std::vector<float>(block_length, 0.f)));
+  std::vector<std::vector<std::vector<float>>> input_sub_frame(
+      correct_num_bands,
+      std::vector<std::vector<float>>(
+          correct_num_channels, std::vector<float>(kSubFrameLength, 0.f)));
+  std::vector<std::vector<rtc::ArrayView<float>>> input_sub_frame_view(
+      input_sub_frame.size(),
+      std::vector<rtc::ArrayView<float>>(correct_num_channels));
   FillSubFrameView(0, 0, &input_sub_frame, &input_sub_frame_view);
-  FrameBlocker blocker(correct_num_bands);
+  FrameBlocker blocker(correct_num_bands, correct_num_channels);
   blocker.InsertSubFrameAndExtractBlock(input_sub_frame_view, &correct_block);
   blocker.InsertSubFrameAndExtractBlock(input_sub_frame_view, &correct_block);
   blocker.InsertSubFrameAndExtractBlock(input_sub_frame_view, &correct_block);
@@ -208,17 +255,20 @@
 // after a wrong number of previous InsertSubFrameAndExtractBlock method calls
 // have been made.
 void RunWrongExtractOrderTest(int sample_rate_hz,
+                              size_t num_channels,
                               size_t num_preceeding_api_calls) {
-  const size_t correct_num_bands = NumBandsForRate(sample_rate_hz);
+  const size_t num_bands = NumBandsForRate(sample_rate_hz);
 
-  std::vector<std::vector<float>> block(correct_num_bands,
-                                        std::vector<float>(kBlockSize, 0.f));
-  std::vector<std::vector<float>> input_sub_frame(
-      correct_num_bands, std::vector<float>(kSubFrameLength, 0.f));
-  std::vector<rtc::ArrayView<float>> input_sub_frame_view(
-      input_sub_frame.size());
+  std::vector<std::vector<std::vector<float>>> block(
+      num_bands, std::vector<std::vector<float>>(
+                     num_channels, std::vector<float>(kBlockSize, 0.f)));
+  std::vector<std::vector<std::vector<float>>> input_sub_frame(
+      num_bands, std::vector<std::vector<float>>(
+                     num_channels, std::vector<float>(kSubFrameLength, 0.f)));
+  std::vector<std::vector<rtc::ArrayView<float>>> input_sub_frame_view(
+      input_sub_frame.size(), std::vector<rtc::ArrayView<float>>(num_channels));
   FillSubFrameView(0, 0, &input_sub_frame, &input_sub_frame_view);
-  FrameBlocker blocker(correct_num_bands);
+  FrameBlocker blocker(num_bands, num_channels);
   for (size_t k = 0; k < num_preceeding_api_calls; ++k) {
     blocker.InsertSubFrameAndExtractBlock(input_sub_frame_view, &block);
   }
@@ -227,9 +277,10 @@
 }
 #endif
 
-std::string ProduceDebugText(int sample_rate_hz) {
+std::string ProduceDebugText(int sample_rate_hz, size_t num_channels) {
   rtc::StringBuilder ss;
   ss << "Sample rate: " << sample_rate_hz;
+  ss << ", number of channels: " << num_channels;
   return ss.Release();
 }
 
@@ -237,104 +288,183 @@
 
 #if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
 TEST(FrameBlocker, WrongNumberOfBandsInBlockForInsertSubFrameAndExtractBlock) {
-  for (auto rate : {8000, 16000, 32000, 48000}) {
-    SCOPED_TRACE(ProduceDebugText(rate));
-    const size_t correct_num_bands = NumBandsForRate(rate);
-    const size_t wrong_num_bands = (correct_num_bands % 3) + 1;
-    RunWronglySizedInsertAndExtractParametersTest(
-        rate, wrong_num_bands, kBlockSize, correct_num_bands, kSubFrameLength);
+  for (auto rate : {16000, 32000, 48000}) {
+    for (size_t correct_num_channels : {1, 2, 4, 8}) {
+      SCOPED_TRACE(ProduceDebugText(rate, correct_num_channels));
+      const size_t correct_num_bands = NumBandsForRate(rate);
+      const size_t wrong_num_bands = (correct_num_bands % 3) + 1;
+      RunWronglySizedInsertAndExtractParametersTest(
+          rate, correct_num_channels, wrong_num_bands, correct_num_channels,
+          kBlockSize, correct_num_bands, correct_num_channels, kSubFrameLength);
+    }
+  }
+}
+
+TEST(FrameBlocker,
+     WrongNumberOfChannelsInBlockForInsertSubFrameAndExtractBlock) {
+  for (auto rate : {16000, 32000, 48000}) {
+    for (size_t correct_num_channels : {1, 2, 4, 8}) {
+      SCOPED_TRACE(ProduceDebugText(rate, correct_num_channels));
+      const size_t correct_num_bands = NumBandsForRate(rate);
+      const size_t wrong_num_channels = correct_num_channels + 1;
+      RunWronglySizedInsertAndExtractParametersTest(
+          rate, correct_num_channels, correct_num_bands, wrong_num_channels,
+          kBlockSize, correct_num_bands, correct_num_channels, kSubFrameLength);
+    }
   }
 }
 
 TEST(FrameBlocker,
      WrongNumberOfBandsInSubFrameForInsertSubFrameAndExtractBlock) {
-  for (auto rate : {8000, 16000, 32000, 48000}) {
-    SCOPED_TRACE(ProduceDebugText(rate));
-    const size_t correct_num_bands = NumBandsForRate(rate);
-    const size_t wrong_num_bands = (correct_num_bands % 3) + 1;
-    RunWronglySizedInsertAndExtractParametersTest(
-        rate, correct_num_bands, kBlockSize, wrong_num_bands, kSubFrameLength);
+  for (auto rate : {16000, 32000, 48000}) {
+    for (size_t correct_num_channels : {1, 2, 4, 8}) {
+      SCOPED_TRACE(ProduceDebugText(rate, correct_num_channels));
+      const size_t correct_num_bands = NumBandsForRate(rate);
+      const size_t wrong_num_bands = (correct_num_bands % 3) + 1;
+      RunWronglySizedInsertAndExtractParametersTest(
+          rate, correct_num_channels, correct_num_bands, correct_num_channels,
+          kBlockSize, wrong_num_bands, correct_num_channels, kSubFrameLength);
+    }
+  }
+}
+
+TEST(FrameBlocker,
+     WrongNumberOfChannelsInSubFrameForInsertSubFrameAndExtractBlock) {
+  for (auto rate : {16000, 32000, 48000}) {
+    for (size_t correct_num_channels : {1, 2, 4, 8}) {
+      SCOPED_TRACE(ProduceDebugText(rate, correct_num_channels));
+      const size_t correct_num_bands = NumBandsForRate(rate);
+      const size_t wrong_num_channels = correct_num_channels + 1;
+      RunWronglySizedInsertAndExtractParametersTest(
+          rate, correct_num_channels, correct_num_bands, wrong_num_channels,
+          kBlockSize, correct_num_bands, wrong_num_channels, kSubFrameLength);
+    }
   }
 }
 
 TEST(FrameBlocker,
      WrongNumberOfSamplesInBlockForInsertSubFrameAndExtractBlock) {
-  for (auto rate : {8000, 16000, 32000, 48000}) {
-    SCOPED_TRACE(ProduceDebugText(rate));
-    const size_t correct_num_bands = NumBandsForRate(rate);
-    RunWronglySizedInsertAndExtractParametersTest(
-        rate, correct_num_bands, kBlockSize - 1, correct_num_bands,
-        kSubFrameLength);
+  for (auto rate : {16000, 32000, 48000}) {
+    for (size_t correct_num_channels : {1, 2, 4, 8}) {
+      SCOPED_TRACE(ProduceDebugText(rate, correct_num_channels));
+      const size_t correct_num_bands = NumBandsForRate(rate);
+      RunWronglySizedInsertAndExtractParametersTest(
+          rate, correct_num_channels, correct_num_bands, correct_num_channels,
+          kBlockSize - 1, correct_num_bands, correct_num_channels,
+          kSubFrameLength);
+    }
   }
 }
 
 TEST(FrameBlocker,
      WrongNumberOfSamplesInSubFrameForInsertSubFrameAndExtractBlock) {
-  for (auto rate : {8000, 16000, 32000, 48000}) {
-    SCOPED_TRACE(ProduceDebugText(rate));
-    const size_t correct_num_bands = NumBandsForRate(rate);
-    RunWronglySizedInsertAndExtractParametersTest(rate, correct_num_bands,
-                                                  kBlockSize, correct_num_bands,
-                                                  kSubFrameLength - 1);
-  }
-}
-
-TEST(FrameBlocker, WrongNumberOfBandsInBlockForExtractBlock) {
-  for (auto rate : {8000, 16000, 32000, 48000}) {
-    SCOPED_TRACE(ProduceDebugText(rate));
-    const size_t correct_num_bands = NumBandsForRate(rate);
-    const size_t wrong_num_bands = (correct_num_bands % 3) + 1;
-    RunWronglySizedExtractParameterTest(rate, wrong_num_bands, kBlockSize);
-  }
-}
-
-TEST(FrameBlocker, WrongNumberOfSamplesInBlockForExtractBlock) {
-  for (auto rate : {8000, 16000, 32000, 48000}) {
-    SCOPED_TRACE(ProduceDebugText(rate));
-    const size_t correct_num_bands = NumBandsForRate(rate);
-    RunWronglySizedExtractParameterTest(rate, correct_num_bands,
-                                        kBlockSize - 1);
-  }
-}
-
-TEST(FrameBlocker, WrongNumberOfPreceedingApiCallsForExtractBlock) {
-  for (auto rate : {8000, 16000, 32000, 48000}) {
-    for (size_t num_calls = 0; num_calls < 4; ++num_calls) {
-      rtc::StringBuilder ss;
-      ss << "Sample rate: " << rate;
-      ss << ", Num preceeding InsertSubFrameAndExtractBlock calls: "
-         << num_calls;
-
-      SCOPED_TRACE(ss.str());
-      RunWrongExtractOrderTest(rate, num_calls);
+  for (auto rate : {16000, 32000, 48000}) {
+    for (size_t correct_num_channels : {1, 2, 4, 8}) {
+      SCOPED_TRACE(ProduceDebugText(rate, correct_num_channels));
+      const size_t correct_num_bands = NumBandsForRate(rate);
+      RunWronglySizedInsertAndExtractParametersTest(
+          rate, correct_num_channels, correct_num_bands, correct_num_channels,
+          kBlockSize, correct_num_bands, correct_num_channels,
+          kSubFrameLength - 1);
     }
   }
 }
 
+TEST(FrameBlocker, WrongNumberOfBandsInBlockForExtractBlock) {
+  for (auto rate : {16000, 32000, 48000}) {
+    for (size_t correct_num_channels : {1, 2, 4, 8}) {
+      SCOPED_TRACE(ProduceDebugText(rate, correct_num_channels));
+      const size_t correct_num_bands = NumBandsForRate(rate);
+      const size_t wrong_num_bands = (correct_num_bands % 3) + 1;
+      RunWronglySizedExtractParameterTest(rate, correct_num_channels,
+                                          wrong_num_bands, correct_num_channels,
+                                          kBlockSize);
+    }
+  }
+}
+
+TEST(FrameBlocker, WrongNumberOfChannelsInBlockForExtractBlock) {
+  for (auto rate : {16000, 32000, 48000}) {
+    for (size_t correct_num_channels : {1, 2, 4, 8}) {
+      SCOPED_TRACE(ProduceDebugText(rate, correct_num_channels));
+      const size_t correct_num_bands = NumBandsForRate(rate);
+      const size_t wrong_num_channels = correct_num_channels + 1;
+      RunWronglySizedExtractParameterTest(rate, correct_num_channels,
+                                          correct_num_bands, wrong_num_channels,
+                                          kBlockSize);
+    }
+  }
+}
+
+TEST(FrameBlocker, WrongNumberOfSamplesInBlockForExtractBlock) {
+  for (auto rate : {16000, 32000, 48000}) {
+    for (size_t correct_num_channels : {1, 2, 4, 8}) {
+      SCOPED_TRACE(ProduceDebugText(rate, correct_num_channels));
+      const size_t correct_num_bands = NumBandsForRate(rate);
+      RunWronglySizedExtractParameterTest(rate, correct_num_channels,
+                                          correct_num_bands,
+                                          correct_num_channels, kBlockSize - 1);
+    }
+  }
+}
+
+TEST(FrameBlocker, WrongNumberOfPreceedingApiCallsForExtractBlock) {
+  for (auto rate : {16000, 32000, 48000}) {
+    for (size_t num_channels : {1, 2, 4, 8}) {
+      for (size_t num_calls = 0; num_calls < 4; ++num_calls) {
+        rtc::StringBuilder ss;
+        ss << "Sample rate: " << rate;
+        ss << "Num channels: " << num_channels;
+        ss << ", Num preceeding InsertSubFrameAndExtractBlock calls: "
+           << num_calls;
+
+        SCOPED_TRACE(ss.str());
+        RunWrongExtractOrderTest(rate, num_channels, num_calls);
+      }
+    }
+  }
+}
+
+// Verifies that the verification for 0 number of channels works.
+TEST(FrameBlocker, ZeroNumberOfChannelsParameter) {
+  EXPECT_DEATH(FrameBlocker(16000, 0), "");
+}
+
+// Verifies that the verification for 0 number of bands works.
+TEST(FrameBlocker, ZeroNumberOfBandsParameter) {
+  EXPECT_DEATH(FrameBlocker(0, 1), "");
+}
+
 // Verifiers that the verification for null sub_frame pointer works.
 TEST(FrameBlocker, NullBlockParameter) {
-  std::vector<std::vector<float>> sub_frame(
-      1, std::vector<float>(kSubFrameLength, 0.f));
-  std::vector<rtc::ArrayView<float>> sub_frame_view(sub_frame.size());
+  std::vector<std::vector<std::vector<float>>> sub_frame(
+      1, std::vector<std::vector<float>>(
+             1, std::vector<float>(kSubFrameLength, 0.f)));
+  std::vector<std::vector<rtc::ArrayView<float>>> sub_frame_view(
+      sub_frame.size());
   FillSubFrameView(0, 0, &sub_frame, &sub_frame_view);
   EXPECT_DEATH(
-      FrameBlocker(1).InsertSubFrameAndExtractBlock(sub_frame_view, nullptr),
+      FrameBlocker(1, 1).InsertSubFrameAndExtractBlock(sub_frame_view, nullptr),
       "");
 }
 
 #endif
 
 TEST(FrameBlocker, BlockBitexactness) {
-  for (auto rate : {8000, 16000, 32000, 48000}) {
-    SCOPED_TRACE(ProduceDebugText(rate));
-    RunBlockerTest(rate);
+  for (auto rate : {16000, 32000, 48000}) {
+    for (size_t num_channels : {1, 2, 4, 8}) {
+      SCOPED_TRACE(ProduceDebugText(rate, num_channels));
+      RunBlockerTest(rate, num_channels);
+    }
   }
 }
 
 TEST(FrameBlocker, BlockerAndFramer) {
-  for (auto rate : {8000, 16000, 32000, 48000}) {
-    SCOPED_TRACE(ProduceDebugText(rate));
-    RunBlockerAndFramerTest(rate);
+  for (auto rate : {16000, 32000, 48000}) {
+    for (size_t num_channels : {1, 2, 4, 8}) {
+      SCOPED_TRACE(ProduceDebugText(rate, num_channels));
+      RunBlockerAndFramerTest(rate, num_channels);
+    }
   }
 }
 
diff --git a/modules/audio_processing/aec3/main_filter_update_gain_unittest.cc b/modules/audio_processing/aec3/main_filter_update_gain_unittest.cc
index 34412b8..648762a 100644
--- a/modules/audio_processing/aec3/main_filter_update_gain_unittest.cc
+++ b/modules/audio_processing/aec3/main_filter_update_gain_unittest.cc
@@ -42,6 +42,10 @@
                          std::array<float, kBlockSize>* y_last_block,
                          FftData* G_last_block) {
   ApmDataDumper data_dumper(42);
+  constexpr size_t kNumChannels = 1;
+  constexpr int kSampleRateHz = 48000;
+  constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz);
+
   EchoCanceller3Config config;
   config.filter.main.length_blocks = filter_length_blocks;
   config.filter.shadow.length_blocks = filter_length_blocks;
@@ -61,11 +65,13 @@
   MainFilterUpdateGain main_gain(config.filter.main,
                                  config.filter.config_change_duration_blocks);
   Random random_generator(42U);
-  std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
+  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);
   config.delay.default_delay = 1;
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(config, 48000));
+      RenderDelayBuffer::Create(config, kSampleRateHz, kNumChannels));
   AecState aec_state(config);
   RenderSignalAnalyzer render_signal_analyzer(config);
   absl::optional<DelayEstimate> delay_estimate;
@@ -101,11 +107,19 @@
 
     // Create the render signal.
     if (use_silent_render_in_second_half && k > num_blocks_to_process / 2) {
-      std::fill(x[0].begin(), x[0].end(), 0.f);
+      for (size_t band = 0; band < x.size(); ++band) {
+        for (size_t channel = 0; channel < x[band].size(); ++channel) {
+          std::fill(x[band][channel].begin(), x[band][channel].end(), 0.f);
+        }
+      }
     } else {
-      RandomizeSampleVector(&random_generator, x[0]);
+      for (size_t band = 0; band < x.size(); ++band) {
+        for (size_t channel = 0; channel < x[band].size(); ++channel) {
+          RandomizeSampleVector(&random_generator, x[band][channel]);
+        }
+      }
     }
-    delay_buffer.Delay(x[0], y);
+    delay_buffer.Delay(x[0][0], y);
 
     render_delay_buffer->Insert(x);
     if (k == 0) {
diff --git a/modules/audio_processing/aec3/matched_filter.cc b/modules/audio_processing/aec3/matched_filter.cc
index 757219d..5a62b7c 100644
--- a/modules/audio_processing/aec3/matched_filter.cc
+++ b/modules/audio_processing/aec3/matched_filter.cc
@@ -442,15 +442,15 @@
                                         size_t shift,
                                         size_t downsampling_factor) const {
   size_t alignment_shift = 0;
-  const int fs_by_1000 = LowestBandRate(sample_rate_hz) / 1000;
+  constexpr int kFsBy1000 = 16;
   for (size_t k = 0; k < filters_.size(); ++k) {
     int start = static_cast<int>(alignment_shift * downsampling_factor);
     int end = static_cast<int>((alignment_shift + filters_[k].size()) *
                                downsampling_factor);
     RTC_LOG(LS_INFO) << "Filter " << k << ": start: "
-                     << (start - static_cast<int>(shift)) / fs_by_1000
+                     << (start - static_cast<int>(shift)) / kFsBy1000
                      << " ms, end: "
-                     << (end - static_cast<int>(shift)) / fs_by_1000 << " ms.";
+                     << (end - static_cast<int>(shift)) / kFsBy1000 << " ms.";
     alignment_shift += filter_intra_lag_shift_;
   }
 }
diff --git a/modules/audio_processing/aec3/matched_filter_unittest.cc b/modules/audio_processing/aec3/matched_filter_unittest.cc
index c204af4..8f2c5c2 100644
--- a/modules/audio_processing/aec3/matched_filter_unittest.cc
+++ b/modules/audio_processing/aec3/matched_filter_unittest.cc
@@ -140,11 +140,16 @@
 // delayed signals.
 TEST(MatchedFilter, LagEstimation) {
   Random random_generator(42U);
+  constexpr size_t kNumChannels = 1;
+  constexpr int kSampleRateHz = 48000;
+  constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz);
+
   for (auto down_sampling_factor : kDownSamplingFactors) {
     const size_t sub_block_size = kBlockSize / down_sampling_factor;
 
-    std::vector<std::vector<float>> render(3,
-                                           std::vector<float>(kBlockSize, 0.f));
+    std::vector<std::vector<std::vector<float>>> render(
+        kNumBands, std::vector<std::vector<float>>(
+                       kNumChannels, std::vector<float>(kBlockSize, 0.f)));
     std::array<float, kBlockSize> capture;
     capture.fill(0.f);
     ApmDataDumper data_dumper(0);
@@ -163,12 +168,16 @@
                            config.delay.delay_candidate_detection_threshold);
 
       std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-          RenderDelayBuffer::Create(config, 48000));
+          RenderDelayBuffer::Create(config, kSampleRateHz, kNumChannels));
 
       // Analyze the correlation between render and capture.
       for (size_t k = 0; k < (600 + delay_samples / sub_block_size); ++k) {
-        RandomizeSampleVector(&random_generator, render[0]);
-        signal_delay_buffer.Delay(render[0], capture);
+        for (size_t band = 0; band < kNumBands; ++band) {
+          for (size_t channel = 0; channel < kNumChannels; ++channel) {
+            RandomizeSampleVector(&random_generator, render[band][channel]);
+          }
+        }
+        signal_delay_buffer.Delay(render[0][0], capture);
         render_delay_buffer->Insert(render);
 
         if (k == 0) {
@@ -245,6 +254,9 @@
 // Verifies that the matched filter does not produce reliable and accurate
 // estimates for uncorrelated render and capture signals.
 TEST(MatchedFilter, LagNotReliableForUncorrelatedRenderAndCapture) {
+  constexpr size_t kNumChannels = 1;
+  constexpr int kSampleRateHz = 48000;
+  constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz);
   Random random_generator(42U);
   for (auto down_sampling_factor : kDownSamplingFactors) {
     EchoCanceller3Config config;
@@ -252,14 +264,15 @@
     config.delay.num_filters = kNumMatchedFilters;
     const size_t sub_block_size = kBlockSize / down_sampling_factor;
 
-    std::vector<std::vector<float>> render(3,
-                                           std::vector<float>(kBlockSize, 0.f));
+    std::vector<std::vector<std::vector<float>>> render(
+        kNumBands, std::vector<std::vector<float>>(
+                       kNumChannels, std::vector<float>(kBlockSize, 0.f)));
     std::array<float, kBlockSize> capture_data;
     rtc::ArrayView<float> capture(capture_data.data(), sub_block_size);
     std::fill(capture.begin(), capture.end(), 0.f);
     ApmDataDumper data_dumper(0);
     std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-        RenderDelayBuffer::Create(config, 48000));
+        RenderDelayBuffer::Create(config, kSampleRateHz, kNumChannels));
     MatchedFilter filter(&data_dumper, DetectOptimization(), sub_block_size,
                          kWindowSizeSubBlocks, kNumMatchedFilters,
                          kAlignmentShiftSubBlocks, 150,
@@ -268,7 +281,7 @@
 
     // Analyze the correlation between render and capture.
     for (size_t k = 0; k < 100; ++k) {
-      RandomizeSampleVector(&random_generator, render[0]);
+      RandomizeSampleVector(&random_generator, render[0][0]);
       RandomizeSampleVector(&random_generator, capture);
       render_delay_buffer->Insert(render);
       filter.Update(render_delay_buffer->GetDownsampledRenderBuffer(), capture);
@@ -289,11 +302,16 @@
 // render signals of low level.
 TEST(MatchedFilter, LagNotUpdatedForLowLevelRender) {
   Random random_generator(42U);
+  constexpr size_t kNumChannels = 1;
+  constexpr int kSampleRateHz = 48000;
+  constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz);
+
   for (auto down_sampling_factor : kDownSamplingFactors) {
     const size_t sub_block_size = kBlockSize / down_sampling_factor;
 
-    std::vector<std::vector<float>> render(3,
-                                           std::vector<float>(kBlockSize, 0.f));
+    std::vector<std::vector<std::vector<float>>> render(
+        kNumBands, std::vector<std::vector<float>>(
+                       kNumChannels, std::vector<float>(kBlockSize, 0.f)));
     std::array<float, kBlockSize> capture;
     capture.fill(0.f);
     ApmDataDumper data_dumper(0);
@@ -304,16 +322,17 @@
                          config.delay.delay_estimate_smoothing,
                          config.delay.delay_candidate_detection_threshold);
     std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-        RenderDelayBuffer::Create(EchoCanceller3Config(), 48000));
+        RenderDelayBuffer::Create(EchoCanceller3Config(), kSampleRateHz,
+                                  kNumChannels));
     Decimator capture_decimator(down_sampling_factor);
 
     // Analyze the correlation between render and capture.
     for (size_t k = 0; k < 100; ++k) {
-      RandomizeSampleVector(&random_generator, render[0]);
-      for (auto& render_k : render[0]) {
+      RandomizeSampleVector(&random_generator, render[0][0]);
+      for (auto& render_k : render[0][0]) {
         render_k *= 149.f / 32767.f;
       }
-      std::copy(render[0].begin(), render[0].end(), capture.begin());
+      std::copy(render[0][0].begin(), render[0][0].end(), capture.begin());
       std::array<float, kBlockSize> downsampled_capture_data;
       rtc::ArrayView<float> downsampled_capture(downsampled_capture_data.data(),
                                                 sub_block_size);
diff --git a/modules/audio_processing/aec3/matrix_buffer.cc b/modules/audio_processing/aec3/matrix_buffer.cc
index bd6daea..2fd71b4 100644
--- a/modules/audio_processing/aec3/matrix_buffer.cc
+++ b/modules/audio_processing/aec3/matrix_buffer.cc
@@ -14,14 +14,22 @@
 
 namespace webrtc {
 
-MatrixBuffer::MatrixBuffer(size_t size, size_t height, size_t width)
+MatrixBuffer::MatrixBuffer(size_t size,
+                           size_t num_bands,
+                           size_t num_channels,
+                           size_t frame_length)
     : size(static_cast<int>(size)),
       buffer(size,
-             std::vector<std::vector<float>>(height,
-                                             std::vector<float>(width, 0.f))) {
-  for (auto& c : buffer) {
-    for (auto& b : c) {
-      std::fill(b.begin(), b.end(), 0.f);
+             std::vector<std::vector<std::vector<float>>>(
+                 num_bands,
+                 std::vector<std::vector<float>>(
+                     num_channels,
+                     std::vector<float>(frame_length, 0.f)))) {
+  for (auto& block : buffer) {
+    for (auto& band : block) {
+      for (auto& channel : band) {
+        std::fill(channel.begin(), channel.end(), 0.f);
+      }
     }
   }
 }
diff --git a/modules/audio_processing/aec3/matrix_buffer.h b/modules/audio_processing/aec3/matrix_buffer.h
index 8fb96d21..97736a3 100644
--- a/modules/audio_processing/aec3/matrix_buffer.h
+++ b/modules/audio_processing/aec3/matrix_buffer.h
@@ -21,8 +21,12 @@
 
 // Struct for bundling a circular buffer of two dimensional vector objects
 // together with the read and write indices.
+// TODO(peah): Change name of this class to be more specific to what it does.
 struct MatrixBuffer {
-  MatrixBuffer(size_t size, size_t height, size_t width);
+  MatrixBuffer(size_t size,
+               size_t num_bands,
+               size_t num_channels,
+               size_t frame_length);
   ~MatrixBuffer();
 
   int IncIndex(int index) const {
@@ -49,7 +53,7 @@
   void DecReadIndex() { read = DecIndex(read); }
 
   const int size;
-  std::vector<std::vector<std::vector<float>>> buffer;
+  std::vector<std::vector<std::vector<std::vector<float>>>> buffer;
   int write = 0;
   int read = 0;
 };
diff --git a/modules/audio_processing/aec3/mock/mock_block_processor.h b/modules/audio_processing/aec3/mock/mock_block_processor.h
index 85b88f7..cb93714 100644
--- a/modules/audio_processing/aec3/mock/mock_block_processor.h
+++ b/modules/audio_processing/aec3/mock/mock_block_processor.h
@@ -24,12 +24,13 @@
   MockBlockProcessor();
   virtual ~MockBlockProcessor();
 
-  MOCK_METHOD3(ProcessCapture,
-               void(bool level_change,
-                    bool saturated_microphone_signal,
-                    std::vector<std::vector<float>>* capture_block));
+  MOCK_METHOD3(
+      ProcessCapture,
+      void(bool level_change,
+           bool saturated_microphone_signal,
+           std::vector<std::vector<std::vector<float>>>* capture_block));
   MOCK_METHOD1(BufferRender,
-               void(const std::vector<std::vector<float>>& block));
+               void(const std::vector<std::vector<std::vector<float>>>& block));
   MOCK_METHOD1(UpdateEchoLeakageStatus, void(bool leakage_detected));
   MOCK_CONST_METHOD1(GetMetrics, void(EchoControl::Metrics* metrics));
   MOCK_METHOD1(SetAudioBufferDelay, void(size_t delay_ms));
diff --git a/modules/audio_processing/aec3/mock/mock_echo_remover.h b/modules/audio_processing/aec3/mock/mock_echo_remover.h
index 5faea26..f8dd348 100644
--- a/modules/audio_processing/aec3/mock/mock_echo_remover.h
+++ b/modules/audio_processing/aec3/mock/mock_echo_remover.h
@@ -32,7 +32,7 @@
                     bool capture_signal_saturation,
                     const absl::optional<DelayEstimate>& delay_estimate,
                     RenderBuffer* render_buffer,
-                    std::vector<std::vector<float>>* capture));
+                    std::vector<std::vector<std::vector<float>>>* capture));
   MOCK_CONST_METHOD0(Delay, absl::optional<int>());
   MOCK_METHOD1(UpdateEchoLeakageStatus, void(bool leakage_detected));
   MOCK_CONST_METHOD1(GetMetrics, void(EchoControl::Metrics* metrics));
diff --git a/modules/audio_processing/aec3/mock/mock_render_delay_buffer.cc b/modules/audio_processing/aec3/mock/mock_render_delay_buffer.cc
index 7526235..de87000 100644
--- a/modules/audio_processing/aec3/mock/mock_render_delay_buffer.cc
+++ b/modules/audio_processing/aec3/mock/mock_render_delay_buffer.cc
@@ -13,9 +13,11 @@
 namespace webrtc {
 namespace test {
 
-MockRenderDelayBuffer::MockRenderDelayBuffer(int sample_rate_hz)
+MockRenderDelayBuffer::MockRenderDelayBuffer(int sample_rate_hz,
+                                             size_t num_channels)
     : block_buffer_(GetRenderDelayBufferSize(4, 4, 12),
                     NumBandsForRate(sample_rate_hz),
+                    num_channels,
                     kBlockSize),
       spectrum_buffer_(block_buffer_.buffer.size(), kFftLengthBy2Plus1),
       fft_buffer_(block_buffer_.buffer.size()),
diff --git a/modules/audio_processing/aec3/mock/mock_render_delay_buffer.h b/modules/audio_processing/aec3/mock/mock_render_delay_buffer.h
index 0dd1b91..1ad0727 100644
--- a/modules/audio_processing/aec3/mock/mock_render_delay_buffer.h
+++ b/modules/audio_processing/aec3/mock/mock_render_delay_buffer.h
@@ -24,13 +24,13 @@
 
 class MockRenderDelayBuffer : public RenderDelayBuffer {
  public:
-  explicit MockRenderDelayBuffer(int sample_rate_hz);
+  MockRenderDelayBuffer(int sample_rate_hz, size_t num_channels);
   virtual ~MockRenderDelayBuffer();
 
   MOCK_METHOD0(Reset, void());
   MOCK_METHOD1(Insert,
                RenderDelayBuffer::BufferingEvent(
-                   const std::vector<std::vector<float>>& block));
+                   const std::vector<std::vector<std::vector<float>>>& block));
   MOCK_METHOD0(PrepareCaptureProcessing, RenderDelayBuffer::BufferingEvent());
   MOCK_METHOD1(AlignFromDelay, bool(size_t delay));
   MOCK_METHOD0(AlignFromExternalDelay, void());
diff --git a/modules/audio_processing/aec3/render_buffer.h b/modules/audio_processing/aec3/render_buffer.h
index 762eab8..8759760 100644
--- a/modules/audio_processing/aec3/render_buffer.h
+++ b/modules/audio_processing/aec3/render_buffer.h
@@ -36,7 +36,8 @@
   ~RenderBuffer();
 
   // Get a block.
-  const std::vector<std::vector<float>>& Block(int buffer_offset_blocks) const {
+  const std::vector<std::vector<std::vector<float>>>& Block(
+      int buffer_offset_blocks) const {
     int position =
         block_buffer_->OffsetIndex(block_buffer_->read, buffer_offset_blocks);
     return block_buffer_->buffer[position];
diff --git a/modules/audio_processing/aec3/render_buffer_unittest.cc b/modules/audio_processing/aec3/render_buffer_unittest.cc
index fadd600..4437178 100644
--- a/modules/audio_processing/aec3/render_buffer_unittest.cc
+++ b/modules/audio_processing/aec3/render_buffer_unittest.cc
@@ -22,7 +22,7 @@
 
 // Verifies the check for non-null fft buffer.
 TEST(RenderBuffer, NullExternalFftBuffer) {
-  MatrixBuffer block_buffer(10, 3, kBlockSize);
+  MatrixBuffer block_buffer(10, 3, 1, kBlockSize);
   VectorBuffer spectrum_buffer(10, kFftLengthBy2Plus1);
   EXPECT_DEATH(RenderBuffer(&block_buffer, &spectrum_buffer, nullptr), "");
 }
@@ -30,7 +30,7 @@
 // Verifies the check for non-null spectrum buffer.
 TEST(RenderBuffer, NullExternalSpectrumBuffer) {
   FftBuffer fft_buffer(10);
-  MatrixBuffer block_buffer(10, 3, kBlockSize);
+  MatrixBuffer block_buffer(10, 3, 1, kBlockSize);
   EXPECT_DEATH(RenderBuffer(&block_buffer, nullptr, &fft_buffer), "");
 }
 
diff --git a/modules/audio_processing/aec3/render_delay_buffer.cc b/modules/audio_processing/aec3/render_delay_buffer.cc
index 11fe450..379f5a1 100644
--- a/modules/audio_processing/aec3/render_delay_buffer.cc
+++ b/modules/audio_processing/aec3/render_delay_buffer.cc
@@ -39,12 +39,15 @@
 
 class RenderDelayBufferImpl final : public RenderDelayBuffer {
  public:
-  RenderDelayBufferImpl(const EchoCanceller3Config& config, int sample_rate_hz);
+  RenderDelayBufferImpl(const EchoCanceller3Config& config,
+                        int sample_rate_hz,
+                        size_t num_render_channels);
   RenderDelayBufferImpl() = delete;
   ~RenderDelayBufferImpl() override;
 
   void Reset() override;
-  BufferingEvent Insert(const std::vector<std::vector<float>>& block) override;
+  BufferingEvent Insert(
+      const std::vector<std::vector<std::vector<float>>>& block) override;
   BufferingEvent PrepareCaptureProcessing() override;
   bool AlignFromDelay(size_t delay) override;
   void AlignFromExternalDelay() override;
@@ -90,12 +93,11 @@
   bool external_audio_buffer_delay_verified_after_reset_ = false;
   size_t min_latency_blocks_ = 0;
   size_t excess_render_detection_counter_ = 0;
-  int sample_rate_hz_;
 
   int MapDelayToTotalDelay(size_t delay) const;
   int ComputeDelay() const;
   void ApplyTotalDelay(int delay);
-  void InsertBlock(const std::vector<std::vector<float>>& block,
+  void InsertBlock(const std::vector<std::vector<std::vector<float>>>& block,
                    int previous_write);
   bool DetectActiveRender(rtc::ArrayView<const float> x) const;
   bool DetectExcessRenderBlocks();
@@ -109,7 +111,8 @@
 int RenderDelayBufferImpl::instance_count_ = 0;
 
 RenderDelayBufferImpl::RenderDelayBufferImpl(const EchoCanceller3Config& config,
-                                             int sample_rate_hz)
+                                             int sample_rate_hz,
+                                             size_t num_render_channels)
     : data_dumper_(
           new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
       optimization_(DetectOptimization()),
@@ -122,6 +125,7 @@
                                        config.delay.num_filters,
                                        config.filter.main.length_blocks),
               NumBandsForRate(sample_rate_hz),
+              num_render_channels,
               kBlockSize),
       spectra_(blocks_.buffer.size(), kFftLengthBy2Plus1),
       ffts_(blocks_.buffer.size()),
@@ -132,9 +136,7 @@
       render_decimator_(down_sampling_factor_),
       fft_(),
       render_ds_(sub_block_size_, 0.f),
-      buffer_headroom_(config.filter.main.length_blocks),
-      sample_rate_hz_(sample_rate_hz) {
-  RTC_DCHECK_GE(sample_rate_hz, 8000);
+      buffer_headroom_(config.filter.main.length_blocks) {
   RTC_DCHECK_EQ(blocks_.buffer.size(), ffts_.buffer.size());
   RTC_DCHECK_EQ(spectra_.buffer.size(), ffts_.buffer.size());
 
@@ -184,7 +186,7 @@
 
 // Inserts a new block into the render buffers.
 RenderDelayBuffer::BufferingEvent RenderDelayBufferImpl::Insert(
-    const std::vector<std::vector<float>>& block) {
+    const std::vector<std::vector<std::vector<float>>>& block) {
   ++render_call_counter_;
   if (delay_) {
     if (!last_call_was_render_) {
@@ -212,7 +214,7 @@
 
   // Detect and update render activity.
   if (!render_activity_) {
-    render_activity_counter_ += DetectActiveRender(block[0]) ? 1 : 0;
+    render_activity_counter_ += DetectActiveRender(block[0][0]) ? 1 : 0;
     render_activity_ = render_activity_counter_ >= 20;
   }
 
@@ -315,8 +317,7 @@
   }
 
   // Convert delay from milliseconds to blocks (rounded down).
-  external_audio_buffer_delay_ =
-      delay_ms >> ((sample_rate_hz_ == 8000) ? 1 : 2);
+  external_audio_buffer_delay_ = delay_ms >> 2;
 }
 
 bool RenderDelayBufferImpl::HasReceivedBufferDelay() {
@@ -359,7 +360,7 @@
 
 // Inserts a block into the render buffers.
 void RenderDelayBufferImpl::InsertBlock(
-    const std::vector<std::vector<float>>& block,
+    const std::vector<std::vector<std::vector<float>>>& block,
     int previous_write) {
   auto& b = blocks_;
   auto& lr = low_rate_;
@@ -372,13 +373,14 @@
     std::copy(block[k].begin(), block[k].end(), b.buffer[b.write][k].begin());
   }
 
-  data_dumper_->DumpWav("aec3_render_decimator_input", block[0].size(),
-                        block[0].data(), 16000, 1);
-  render_decimator_.Decimate(block[0], ds);
+  data_dumper_->DumpWav("aec3_render_decimator_input", block[0][0].size(),
+                        block[0][0].data(), 16000, 1);
+  render_decimator_.Decimate(block[0][0], ds);
   data_dumper_->DumpWav("aec3_render_decimator_output", ds.size(), ds.data(),
                         16000 / down_sampling_factor_, 1);
   std::copy(ds.rbegin(), ds.rend(), lr.buffer.begin() + lr.write);
-  fft_.PaddedFft(block[0], b.buffer[previous_write][0], &f.buffer[f.write]);
+  fft_.PaddedFft(block[0][0], b.buffer[previous_write][0][0],
+                 &f.buffer[f.write]);
   f.buffer[f.write].Spectrum(optimization_, s.buffer[s.write]);
 }
 
@@ -457,8 +459,9 @@
 }  // namespace
 
 RenderDelayBuffer* RenderDelayBuffer::Create(const EchoCanceller3Config& config,
-                                             int sample_rate_hz) {
-  return new RenderDelayBufferImpl(config, sample_rate_hz);
+                                             int sample_rate_hz,
+                                             size_t num_render_channels) {
+  return new RenderDelayBufferImpl(config, sample_rate_hz, num_render_channels);
 }
 
 }  // namespace webrtc
diff --git a/modules/audio_processing/aec3/render_delay_buffer.h b/modules/audio_processing/aec3/render_delay_buffer.h
index 562d2c1..e53f6d2 100644
--- a/modules/audio_processing/aec3/render_delay_buffer.h
+++ b/modules/audio_processing/aec3/render_delay_buffer.h
@@ -33,7 +33,8 @@
   };
 
   static RenderDelayBuffer* Create(const EchoCanceller3Config& config,
-                                   int sample_rate_hz);
+                                   int sample_rate_hz,
+                                   size_t num_render_channels);
   virtual ~RenderDelayBuffer() = default;
 
   // Resets the buffer alignment.
@@ -41,7 +42,7 @@
 
   // Inserts a block into the buffer.
   virtual BufferingEvent Insert(
-      const std::vector<std::vector<float>>& block) = 0;
+      const std::vector<std::vector<std::vector<float>>>& block) = 0;
 
   // Updates the buffers one step based on the specified buffer delay. Returns
   // an enum indicating whether there was a special event that occurred.
diff --git a/modules/audio_processing/aec3/render_delay_buffer_unittest.cc b/modules/audio_processing/aec3/render_delay_buffer_unittest.cc
index 143980c..35e8131 100644
--- a/modules/audio_processing/aec3/render_delay_buffer_unittest.cc
+++ b/modules/audio_processing/aec3/render_delay_buffer_unittest.cc
@@ -35,36 +35,43 @@
 // Verifies that the buffer overflow is correctly reported.
 TEST(RenderDelayBuffer, BufferOverflow) {
   const EchoCanceller3Config config;
-  for (auto rate : {8000, 16000, 32000, 48000}) {
-    SCOPED_TRACE(ProduceDebugText(rate));
-    std::unique_ptr<RenderDelayBuffer> delay_buffer(
-        RenderDelayBuffer::Create(config, rate));
-    std::vector<std::vector<float>> block_to_insert(
-        NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
-    for (size_t k = 0; k < 10; ++k) {
-      EXPECT_EQ(RenderDelayBuffer::BufferingEvent::kNone,
-                delay_buffer->Insert(block_to_insert));
-    }
-    bool overrun_occurred = false;
-    for (size_t k = 0; k < 1000; ++k) {
-      RenderDelayBuffer::BufferingEvent event =
-          delay_buffer->Insert(block_to_insert);
-      overrun_occurred =
-          overrun_occurred ||
-          RenderDelayBuffer::BufferingEvent::kRenderOverrun == event;
-    }
+  for (auto num_channels : {1, 2, 8}) {
+    for (auto rate : {16000, 32000, 48000}) {
+      SCOPED_TRACE(ProduceDebugText(rate));
+      std::unique_ptr<RenderDelayBuffer> delay_buffer(
+          RenderDelayBuffer::Create(config, rate, num_channels));
+      std::vector<std::vector<std::vector<float>>> block_to_insert(
+          NumBandsForRate(rate),
+          std::vector<std::vector<float>>(num_channels,
+                                          std::vector<float>(kBlockSize, 0.f)));
+      for (size_t k = 0; k < 10; ++k) {
+        EXPECT_EQ(RenderDelayBuffer::BufferingEvent::kNone,
+                  delay_buffer->Insert(block_to_insert));
+      }
+      bool overrun_occurred = false;
+      for (size_t k = 0; k < 1000; ++k) {
+        RenderDelayBuffer::BufferingEvent event =
+            delay_buffer->Insert(block_to_insert);
+        overrun_occurred =
+            overrun_occurred ||
+            RenderDelayBuffer::BufferingEvent::kRenderOverrun == event;
+      }
 
-    EXPECT_TRUE(overrun_occurred);
+      EXPECT_TRUE(overrun_occurred);
+    }
   }
 }
 
 // Verifies that the check for available block works.
 TEST(RenderDelayBuffer, AvailableBlock) {
-  constexpr size_t kNumBands = 1;
-  std::unique_ptr<RenderDelayBuffer> delay_buffer(
-      RenderDelayBuffer::Create(EchoCanceller3Config(), 16000));
-  std::vector<std::vector<float>> input_block(
-      kNumBands, std::vector<float>(kBlockSize, 1.f));
+  constexpr size_t kNumChannels = 1;
+  constexpr int kSampleRateHz = 48000;
+  constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz);
+  std::unique_ptr<RenderDelayBuffer> delay_buffer(RenderDelayBuffer::Create(
+      EchoCanceller3Config(), kSampleRateHz, kNumChannels));
+  std::vector<std::vector<std::vector<float>>> input_block(
+      kNumBands, std::vector<std::vector<float>>(
+                     kNumChannels, std::vector<float>(kBlockSize, 1.f)));
   EXPECT_EQ(RenderDelayBuffer::BufferingEvent::kNone,
             delay_buffer->Insert(input_block));
   delay_buffer->PrepareCaptureProcessing();
@@ -74,7 +81,7 @@
 TEST(RenderDelayBuffer, AlignFromDelay) {
   EchoCanceller3Config config;
   std::unique_ptr<RenderDelayBuffer> delay_buffer(
-      RenderDelayBuffer::Create(config, 16000));
+      RenderDelayBuffer::Create(config, 16000, 1));
   ASSERT_TRUE(delay_buffer->Delay());
   delay_buffer->Reset();
   size_t initial_internal_delay = 0;
@@ -92,32 +99,55 @@
 // tests on test bots has been fixed.
 TEST(RenderDelayBuffer, DISABLED_WrongDelay) {
   std::unique_ptr<RenderDelayBuffer> delay_buffer(
-      RenderDelayBuffer::Create(EchoCanceller3Config(), 48000));
+      RenderDelayBuffer::Create(EchoCanceller3Config(), 48000, 1));
   EXPECT_DEATH(delay_buffer->AlignFromDelay(21), "");
 }
 
 // Verifies the check for the number of bands in the inserted blocks.
 TEST(RenderDelayBuffer, WrongNumberOfBands) {
   for (auto rate : {16000, 32000, 48000}) {
-    SCOPED_TRACE(ProduceDebugText(rate));
-    std::unique_ptr<RenderDelayBuffer> delay_buffer(
-        RenderDelayBuffer::Create(EchoCanceller3Config(), rate));
-    std::vector<std::vector<float>> block_to_insert(
-        NumBandsForRate(rate < 48000 ? rate + 16000 : 16000),
-        std::vector<float>(kBlockSize, 0.f));
-    EXPECT_DEATH(delay_buffer->Insert(block_to_insert), "");
+    for (size_t num_channels : {1, 2, 8}) {
+      SCOPED_TRACE(ProduceDebugText(rate));
+      std::unique_ptr<RenderDelayBuffer> delay_buffer(RenderDelayBuffer::Create(
+          EchoCanceller3Config(), rate, num_channels));
+      std::vector<std::vector<std::vector<float>>> block_to_insert(
+          NumBandsForRate(rate < 48000 ? rate + 16000 : 16000),
+          std::vector<std::vector<float>>(num_channels,
+                                          std::vector<float>(kBlockSize, 0.f)));
+      EXPECT_DEATH(delay_buffer->Insert(block_to_insert), "");
+    }
+  }
+}
+
+// Verifies the check for the number of channels in the inserted blocks.
+TEST(RenderDelayBuffer, WrongNumberOfChannels) {
+  for (auto rate : {16000, 32000, 48000}) {
+    for (size_t num_channels : {1, 2, 8}) {
+      SCOPED_TRACE(ProduceDebugText(rate));
+      std::unique_ptr<RenderDelayBuffer> delay_buffer(RenderDelayBuffer::Create(
+          EchoCanceller3Config(), rate, num_channels));
+      std::vector<std::vector<std::vector<float>>> block_to_insert(
+          NumBandsForRate(rate),
+          std::vector<std::vector<float>>(num_channels + 1,
+                                          std::vector<float>(kBlockSize, 0.f)));
+      EXPECT_DEATH(delay_buffer->Insert(block_to_insert), "");
+    }
   }
 }
 
 // Verifies the check of the length of the inserted blocks.
 TEST(RenderDelayBuffer, WrongBlockLength) {
-  for (auto rate : {8000, 16000, 32000, 48000}) {
-    SCOPED_TRACE(ProduceDebugText(rate));
-    std::unique_ptr<RenderDelayBuffer> delay_buffer(
-        RenderDelayBuffer::Create(EchoCanceller3Config(), 48000));
-    std::vector<std::vector<float>> block_to_insert(
-        NumBandsForRate(rate), std::vector<float>(kBlockSize - 1, 0.f));
-    EXPECT_DEATH(delay_buffer->Insert(block_to_insert), "");
+  for (auto rate : {16000, 32000, 48000}) {
+    for (size_t num_channels : {1, 2, 8}) {
+      SCOPED_TRACE(ProduceDebugText(rate));
+      std::unique_ptr<RenderDelayBuffer> delay_buffer(RenderDelayBuffer::Create(
+          EchoCanceller3Config(), rate, num_channels));
+      std::vector<std::vector<std::vector<float>>> block_to_insert(
+          NumBandsForRate(rate),
+          std::vector<std::vector<float>>(
+              num_channels, std::vector<float>(kBlockSize - 1, 0.f)));
+      EXPECT_DEATH(delay_buffer->Insert(block_to_insert), "");
+    }
   }
 }
 
diff --git a/modules/audio_processing/aec3/render_delay_controller_unittest.cc b/modules/audio_processing/aec3/render_delay_controller_unittest.cc
index ff3fb7b..995ecc9 100644
--- a/modules/audio_processing/aec3/render_delay_controller_unittest.cc
+++ b/modules/audio_processing/aec3/render_delay_controller_unittest.cc
@@ -53,10 +53,10 @@
     for (auto down_sampling_factor : kDownSamplingFactors) {
       config.delay.down_sampling_factor = down_sampling_factor;
       config.delay.num_filters = num_matched_filters;
-      for (auto rate : {8000, 16000, 32000, 48000}) {
+      for (auto rate : {16000, 32000, 48000}) {
         SCOPED_TRACE(ProduceDebugText(rate));
         std::unique_ptr<RenderDelayBuffer> delay_buffer(
-            RenderDelayBuffer::Create(config, rate));
+            RenderDelayBuffer::Create(config, rate, 1));
         std::unique_ptr<RenderDelayController> delay_controller(
             RenderDelayController::Create(config, rate));
         for (size_t k = 0; k < 100; ++k) {
@@ -72,6 +72,7 @@
 
 // Verifies the basic API call sequence.
 TEST(RenderDelayController, BasicApiCalls) {
+  constexpr size_t kNumChannels = 1;
   std::vector<float> capture_block(kBlockSize, 0.f);
   absl::optional<DelayEstimate> delay_blocks;
   for (size_t num_matched_filters = 4; num_matched_filters == 10;
@@ -80,11 +81,13 @@
       EchoCanceller3Config config;
       config.delay.down_sampling_factor = down_sampling_factor;
       config.delay.num_filters = num_matched_filters;
-      for (auto rate : {8000, 16000, 32000, 48000}) {
-        std::vector<std::vector<float>> render_block(
-            NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
+      for (auto rate : {16000, 32000, 48000}) {
+        std::vector<std::vector<std::vector<float>>> render_block(
+            NumBandsForRate(rate),
+            std::vector<std::vector<float>>(
+                kNumChannels, std::vector<float>(kBlockSize, 0.f)));
         std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-            RenderDelayBuffer::Create(config, rate));
+            RenderDelayBuffer::Create(config, rate, kNumChannels));
         std::unique_ptr<RenderDelayController> delay_controller(
             RenderDelayController::Create(EchoCanceller3Config(), rate));
         for (size_t k = 0; k < 10; ++k) {
@@ -114,35 +117,45 @@
       config.delay.down_sampling_factor = down_sampling_factor;
       config.delay.num_filters = num_matched_filters;
 
-      for (auto rate : {8000, 16000, 32000, 48000}) {
-        std::vector<std::vector<float>> render_block(
-            NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
+      for (size_t num_render_channels : {1, 2}) {
+        for (auto rate : {16000, 32000, 48000}) {
+          std::vector<std::vector<std::vector<float>>> render_block(
+              NumBandsForRate(rate),
+              std::vector<std::vector<float>>(
+                  num_render_channels, std::vector<float>(kBlockSize, 0.f)));
 
-        for (size_t delay_samples : {15, 50, 150, 200, 800, 4000}) {
-          absl::optional<DelayEstimate> delay_blocks;
-          SCOPED_TRACE(ProduceDebugText(rate, delay_samples));
-          std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-              RenderDelayBuffer::Create(config, rate));
-          std::unique_ptr<RenderDelayController> delay_controller(
-              RenderDelayController::Create(config, rate));
-          DelayBuffer<float> signal_delay_buffer(delay_samples);
-          for (size_t k = 0; k < (400 + delay_samples / kBlockSize); ++k) {
-            RandomizeSampleVector(&random_generator, render_block[0]);
-            signal_delay_buffer.Delay(render_block[0], capture_block);
-            render_delay_buffer->Insert(render_block);
-            render_delay_buffer->PrepareCaptureProcessing();
-            delay_blocks = delay_controller->GetDelay(
-                render_delay_buffer->GetDownsampledRenderBuffer(),
-                render_delay_buffer->Delay(), capture_block);
+          for (size_t delay_samples : {15, 50, 150, 200, 800, 4000}) {
+            absl::optional<DelayEstimate> delay_blocks;
+            SCOPED_TRACE(ProduceDebugText(rate, delay_samples));
+            std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
+                RenderDelayBuffer::Create(config, rate, num_render_channels));
+            std::unique_ptr<RenderDelayController> delay_controller(
+                RenderDelayController::Create(config, rate));
+            DelayBuffer<float> signal_delay_buffer(delay_samples);
+            for (size_t k = 0; k < (400 + delay_samples / kBlockSize); ++k) {
+              for (size_t band = 0; band < render_block.size(); ++band) {
+                for (size_t channel = 0; channel < render_block[band].size();
+                     ++channel) {
+                  RandomizeSampleVector(&random_generator,
+                                        render_block[band][channel]);
+                }
+              }
+              signal_delay_buffer.Delay(render_block[0][0], capture_block);
+              render_delay_buffer->Insert(render_block);
+              render_delay_buffer->PrepareCaptureProcessing();
+              delay_blocks = delay_controller->GetDelay(
+                  render_delay_buffer->GetDownsampledRenderBuffer(),
+                  render_delay_buffer->Delay(), capture_block);
+            }
+            ASSERT_TRUE(!!delay_blocks);
+
+            constexpr int kDelayHeadroomBlocks = 1;
+            size_t expected_delay_blocks =
+                std::max(0, static_cast<int>(delay_samples / kBlockSize) -
+                                kDelayHeadroomBlocks);
+
+            EXPECT_EQ(expected_delay_blocks, delay_blocks->delay);
           }
-          ASSERT_TRUE(!!delay_blocks);
-
-          constexpr int kDelayHeadroomBlocks = 1;
-          size_t expected_delay_blocks =
-              std::max(0, static_cast<int>(delay_samples / kBlockSize) -
-                              kDelayHeadroomBlocks);
-
-          EXPECT_EQ(expected_delay_blocks, delay_blocks->delay);
         }
       }
     }
@@ -153,35 +166,41 @@
 // delays.
 TEST(RenderDelayController, NonCausalAlignment) {
   Random random_generator(42U);
+  constexpr size_t kNumRenderChannels = 1;
+  constexpr size_t kNumCaptureChannels = 1;
   for (size_t num_matched_filters = 4; num_matched_filters == 10;
        num_matched_filters++) {
     for (auto down_sampling_factor : kDownSamplingFactors) {
       EchoCanceller3Config config;
       config.delay.down_sampling_factor = down_sampling_factor;
       config.delay.num_filters = num_matched_filters;
-      for (auto rate : {8000, 16000, 32000, 48000}) {
-        std::vector<std::vector<float>> render_block(
-            NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
-        std::vector<std::vector<float>> capture_block(
-            NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
+      for (auto rate : {16000, 32000, 48000}) {
+        std::vector<std::vector<std::vector<float>>> render_block(
+            NumBandsForRate(rate),
+            std::vector<std::vector<float>>(
+                kNumRenderChannels, std::vector<float>(kBlockSize, 0.f)));
+        std::vector<std::vector<std::vector<float>>> capture_block(
+            NumBandsForRate(rate),
+            std::vector<std::vector<float>>(
+                kNumCaptureChannels, std::vector<float>(kBlockSize, 0.f)));
 
         for (int delay_samples : {-15, -50, -150, -200}) {
           absl::optional<DelayEstimate> delay_blocks;
           SCOPED_TRACE(ProduceDebugText(rate, -delay_samples));
           std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-              RenderDelayBuffer::Create(config, rate));
+              RenderDelayBuffer::Create(config, rate, kNumRenderChannels));
           std::unique_ptr<RenderDelayController> delay_controller(
               RenderDelayController::Create(EchoCanceller3Config(), rate));
           DelayBuffer<float> signal_delay_buffer(-delay_samples);
           for (int k = 0;
                k < (400 - delay_samples / static_cast<int>(kBlockSize)); ++k) {
-            RandomizeSampleVector(&random_generator, capture_block[0]);
-            signal_delay_buffer.Delay(capture_block[0], render_block[0]);
+            RandomizeSampleVector(&random_generator, capture_block[0][0]);
+            signal_delay_buffer.Delay(capture_block[0][0], render_block[0][0]);
             render_delay_buffer->Insert(render_block);
             render_delay_buffer->PrepareCaptureProcessing();
             delay_blocks = delay_controller->GetDelay(
                 render_delay_buffer->GetDownsampledRenderBuffer(),
-                render_delay_buffer->Delay(), capture_block[0]);
+                render_delay_buffer->Delay(), capture_block[0][0]);
           }
 
           ASSERT_FALSE(delay_blocks);
@@ -195,6 +214,7 @@
 // simple timeshifts between the signals when there is jitter in the API calls.
 TEST(RenderDelayController, AlignmentWithJitter) {
   Random random_generator(42U);
+  constexpr size_t kNumRenderChannels = 1;
   std::vector<float> capture_block(kBlockSize, 0.f);
   for (size_t num_matched_filters = 4; num_matched_filters == 10;
        num_matched_filters++) {
@@ -202,14 +222,16 @@
       EchoCanceller3Config config;
       config.delay.down_sampling_factor = down_sampling_factor;
       config.delay.num_filters = num_matched_filters;
-      for (auto rate : {8000, 16000, 32000, 48000}) {
-        std::vector<std::vector<float>> render_block(
-            NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
+      for (auto rate : {16000, 32000, 48000}) {
+        std::vector<std::vector<std::vector<float>>> render_block(
+            NumBandsForRate(rate),
+            std::vector<std::vector<float>>(
+                kNumRenderChannels, std::vector<float>(kBlockSize, 0.f)));
         for (size_t delay_samples : {15, 50, 300, 800}) {
           absl::optional<DelayEstimate> delay_blocks;
           SCOPED_TRACE(ProduceDebugText(rate, delay_samples));
           std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-              RenderDelayBuffer::Create(config, rate));
+              RenderDelayBuffer::Create(config, rate, kNumRenderChannels));
           std::unique_ptr<RenderDelayController> delay_controller(
               RenderDelayController::Create(config, rate));
           DelayBuffer<float> signal_delay_buffer(delay_samples);
@@ -220,8 +242,8 @@
                ++j) {
             std::vector<std::vector<float>> capture_block_buffer;
             for (size_t k = 0; k < (kMaxTestJitterBlocks - 1); ++k) {
-              RandomizeSampleVector(&random_generator, render_block[0]);
-              signal_delay_buffer.Delay(render_block[0], capture_block);
+              RandomizeSampleVector(&random_generator, render_block[0][0]);
+              signal_delay_buffer.Delay(render_block[0][0], capture_block);
               capture_block_buffer.push_back(capture_block);
               render_delay_buffer->Insert(render_block);
             }
@@ -259,10 +281,10 @@
       EchoCanceller3Config config;
       config.delay.down_sampling_factor = down_sampling_factor;
       config.delay.num_filters = num_matched_filters;
-      for (auto rate : {8000, 16000, 32000, 48000}) {
+      for (auto rate : {16000, 32000, 48000}) {
         SCOPED_TRACE(ProduceDebugText(rate));
         std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-            RenderDelayBuffer::Create(config, rate));
+            RenderDelayBuffer::Create(config, rate, 1));
 
         std::unique_ptr<RenderDelayController> delay_controller(
             RenderDelayController::Create(config, rate));
@@ -277,10 +299,10 @@
 TEST(RenderDelayController, WrongCaptureSize) {
   std::vector<float> block(kBlockSize - 1, 0.f);
   EchoCanceller3Config config;
-  for (auto rate : {8000, 16000, 32000, 48000}) {
+  for (auto rate : {16000, 32000, 48000}) {
     SCOPED_TRACE(ProduceDebugText(rate));
     std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-        RenderDelayBuffer::Create(config, rate));
+        RenderDelayBuffer::Create(config, rate, 1));
     EXPECT_DEATH(
         std::unique_ptr<RenderDelayController>(
             RenderDelayController::Create(EchoCanceller3Config(), rate))
@@ -298,7 +320,7 @@
     SCOPED_TRACE(ProduceDebugText(rate));
     EchoCanceller3Config config;
     std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-        RenderDelayBuffer::Create(config, rate));
+        RenderDelayBuffer::Create(config, rate, 1));
     EXPECT_DEATH(
         std::unique_ptr<RenderDelayController>(
             RenderDelayController::Create(EchoCanceller3Config(), rate)),
diff --git a/modules/audio_processing/aec3/render_signal_analyzer.cc b/modules/audio_processing/aec3/render_signal_analyzer.cc
index e3e41a7..88bacaf 100644
--- a/modules/audio_processing/aec3/render_signal_analyzer.cc
+++ b/modules/audio_processing/aec3/render_signal_analyzer.cc
@@ -66,13 +66,15 @@
   }
 
   // Assess the render signal strength.
-  const std::vector<std::vector<float>>& x_latest = render_buffer.Block(0);
-  auto result0 = std::minmax_element(x_latest[0].begin(), x_latest[0].end());
+  const std::vector<std::vector<std::vector<float>>>& x_latest =
+      render_buffer.Block(0);
+  auto result0 =
+      std::minmax_element(x_latest[0][0].begin(), x_latest[0][0].end());
   float max_abs = std::max(fabs(*result0.first), fabs(*result0.second));
 
   if (x_latest.size() > 1) {
     const auto result1 =
-        std::minmax_element(x_latest[1].begin(), x_latest[1].end());
+        std::minmax_element(x_latest[1][0].begin(), x_latest[1][0].end());
     max_abs =
         std::max(max_abs, static_cast<float>(std::max(fabs(*result1.first),
                                                       fabs(*result1.second))));
diff --git a/modules/audio_processing/aec3/render_signal_analyzer_unittest.cc b/modules/audio_processing/aec3/render_signal_analyzer_unittest.cc
index 53a41b1..27a31f0 100644
--- a/modules/audio_processing/aec3/render_signal_analyzer_unittest.cc
+++ b/modules/audio_processing/aec3/render_signal_analyzer_unittest.cc
@@ -33,14 +33,23 @@
 void ProduceSinusoid(int sample_rate_hz,
                      float sinusoidal_frequency_hz,
                      size_t* sample_counter,
-                     rtc::ArrayView<float> x) {
+                     std::vector<std::vector<std::vector<float>>>* x) {
   // Produce a sinusoid of the specified frequency.
   for (size_t k = *sample_counter, j = 0; k < (*sample_counter + kBlockSize);
        ++k, ++j) {
-    x[j] = 32767.f *
-           std::sin(2.f * kPi * sinusoidal_frequency_hz * k / sample_rate_hz);
+    for (size_t channel = 0; channel < (*x)[0].size(); ++channel) {
+      (*x)[0][channel][j] =
+          32767.f *
+          std::sin(2.f * kPi * sinusoidal_frequency_hz * k / sample_rate_hz);
+    }
   }
   *sample_counter = *sample_counter + kBlockSize;
+
+  for (size_t band = 1; band < x->size(); ++band) {
+    for (size_t channel = 0; channel < (*x)[band].size(); ++channel) {
+      std::fill((*x)[band][channel].begin(), (*x)[band][channel].end(), 0.f);
+    }
+  }
 }
 
 }  // namespace
@@ -58,15 +67,17 @@
 TEST(RenderSignalAnalyzer, NoFalseDetectionOfNarrowBands) {
   RenderSignalAnalyzer analyzer(EchoCanceller3Config{});
   Random random_generator(42U);
-  std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
+  std::vector<std::vector<std::vector<float>>> x(
+      3,
+      std::vector<std::vector<float>>(1, std::vector<float>(kBlockSize, 0.f)));
   std::array<float, kBlockSize> x_old;
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(EchoCanceller3Config(), 48000));
+      RenderDelayBuffer::Create(EchoCanceller3Config(), 48000, 1));
   std::array<float, kFftLengthBy2Plus1> mask;
   x_old.fill(0.f);
 
   for (size_t k = 0; k < 100; ++k) {
-    RandomizeSampleVector(&random_generator, x[0]);
+    RandomizeSampleVector(&random_generator, x[0][0]);
 
     render_delay_buffer->Insert(x);
     if (k == 0) {
@@ -89,12 +100,17 @@
 TEST(RenderSignalAnalyzer, NarrowBandDetection) {
   RenderSignalAnalyzer analyzer(EchoCanceller3Config{});
   Random random_generator(42U);
-  std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
+  constexpr size_t kNumChannels = 1;
+  constexpr int kSampleRateHz = 48000;
+  constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz);
+  std::vector<std::vector<std::vector<float>>> x(
+      kNumBands, std::vector<std::vector<float>>(
+                     kNumChannels, std::vector<float>(kBlockSize, 0.f)));
   std::array<float, kBlockSize> x_old;
   Aec3Fft fft;
   EchoCanceller3Config config;
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(config, 48000));
+      RenderDelayBuffer::Create(config, kSampleRateHz, kNumChannels));
 
   std::array<float, kFftLengthBy2Plus1> mask;
   x_old.fill(0.f);
@@ -104,7 +120,7 @@
     size_t sample_counter = 0;
     for (size_t k = 0; k < 100; ++k) {
       ProduceSinusoid(16000, 16000 / 2 * kSinusFrequencyBin / kFftLengthBy2,
-                      &sample_counter, x[0]);
+                      &sample_counter, &x);
 
       render_delay_buffer->Insert(x);
       if (k == 0) {
diff --git a/modules/audio_processing/aec3/residual_echo_estimator_unittest.cc b/modules/audio_processing/aec3/residual_echo_estimator_unittest.cc
index d277d42..863f8f8 100644
--- a/modules/audio_processing/aec3/residual_echo_estimator_unittest.cc
+++ b/modules/audio_processing/aec3/residual_echo_estimator_unittest.cc
@@ -27,7 +27,7 @@
   EchoCanceller3Config config;
   AecState aec_state(config);
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(config, 48000));
+      RenderDelayBuffer::Create(config, 48000, 1));
   std::vector<std::array<float, kFftLengthBy2Plus1>> H2;
   std::array<float, kFftLengthBy2Plus1> S2_linear;
   std::array<float, kFftLengthBy2Plus1> Y2;
@@ -42,12 +42,16 @@
 // TODO(peah): This test is broken in the sense that it not at all tests what it
 // seems to test. Enable the test once that is adressed.
 TEST(ResidualEchoEstimator, DISABLED_BasicTest) {
+  constexpr size_t kNumChannels = 1;
+  constexpr int kSampleRateHz = 48000;
+  constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz);
+
   EchoCanceller3Config config;
   config.ep_strength.default_len = 0.f;
   ResidualEchoEstimator estimator(config);
   AecState aec_state(config);
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(config, 48000));
+      RenderDelayBuffer::Create(config, kSampleRateHz, kNumChannels));
 
   std::array<float, kFftLengthBy2Plus1> E2_main;
   std::array<float, kFftLengthBy2Plus1> E2_shadow;
@@ -57,7 +61,9 @@
   std::array<float, kFftLengthBy2Plus1> R2;
   EchoPathVariability echo_path_variability(
       false, EchoPathVariability::DelayAdjustment::kNone, false);
-  std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
+  std::vector<std::vector<std::vector<float>>> x(
+      kNumBands, std::vector<std::vector<float>>(
+                     kNumChannels, std::vector<float>(kBlockSize, 0.f)));
   std::vector<std::array<float, kFftLengthBy2Plus1>> H2(10);
   Random random_generator(42U);
   SubtractorOutput output;
@@ -86,8 +92,8 @@
   Y2.fill(kLevel);
 
   for (int k = 0; k < 1993; ++k) {
-    RandomizeSampleVector(&random_generator, x[0]);
-    std::for_each(x[0].begin(), x[0].end(), [](float& a) { a /= 30.f; });
+    RandomizeSampleVector(&random_generator, x[0][0]);
+    std::for_each(x[0][0].begin(), x[0][0].end(), [](float& a) { a /= 30.f; });
     render_delay_buffer->Insert(x);
     if (k == 0) {
       render_delay_buffer->Reset();
diff --git a/modules/audio_processing/aec3/shadow_filter_update_gain_unittest.cc b/modules/audio_processing/aec3/shadow_filter_update_gain_unittest.cc
index 7372e5e..b49b00d 100644
--- a/modules/audio_processing/aec3/shadow_filter_update_gain_unittest.cc
+++ b/modules/audio_processing/aec3/shadow_filter_update_gain_unittest.cc
@@ -32,6 +32,7 @@
 // gain functionality.
 void RunFilterUpdateTest(int num_blocks_to_process,
                          size_t delay_samples,
+                         size_t num_render_channels,
                          int filter_length_blocks,
                          const std::vector<int>& blocks_with_saturation,
                          std::array<float, kBlockSize>* e_last_block,
@@ -50,17 +51,19 @@
                                   DetectOptimization(), &data_dumper);
   Aec3Fft fft;
 
+  constexpr int kSampleRateHz = 48000;
   config.delay.default_delay = 1;
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(config, 48000));
+      RenderDelayBuffer::Create(config, kSampleRateHz, num_render_channels));
 
-  std::array<float, kBlockSize> x_old;
-  x_old.fill(0.f);
   ShadowFilterUpdateGain shadow_gain(
       config.filter.shadow, config.filter.config_change_duration_blocks);
   Random random_generator(42U);
-  std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
-  std::vector<float> y(kBlockSize, 0.f);
+  std::vector<std::vector<std::vector<float>>> x(
+      NumBandsForRate(kSampleRateHz),
+      std::vector<std::vector<float>>(num_render_channels,
+                                      std::vector<float>(kBlockSize, 0.f)));
+  std::array<float, kBlockSize> y;
   AecState aec_state(config);
   RenderSignalAnalyzer render_signal_analyzer(config);
   std::array<float, kFftLength> s;
@@ -79,8 +82,12 @@
                   k) != blocks_with_saturation.end();
 
     // Create the render signal.
-    RandomizeSampleVector(&random_generator, x[0]);
-    delay_buffer.Delay(x[0], y);
+    for (size_t band = 0; band < x.size(); ++band) {
+      for (size_t channel = 0; channel < x[band].size(); ++channel) {
+        RandomizeSampleVector(&random_generator, x[band][channel]);
+      }
+    }
+    delay_buffer.Delay(x[0][0], y);
 
     render_delay_buffer->Insert(x);
     if (k == 0) {
@@ -151,25 +158,30 @@
 TEST(ShadowFilterUpdateGain, GainCausesFilterToConverge) {
   std::vector<int> blocks_with_echo_path_changes;
   std::vector<int> blocks_with_saturation;
-  for (size_t filter_length_blocks : {12, 20, 30}) {
-    for (size_t delay_samples : {0, 64, 150, 200, 301}) {
-      SCOPED_TRACE(ProduceDebugText(delay_samples, filter_length_blocks));
 
-      std::array<float, kBlockSize> e;
-      std::array<float, kBlockSize> y;
-      FftData G;
+  for (size_t num_render_channels : {1, 2, 8}) {
+    for (size_t filter_length_blocks : {12, 20, 30}) {
+      for (size_t delay_samples : {0, 64, 150, 200, 301}) {
+        SCOPED_TRACE(ProduceDebugText(delay_samples, filter_length_blocks));
 
-      RunFilterUpdateTest(1000, delay_samples, filter_length_blocks,
-                          blocks_with_saturation, &e, &y, &G);
+        std::array<float, kBlockSize> e;
+        std::array<float, kBlockSize> y;
+        FftData G;
 
-      // Verify that the main filter is able to perform well.
-      // Use different criteria to take overmodelling into account.
-      if (filter_length_blocks == 12) {
-        EXPECT_LT(1000 * std::inner_product(e.begin(), e.end(), e.begin(), 0.f),
-                  std::inner_product(y.begin(), y.end(), y.begin(), 0.f));
-      } else {
-        EXPECT_LT(std::inner_product(e.begin(), e.end(), e.begin(), 0.f),
-                  std::inner_product(y.begin(), y.end(), y.begin(), 0.f));
+        RunFilterUpdateTest(1000, delay_samples, num_render_channels,
+                            filter_length_blocks, blocks_with_saturation, &e,
+                            &y, &G);
+
+        // Verify that the main filter is able to perform well.
+        // Use different criteria to take overmodelling into account.
+        if (filter_length_blocks == 12) {
+          EXPECT_LT(
+              1000 * std::inner_product(e.begin(), e.end(), e.begin(), 0.f),
+              std::inner_product(y.begin(), y.end(), y.begin(), 0.f));
+        } else {
+          EXPECT_LT(std::inner_product(e.begin(), e.end(), e.begin(), 0.f),
+                    std::inner_product(y.begin(), y.end(), y.begin(), 0.f));
+        }
       }
     }
   }
@@ -178,36 +190,38 @@
 // Verifies that the magnitude of the gain on average decreases for a
 // persistently exciting signal.
 TEST(ShadowFilterUpdateGain, DecreasingGain) {
-  for (size_t filter_length_blocks : {12, 20, 30}) {
-    SCOPED_TRACE(ProduceDebugText(filter_length_blocks));
-    std::vector<int> blocks_with_echo_path_changes;
-    std::vector<int> blocks_with_saturation;
+  for (size_t num_render_channels : {1, 2, 8}) {
+    for (size_t filter_length_blocks : {12, 20, 30}) {
+      SCOPED_TRACE(ProduceDebugText(filter_length_blocks));
+      std::vector<int> blocks_with_echo_path_changes;
+      std::vector<int> blocks_with_saturation;
 
-    std::array<float, kBlockSize> e;
-    std::array<float, kBlockSize> y;
-    FftData G_a;
-    FftData G_b;
-    FftData G_c;
-    std::array<float, kFftLengthBy2Plus1> G_a_power;
-    std::array<float, kFftLengthBy2Plus1> G_b_power;
-    std::array<float, kFftLengthBy2Plus1> G_c_power;
+      std::array<float, kBlockSize> e;
+      std::array<float, kBlockSize> y;
+      FftData G_a;
+      FftData G_b;
+      FftData G_c;
+      std::array<float, kFftLengthBy2Plus1> G_a_power;
+      std::array<float, kFftLengthBy2Plus1> G_b_power;
+      std::array<float, kFftLengthBy2Plus1> G_c_power;
 
-    RunFilterUpdateTest(100, 65, filter_length_blocks, blocks_with_saturation,
-                        &e, &y, &G_a);
-    RunFilterUpdateTest(200, 65, filter_length_blocks, blocks_with_saturation,
-                        &e, &y, &G_b);
-    RunFilterUpdateTest(300, 65, filter_length_blocks, blocks_with_saturation,
-                        &e, &y, &G_c);
+      RunFilterUpdateTest(100, 65, num_render_channels, filter_length_blocks,
+                          blocks_with_saturation, &e, &y, &G_a);
+      RunFilterUpdateTest(200, 65, num_render_channels, filter_length_blocks,
+                          blocks_with_saturation, &e, &y, &G_b);
+      RunFilterUpdateTest(300, 65, num_render_channels, filter_length_blocks,
+                          blocks_with_saturation, &e, &y, &G_c);
 
-    G_a.Spectrum(Aec3Optimization::kNone, G_a_power);
-    G_b.Spectrum(Aec3Optimization::kNone, G_b_power);
-    G_c.Spectrum(Aec3Optimization::kNone, G_c_power);
+      G_a.Spectrum(Aec3Optimization::kNone, G_a_power);
+      G_b.Spectrum(Aec3Optimization::kNone, G_b_power);
+      G_c.Spectrum(Aec3Optimization::kNone, G_c_power);
 
-    EXPECT_GT(std::accumulate(G_a_power.begin(), G_a_power.end(), 0.),
-              std::accumulate(G_b_power.begin(), G_b_power.end(), 0.));
+      EXPECT_GT(std::accumulate(G_a_power.begin(), G_a_power.end(), 0.),
+                std::accumulate(G_b_power.begin(), G_b_power.end(), 0.));
 
-    EXPECT_GT(std::accumulate(G_b_power.begin(), G_b_power.end(), 0.),
-              std::accumulate(G_c_power.begin(), G_c_power.end(), 0.));
+      EXPECT_GT(std::accumulate(G_b_power.begin(), G_b_power.end(), 0.),
+                std::accumulate(G_c_power.begin(), G_c_power.end(), 0.));
+    }
   }
 }
 
@@ -218,21 +232,23 @@
   for (int k = 99; k < 200; ++k) {
     blocks_with_saturation.push_back(k);
   }
-  for (size_t filter_length_blocks : {12, 20, 30}) {
-    SCOPED_TRACE(ProduceDebugText(filter_length_blocks));
+  for (size_t num_render_channels : {1, 2, 8}) {
+    for (size_t filter_length_blocks : {12, 20, 30}) {
+      SCOPED_TRACE(ProduceDebugText(filter_length_blocks));
 
-    std::array<float, kBlockSize> e;
-    std::array<float, kBlockSize> y;
-    FftData G_a;
-    FftData G_a_ref;
-    G_a_ref.re.fill(0.f);
-    G_a_ref.im.fill(0.f);
+      std::array<float, kBlockSize> e;
+      std::array<float, kBlockSize> y;
+      FftData G_a;
+      FftData G_a_ref;
+      G_a_ref.re.fill(0.f);
+      G_a_ref.im.fill(0.f);
 
-    RunFilterUpdateTest(100, 65, filter_length_blocks, blocks_with_saturation,
-                        &e, &y, &G_a);
+      RunFilterUpdateTest(100, 65, num_render_channels, filter_length_blocks,
+                          blocks_with_saturation, &e, &y, &G_a);
 
-    EXPECT_EQ(G_a_ref.re, G_a.re);
-    EXPECT_EQ(G_a_ref.im, G_a.im);
+      EXPECT_EQ(G_a_ref.re, G_a.re);
+      EXPECT_EQ(G_a_ref.im, G_a.im);
+    }
   }
 }
 
diff --git a/modules/audio_processing/aec3/signal_dependent_erle_estimator_unittest.cc b/modules/audio_processing/aec3/signal_dependent_erle_estimator_unittest.cc
index f27c905..b8c83f7 100644
--- a/modules/audio_processing/aec3/signal_dependent_erle_estimator_unittest.cc
+++ b/modules/audio_processing/aec3/signal_dependent_erle_estimator_unittest.cc
@@ -24,7 +24,7 @@
 
 namespace {
 
-void GetActiveFrame(rtc::ArrayView<float> x) {
+void GetActiveFrame(std::vector<std::vector<std::vector<float>>>* x) {
   const std::array<float, kBlockSize> frame = {
       7459.88, 17209.6, 17383,   20768.9, 16816.7, 18386.3, 4492.83, 9675.85,
       6665.52, 14808.6, 9342.3,  7483.28, 19261.7, 4145.98, 1622.18, 13475.2,
@@ -34,8 +34,12 @@
       11405,   15031.4, 14541.6, 19765.5, 18346.3, 19350.2, 3157.47, 18095.8,
       1743.68, 21328.2, 19727.5, 7295.16, 10332.4, 11055.5, 20107.4, 14708.4,
       12416.2, 16434,   2454.69, 9840.8,  6867.23, 1615.75, 6059.9,  8394.19};
-  RTC_DCHECK_GE(x.size(), frame.size());
-  std::copy(frame.begin(), frame.end(), x.begin());
+  for (size_t band = 0; band < x->size(); ++band) {
+    for (size_t channel = 0; channel < (*x)[band].size(); ++channel) {
+      RTC_DCHECK_GE((*x)[band][channel].size(), frame.size());
+      std::copy(frame.begin(), frame.end(), (*x)[band][channel].begin());
+    }
+  }
 }
 
 class TestInputs {
@@ -58,13 +62,15 @@
   std::array<float, kFftLengthBy2Plus1> Y2_;
   std::array<float, kFftLengthBy2Plus1> E2_;
   std::vector<std::array<float, kFftLengthBy2Plus1>> H2_;
-  std::vector<std::vector<float>> x_;
+  std::vector<std::vector<std::vector<float>>> x_;
 };
 
 TestInputs::TestInputs(const EchoCanceller3Config& cfg)
-    : render_delay_buffer_(RenderDelayBuffer::Create(cfg, 16000)),
+    : render_delay_buffer_(RenderDelayBuffer::Create(cfg, 16000, 1)),
       H2_(cfg.filter.main.length_blocks),
-      x_(1, std::vector<float>(kBlockSize, 0.f)) {
+      x_(1,
+         std::vector<std::vector<float>>(1,
+                                         std::vector<float>(kBlockSize, 0.f))) {
   render_delay_buffer_->AlignFromDelay(4);
   render_buffer_ = render_delay_buffer_->GetRenderBuffer();
   for (auto& H : H2_) {
@@ -77,9 +83,9 @@
 
 void TestInputs::Update() {
   if (n_ % 2 == 0) {
-    std::fill(x_[0].begin(), x_[0].end(), 0.f);
+    std::fill(x_[0][0].begin(), x_[0][0].end(), 0.f);
   } else {
-    GetActiveFrame(x_[0]);
+    GetActiveFrame(&x_);
   }
 
   render_delay_buffer_->Insert(x_);
diff --git a/modules/audio_processing/aec3/subtractor_unittest.cc b/modules/audio_processing/aec3/subtractor_unittest.cc
index bcf3b27..f29b446 100644
--- a/modules/audio_processing/aec3/subtractor_unittest.cc
+++ b/modules/audio_processing/aec3/subtractor_unittest.cc
@@ -31,19 +31,24 @@
                         bool uncorrelated_inputs,
                         const std::vector<int>& blocks_with_echo_path_changes) {
   ApmDataDumper data_dumper(42);
+  constexpr size_t kNumChannels = 1;
+  constexpr int kSampleRateHz = 48000;
+  constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz);
   EchoCanceller3Config config;
   config.filter.main.length_blocks = main_filter_length_blocks;
   config.filter.shadow.length_blocks = shadow_filter_length_blocks;
 
   Subtractor subtractor(config, &data_dumper, DetectOptimization());
   absl::optional<DelayEstimate> delay_estimate;
-  std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
+  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::array<float, kBlockSize> x_old;
   SubtractorOutput output;
   config.delay.default_delay = 1;
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(config, 48000));
+      RenderDelayBuffer::Create(config, kSampleRateHz, kNumChannels));
   RenderSignalAnalyzer render_signal_analyzer(config);
   Random random_generator(42U);
   Aec3Fft fft;
@@ -58,11 +63,11 @@
 
   DelayBuffer<float> delay_buffer(delay_samples);
   for (int k = 0; k < num_blocks_to_process; ++k) {
-    RandomizeSampleVector(&random_generator, x[0]);
+    RandomizeSampleVector(&random_generator, x[0][0]);
     if (uncorrelated_inputs) {
       RandomizeSampleVector(&random_generator, y);
     } else {
-      delay_buffer.Delay(x[0], y);
+      delay_buffer.Delay(x[0][0], y);
     }
     render_delay_buffer->Insert(x);
     if (k == 0) {
@@ -126,7 +131,7 @@
   EchoCanceller3Config config;
   Subtractor subtractor(config, &data_dumper, DetectOptimization());
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(config, 48000));
+      RenderDelayBuffer::Create(config, 48000, 1));
   RenderSignalAnalyzer render_signal_analyzer(config);
   std::vector<float> y(kBlockSize, 0.f);
 
@@ -142,7 +147,7 @@
   EchoCanceller3Config config;
   Subtractor subtractor(config, &data_dumper, DetectOptimization());
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(config, 48000));
+      RenderDelayBuffer::Create(config, 48000, 1));
   RenderSignalAnalyzer render_signal_analyzer(config);
   std::vector<float> y(kBlockSize - 1, 0.f);
   SubtractorOutput output;
diff --git a/modules/audio_processing/aec3/suppression_filter.cc b/modules/audio_processing/aec3/suppression_filter.cc
index 6fe296c..6679a87 100644
--- a/modules/audio_processing/aec3/suppression_filter.cc
+++ b/modules/audio_processing/aec3/suppression_filter.cc
@@ -79,7 +79,7 @@
     const std::array<float, kFftLengthBy2Plus1>& suppression_gain,
     float high_bands_gain,
     const FftData& E_lowest_band,
-    std::vector<std::vector<float>>* e) {
+    std::vector<std::vector<std::vector<float>>>* e) {
   RTC_DCHECK(e);
   RTC_DCHECK_EQ(e->size(), NumBandsForRate(sample_rate_hz_));
   FftData E;
@@ -111,14 +111,14 @@
 
   fft_.Ifft(E, &e_extended);
   std::transform(e_output_old_[0].begin(), e_output_old_[0].end(),
-                 std::begin(kSqrtHanning) + kFftLengthBy2, (*e)[0].begin(),
+                 std::begin(kSqrtHanning) + kFftLengthBy2, (*e)[0][0].begin(),
                  [&](float a, float b) { return kIfftNormalization * a * b; });
   std::transform(e_extended.begin(), e_extended.begin() + kFftLengthBy2,
                  std::begin(kSqrtHanning), e_extended.begin(),
                  [&](float a, float b) { return kIfftNormalization * a * b; });
-  std::transform((*e)[0].begin(), (*e)[0].end(), e_extended.begin(),
-                 (*e)[0].begin(), std::plus<float>());
-  std::for_each((*e)[0].begin(), (*e)[0].end(), [](float& x_k) {
+  std::transform((*e)[0][0].begin(), (*e)[0][0].end(), e_extended.begin(),
+                 (*e)[0][0].begin(), std::plus<float>());
+  std::for_each((*e)[0][0].begin(), (*e)[0][0].end(), [](float& x_k) {
     x_k = rtc::SafeClamp(x_k, -32768.f, 32767.f);
   });
   std::copy(e_extended.begin() + kFftLengthBy2, e_extended.begin() + kFftLength,
@@ -140,8 +140,9 @@
         0.4f * std::sqrt(1.f - high_bands_gain * high_bands_gain);
 
     std::transform(
-        (*e)[1].begin(), (*e)[1].end(), time_domain_high_band_noise.begin(),
-        (*e)[1].begin(), [&](float a, float b) {
+        (*e)[1][0].begin(), (*e)[1][0].end(),
+        time_domain_high_band_noise.begin(), (*e)[1][0].begin(),
+        [&](float a, float b) {
           return std::max(
               std::min(b * high_bands_noise_scaling + high_bands_gain * a,
                        32767.0f),
@@ -150,16 +151,16 @@
 
     if (e->size() > 2) {
       RTC_DCHECK_EQ(3, e->size());
-      std::for_each((*e)[2].begin(), (*e)[2].end(), [&](float& a) {
+      std::for_each((*e)[2][0].begin(), (*e)[2][0].end(), [&](float& a) {
         a = rtc::SafeClamp(a * high_bands_gain, -32768.f, 32767.f);
       });
     }
 
     std::array<float, kFftLengthBy2> tmp;
     for (size_t k = 1; k < e->size(); ++k) {
-      std::copy((*e)[k].begin(), (*e)[k].end(), tmp.begin());
+      std::copy((*e)[k][0].begin(), (*e)[k][0].end(), tmp.begin());
       std::copy(e_output_old_[k].begin(), e_output_old_[k].end(),
-                (*e)[k].begin());
+                (*e)[k][0].begin());
       std::copy(tmp.begin(), tmp.end(), e_output_old_[k].begin());
     }
   }
diff --git a/modules/audio_processing/aec3/suppression_filter.h b/modules/audio_processing/aec3/suppression_filter.h
index 63569b1..03b13c8 100644
--- a/modules/audio_processing/aec3/suppression_filter.h
+++ b/modules/audio_processing/aec3/suppression_filter.h
@@ -31,7 +31,7 @@
                  const std::array<float, kFftLengthBy2Plus1>& suppression_gain,
                  float high_bands_gain,
                  const FftData& E_lowest_band,
-                 std::vector<std::vector<float>>* e);
+                 std::vector<std::vector<std::vector<float>>>* e);
 
  private:
   const Aec3Optimization optimization_;
diff --git a/modules/audio_processing/aec3/suppression_filter_unittest.cc b/modules/audio_processing/aec3/suppression_filter_unittest.cc
index 80d96ec..1e05a02 100644
--- a/modules/audio_processing/aec3/suppression_filter_unittest.cc
+++ b/modules/audio_processing/aec3/suppression_filter_unittest.cc
@@ -26,14 +26,23 @@
 void ProduceSinusoid(int sample_rate_hz,
                      float sinusoidal_frequency_hz,
                      size_t* sample_counter,
-                     rtc::ArrayView<float> x) {
+                     std::vector<std::vector<std::vector<float>>>* x) {
   // Produce a sinusoid of the specified frequency.
   for (size_t k = *sample_counter, j = 0; k < (*sample_counter + kBlockSize);
        ++k, ++j) {
-    x[j] = 32767.f *
-           std::sin(2.f * kPi * sinusoidal_frequency_hz * k / sample_rate_hz);
+    for (size_t channel = 0; channel < (*x)[0].size(); ++channel) {
+      (*x)[0][channel][j] =
+          32767.f *
+          std::sin(2.f * kPi * sinusoidal_frequency_hz * k / sample_rate_hz);
+    }
   }
   *sample_counter = *sample_counter + kBlockSize;
+
+  for (size_t band = 1; band < x->size(); ++band) {
+    for (size_t channel = 0; channel < (*x)[band].size(); ++channel) {
+      std::fill((*x)[band][channel].begin(), (*x)[band][channel].end(), 0.f);
+    }
+  }
 }
 
 }  // namespace
@@ -75,29 +84,41 @@
   cn_high_bands.re.fill(1.f);
   cn_high_bands.im.fill(1.f);
 
-  std::vector<std::vector<float>> e(3, std::vector<float>(kBlockSize, 0.f));
-  std::vector<std::vector<float>> e_ref = e;
+  std::vector<std::vector<std::vector<float>>> e(
+      3,
+      std::vector<std::vector<float>>(1, std::vector<float>(kBlockSize, 0.f)));
+  std::vector<std::vector<std::vector<float>>> e_ref = e;
 
   FftData E;
-  fft.PaddedFft(e[0], e_old_, Aec3Fft::Window::kSqrtHanning, &E);
-  std::copy(e[0].begin(), e[0].end(), e_old_.begin());
+  fft.PaddedFft(e[0][0], e_old_, Aec3Fft::Window::kSqrtHanning, &E);
+  std::copy(e[0][0].begin(), e[0][0].end(), e_old_.begin());
 
   filter.ApplyGain(cn, cn_high_bands, gain, 1.f, E, &e);
 
-  for (size_t k = 0; k < e.size(); ++k) {
-    EXPECT_EQ(e_ref[k], e[k]);
+  for (size_t band = 0; band < e.size(); ++band) {
+    for (size_t channel = 0; channel < e[band].size(); ++channel) {
+      for (size_t sample = 0; sample < e[band][channel].size(); ++sample) {
+        EXPECT_EQ(e_ref[band][channel][sample], e[band][channel][sample]);
+      }
+    }
   }
 }
 
 // Verifies that the suppressor is able to suppress a signal.
 TEST(SuppressionFilter, SignalSuppression) {
-  SuppressionFilter filter(Aec3Optimization::kNone, 48000);
+  constexpr int kSampleRateHz = 48000;
+  constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz);
+  constexpr size_t kNumChannels = 1;
+
+  SuppressionFilter filter(Aec3Optimization::kNone, kSampleRateHz);
   FftData cn;
   FftData cn_high_bands;
   std::array<float, kFftLengthBy2> e_old_;
   Aec3Fft fft;
   std::array<float, kFftLengthBy2Plus1> gain;
-  std::vector<std::vector<float>> e(3, std::vector<float>(kBlockSize, 0.f));
+  std::vector<std::vector<std::vector<float>>> e(
+      kNumBands, std::vector<std::vector<float>>(
+                     kNumChannels, std::vector<float>(kBlockSize, 0.f)));
   e_old_.fill(0.f);
 
   gain.fill(1.f);
@@ -113,18 +134,17 @@
   float e0_input = 0.f;
   float e0_output = 0.f;
   for (size_t k = 0; k < 100; ++k) {
-    ProduceSinusoid(16000, 16000 * 40 / kFftLengthBy2 / 2, &sample_counter,
-                    e[0]);
-    e0_input =
-        std::inner_product(e[0].begin(), e[0].end(), e[0].begin(), e0_input);
+    ProduceSinusoid(16000, 16000 * 40 / kFftLengthBy2 / 2, &sample_counter, &e);
+    e0_input = std::inner_product(e[0][0].begin(), e[0][0].end(),
+                                  e[0][0].begin(), e0_input);
 
     FftData E;
-    fft.PaddedFft(e[0], e_old_, Aec3Fft::Window::kSqrtHanning, &E);
-    std::copy(e[0].begin(), e[0].end(), e_old_.begin());
+    fft.PaddedFft(e[0][0], e_old_, Aec3Fft::Window::kSqrtHanning, &E);
+    std::copy(e[0][0].begin(), e[0][0].end(), e_old_.begin());
 
     filter.ApplyGain(cn, cn_high_bands, gain, 1.f, E, &e);
-    e0_output =
-        std::inner_product(e[0].begin(), e[0].end(), e[0].begin(), e0_output);
+    e0_output = std::inner_product(e[0][0].begin(), e[0][0].end(),
+                                   e[0][0].begin(), e0_output);
   }
 
   EXPECT_LT(e0_output, e0_input / 1000.f);
@@ -133,13 +153,19 @@
 // Verifies that the suppressor is able to pass through a desired signal while
 // applying suppressing for some frequencies.
 TEST(SuppressionFilter, SignalTransparency) {
-  SuppressionFilter filter(Aec3Optimization::kNone, 48000);
+  constexpr size_t kNumChannels = 1;
+  constexpr int kSampleRateHz = 48000;
+  constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz);
+
+  SuppressionFilter filter(Aec3Optimization::kNone, kSampleRateHz);
   FftData cn;
   std::array<float, kFftLengthBy2> e_old_;
   Aec3Fft fft;
   FftData cn_high_bands;
   std::array<float, kFftLengthBy2Plus1> gain;
-  std::vector<std::vector<float>> e(3, std::vector<float>(kBlockSize, 0.f));
+  std::vector<std::vector<std::vector<float>>> e(
+      kNumBands, std::vector<std::vector<float>>(
+                     kNumChannels, std::vector<float>(kBlockSize, 0.f)));
   e_old_.fill(0.f);
   gain.fill(1.f);
   std::for_each(gain.begin() + 30, gain.end(), [](float& a) { a = 0.f; });
@@ -154,18 +180,17 @@
   float e0_input = 0.f;
   float e0_output = 0.f;
   for (size_t k = 0; k < 100; ++k) {
-    ProduceSinusoid(16000, 16000 * 10 / kFftLengthBy2 / 2, &sample_counter,
-                    e[0]);
-    e0_input =
-        std::inner_product(e[0].begin(), e[0].end(), e[0].begin(), e0_input);
+    ProduceSinusoid(16000, 16000 * 10 / kFftLengthBy2 / 2, &sample_counter, &e);
+    e0_input = std::inner_product(e[0][0].begin(), e[0][0].end(),
+                                  e[0][0].begin(), e0_input);
 
     FftData E;
-    fft.PaddedFft(e[0], e_old_, Aec3Fft::Window::kSqrtHanning, &E);
-    std::copy(e[0].begin(), e[0].end(), e_old_.begin());
+    fft.PaddedFft(e[0][0], e_old_, Aec3Fft::Window::kSqrtHanning, &E);
+    std::copy(e[0][0].begin(), e[0][0].end(), e_old_.begin());
 
     filter.ApplyGain(cn, cn_high_bands, gain, 1.f, E, &e);
-    e0_output =
-        std::inner_product(e[0].begin(), e[0].end(), e[0].begin(), e0_output);
+    e0_output = std::inner_product(e[0][0].begin(), e[0][0].end(),
+                                   e[0][0].begin(), e0_output);
   }
 
   EXPECT_LT(0.9f * e0_input, e0_output);
@@ -173,13 +198,19 @@
 
 // Verifies that the suppressor delay.
 TEST(SuppressionFilter, Delay) {
-  SuppressionFilter filter(Aec3Optimization::kNone, 48000);
+  constexpr size_t kNumChannels = 1;
+  constexpr int kSampleRateHz = 48000;
+  constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz);
+
+  SuppressionFilter filter(Aec3Optimization::kNone, kSampleRateHz);
   FftData cn;
   FftData cn_high_bands;
   std::array<float, kFftLengthBy2> e_old_;
   Aec3Fft fft;
   std::array<float, kFftLengthBy2Plus1> gain;
-  std::vector<std::vector<float>> e(3, std::vector<float>(kBlockSize, 0.f));
+  std::vector<std::vector<std::vector<float>>> e(
+      kNumBands, std::vector<std::vector<float>>(
+                     kNumChannels, std::vector<float>(kBlockSize, 0.f)));
 
   gain.fill(1.f);
 
@@ -189,21 +220,26 @@
   cn_high_bands.im.fill(0.f);
 
   for (size_t k = 0; k < 100; ++k) {
-    for (size_t j = 0; j < 3; ++j) {
-      for (size_t i = 0; i < kBlockSize; ++i) {
-        e[j][i] = k * kBlockSize + i;
+    for (size_t band = 0; band < kNumBands; ++band) {
+      for (size_t channel = 0; channel < kNumChannels; ++channel) {
+        for (size_t sample = 0; sample < kBlockSize; ++sample) {
+          e[band][channel][sample] = k * kBlockSize + sample + channel;
+        }
       }
     }
 
     FftData E;
-    fft.PaddedFft(e[0], e_old_, Aec3Fft::Window::kSqrtHanning, &E);
-    std::copy(e[0].begin(), e[0].end(), e_old_.begin());
+    fft.PaddedFft(e[0][0], e_old_, Aec3Fft::Window::kSqrtHanning, &E);
+    std::copy(e[0][0].begin(), e[0][0].end(), e_old_.begin());
 
     filter.ApplyGain(cn, cn_high_bands, gain, 1.f, E, &e);
     if (k > 2) {
-      for (size_t j = 0; j < 2; ++j) {
-        for (size_t i = 0; i < kBlockSize; ++i) {
-          EXPECT_NEAR(k * kBlockSize + i - kBlockSize, e[j][i], 0.01);
+      for (size_t band = 0; band < kNumBands; ++band) {
+        for (size_t channel = 0; channel < kNumChannels; ++channel) {
+          for (size_t sample = 0; sample < kBlockSize; ++sample) {
+            EXPECT_NEAR(k * kBlockSize + sample - kBlockSize + channel,
+                        e[band][channel][sample], 0.01);
+          }
         }
       }
     }
diff --git a/modules/audio_processing/aec3/suppression_gain.cc b/modules/audio_processing/aec3/suppression_gain.cc
index 4831b71..89ebe0f 100644
--- a/modules/audio_processing/aec3/suppression_gain.cc
+++ b/modules/audio_processing/aec3/suppression_gain.cc
@@ -108,7 +108,7 @@
     const std::array<float, kFftLengthBy2Plus1>& comfort_noise_spectrum,
     const absl::optional<int>& narrow_peak_band,
     bool saturated_echo,
-    const std::vector<std::vector<float>>& render,
+    const std::vector<std::vector<std::vector<float>>>& render,
     const std::array<float, kFftLengthBy2Plus1>& low_band_gain) const {
   RTC_DCHECK_LT(0, render.size());
   if (render.size() == 1) {
@@ -131,12 +131,12 @@
 
   // Compute the upper and lower band energies.
   const auto sum_of_squares = [](float a, float b) { return a + b * b; };
-  const float low_band_energy =
-      std::accumulate(render[0].begin(), render[0].end(), 0.f, sum_of_squares);
+  const float low_band_energy = std::accumulate(
+      render[0][0].begin(), render[0][0].end(), 0.f, sum_of_squares);
   float high_band_energy = 0.f;
   for (size_t k = 1; k < render.size(); ++k) {
-    const float energy = std::accumulate(render[k].begin(), render[k].end(),
-                                         0.f, sum_of_squares);
+    const float energy = std::accumulate(
+        render[k][0].begin(), render[k][0].end(), 0.f, sum_of_squares);
     high_band_energy = std::max(high_band_energy, energy);
   }
 
@@ -317,7 +317,7 @@
     const std::array<float, kFftLengthBy2Plus1>& comfort_noise_spectrum,
     const RenderSignalAnalyzer& render_signal_analyzer,
     const AecState& aec_state,
-    const std::vector<std::vector<float>>& render,
+    const std::vector<std::vector<std::vector<float>>>& render,
     float* high_bands_gain,
     std::array<float, kFftLengthBy2Plus1>* low_band_gain) {
   RTC_DCHECK(high_bands_gain);
@@ -366,10 +366,10 @@
 // Detects when the render signal can be considered to have low power and
 // consist of stationary noise.
 bool SuppressionGain::LowNoiseRenderDetector::Detect(
-    const std::vector<std::vector<float>>& render) {
+    const std::vector<std::vector<std::vector<float>>>& render) {
   float x2_sum = 0.f;
   float x2_max = 0.f;
-  for (auto x_k : render[0]) {
+  for (auto x_k : render[0][0]) {
     const float x2 = x_k * x_k;
     x2_sum += x2;
     x2_max = std::max(x2_max, x2);
diff --git a/modules/audio_processing/aec3/suppression_gain.h b/modules/audio_processing/aec3/suppression_gain.h
index 2b34dbe..a583ef0 100644
--- a/modules/audio_processing/aec3/suppression_gain.h
+++ b/modules/audio_processing/aec3/suppression_gain.h
@@ -41,7 +41,7 @@
       const std::array<float, kFftLengthBy2Plus1>& comfort_noise_spectrum,
       const RenderSignalAnalyzer& render_signal_analyzer,
       const AecState& aec_state,
-      const std::vector<std::vector<float>>& render,
+      const std::vector<std::vector<std::vector<float>>>& render,
       float* high_bands_gain,
       std::array<float, kFftLengthBy2Plus1>* low_band_gain);
 
@@ -55,7 +55,7 @@
       const std::array<float, kFftLengthBy2Plus1>& comfort_noise_spectrum,
       const absl::optional<int>& narrow_peak_band,
       bool saturated_echo,
-      const std::vector<std::vector<float>>& render,
+      const std::vector<std::vector<std::vector<float>>>& render,
       const std::array<float, kFftLengthBy2Plus1>& low_band_gain) const;
 
   void GainToNoAudibleEcho(
@@ -84,7 +84,7 @@
 
   class LowNoiseRenderDetector {
    public:
-    bool Detect(const std::vector<std::vector<float>>& render);
+    bool Detect(const std::vector<std::vector<std::vector<float>>>& render);
 
    private:
     float average_power_ = 32768.f * 32768.f;
diff --git a/modules/audio_processing/aec3/suppression_gain_unittest.cc b/modules/audio_processing/aec3/suppression_gain_unittest.cc
index 7d305f8..331b903 100644
--- a/modules/audio_processing/aec3/suppression_gain_unittest.cc
+++ b/modules/audio_processing/aec3/suppression_gain_unittest.cc
@@ -47,8 +47,9 @@
       SuppressionGain(EchoCanceller3Config{}, DetectOptimization(), 16000)
           .GetGain(E2, S2, R2, N2,
                    RenderSignalAnalyzer((EchoCanceller3Config{})), aec_state,
-                   std::vector<std::vector<float>>(
-                       3, std::vector<float>(kBlockSize, 0.f)),
+                   std::vector<std::vector<std::vector<float>>>(
+                       3, std::vector<std::vector<float>>(
+                              1, std::vector<float>(kBlockSize, 0.f))),
                    &high_bands_gain, nullptr),
       "");
 }
@@ -57,8 +58,11 @@
 
 // Does a sanity check that the gains are correctly computed.
 TEST(SuppressionGain, BasicGainComputation) {
+  constexpr size_t kNumChannels = 1;
+  constexpr int kSampleRateHz = 16000;
+  constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz);
   SuppressionGain suppression_gain(EchoCanceller3Config(), DetectOptimization(),
-                                   16000);
+                                   kSampleRateHz);
   RenderSignalAnalyzer analyzer(EchoCanceller3Config{});
   float high_bands_gain;
   std::array<float, kFftLengthBy2Plus1> E2;
@@ -69,13 +73,15 @@
   std::array<float, kFftLengthBy2Plus1> g;
   SubtractorOutput output;
   std::array<float, kBlockSize> y;
-  std::vector<std::vector<float>> x(1, std::vector<float>(kBlockSize, 0.f));
+  std::vector<std::vector<std::vector<float>>> x(
+      kNumBands, std::vector<std::vector<float>>(
+                     kNumChannels, std::vector<float>(kBlockSize, 0.f)));
   EchoCanceller3Config config;
   AecState aec_state(config);
   ApmDataDumper data_dumper(42);
   Subtractor subtractor(config, &data_dumper, DetectOptimization());
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(config, 48000));
+      RenderDelayBuffer::Create(config, kSampleRateHz, kNumChannels));
   absl::optional<DelayEstimate> delay_estimate;
 
   // Ensure that a strong noise is detected to mask any echoes.
diff --git a/modules/audio_processing/audio_processing_impl.cc b/modules/audio_processing/audio_processing_impl.cc
index bc61b52..d639fd5 100644
--- a/modules/audio_processing/audio_processing_impl.cc
+++ b/modules/audio_processing/audio_processing_impl.cc
@@ -1849,7 +1849,8 @@
           echo_control_factory_->Create(proc_sample_rate_hz());
     } else {
       private_submodules_->echo_controller = absl::make_unique<EchoCanceller3>(
-          EchoCanceller3Config(), proc_sample_rate_hz());
+          EchoCanceller3Config(), proc_sample_rate_hz(),
+          /*num_render_channels=*/1, /*num_capture_channels=*/1);
     }
 
     capture_nonlocked_.echo_controller_enabled = true;
diff --git a/modules/audio_processing/audio_processing_impl_unittest.cc b/modules/audio_processing/audio_processing_impl_unittest.cc
index 72bd6735..68d17ae 100644
--- a/modules/audio_processing/audio_processing_impl_unittest.cc
+++ b/modules/audio_processing/audio_processing_impl_unittest.cc
@@ -60,6 +60,12 @@
     return mock;
   }
 
+  std::unique_ptr<EchoControl> Create(int sample_rate_hz,
+                                      size_t num_render_channels,
+                                      size_t num_capture_channels) override {
+    return Create(sample_rate_hz);
+  }
+
  private:
   std::unique_ptr<MockEchoControl> next_mock_;
 };
diff --git a/modules/audio_processing/audio_processing_unittest.cc b/modules/audio_processing/audio_processing_unittest.cc
index 9c30ab0..14ca329 100644
--- a/modules/audio_processing/audio_processing_unittest.cc
+++ b/modules/audio_processing/audio_processing_unittest.cc
@@ -2513,6 +2513,12 @@
     EXPECT_CALL(*ec, ProcessCapture(::testing::_, ::testing::_)).Times(2);
     return std::unique_ptr<EchoControl>(ec);
   }
+
+  std::unique_ptr<EchoControl> Create(int sample_rate_hz,
+                                      size_t num_render_channels,
+                                      size_t num_capture_channels) {
+    return Create(sample_rate_hz);
+  }
 };
 
 TEST(ApmConfiguration, EchoControlInjection) {