Redesign of the render buffering in AEC3

This CL centralizes the render buffering in AEC3 so that all render
buffers are updated and synchronized/aligned with the render alignment
buffer.

Bug: webrtc:8597, chromium:790905
Change-Id: I8a94e5c1f27316b6100b420eec9652ea31c1a91d
Reviewed-on: https://webrtc-review.googlesource.com/25680
Commit-Queue: Per Åhgren <peah@webrtc.org>
Reviewed-by: Gustaf Ullberg <gustaf@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#20989}
diff --git a/modules/audio_processing/BUILD.gn b/modules/audio_processing/BUILD.gn
index fdc4783..6f09165 100644
--- a/modules/audio_processing/BUILD.gn
+++ b/modules/audio_processing/BUILD.gn
@@ -62,6 +62,8 @@
     "aec3/erl_estimator.h",
     "aec3/erle_estimator.cc",
     "aec3/erle_estimator.h",
+    "aec3/fft_buffer.cc",
+    "aec3/fft_buffer.h",
     "aec3/fft_data.h",
     "aec3/frame_blocker.cc",
     "aec3/frame_blocker.h",
@@ -71,6 +73,8 @@
     "aec3/matched_filter.h",
     "aec3/matched_filter_lag_aggregator.cc",
     "aec3/matched_filter_lag_aggregator.h",
+    "aec3/matrix_buffer.cc",
+    "aec3/matrix_buffer.h",
     "aec3/output_selector.cc",
     "aec3/output_selector.h",
     "aec3/render_buffer.cc",
@@ -94,6 +98,8 @@
     "aec3/suppression_filter.h",
     "aec3/suppression_gain.cc",
     "aec3/suppression_gain.h",
+    "aec3/vector_buffer.cc",
+    "aec3/vector_buffer.h",
     "aec3/vector_math.h",
     "aecm/aecm_core.cc",
     "aecm/aecm_core.h",
diff --git a/modules/audio_processing/aec3/adaptive_fir_filter_unittest.cc b/modules/audio_processing/aec3/adaptive_fir_filter_unittest.cc
index 11d7e02..b3a93a7 100644
--- a/modules/audio_processing/aec3/adaptive_fir_filter_unittest.cc
+++ b/modules/audio_processing/aec3/adaptive_fir_filter_unittest.cc
@@ -21,6 +21,7 @@
 #include "modules/audio_processing/aec3/aec3_fft.h"
 #include "modules/audio_processing/aec3/aec_state.h"
 #include "modules/audio_processing/aec3/cascaded_biquad_filter.h"
+#include "modules/audio_processing/aec3/render_delay_buffer.h"
 #include "modules/audio_processing/aec3/render_signal_analyzer.h"
 #include "modules/audio_processing/aec3/shadow_filter_update_gain.h"
 #include "modules/audio_processing/logging/apm_data_dumper.h"
@@ -47,8 +48,8 @@
 // Verifies that the optimized methods for filter adaptation are similar to
 // their reference counterparts.
 TEST(AdaptiveFirFilter, FilterAdaptationNeonOptimizations) {
-  RenderBuffer render_buffer(Aec3Optimization::kNone, 3, 12,
-                             std::vector<size_t>(1, 12));
+  std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
+      RenderDelayBuffer::Create(EchoCanceller3Config(), 3));
   Random random_generator(42U);
   std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
   FftData S_C;
@@ -66,8 +67,13 @@
 
   for (size_t k = 0; k < 30; ++k) {
     RandomizeSampleVector(&random_generator, x[0]);
-    render_buffer.Insert(x);
+    render_delay_buffer->Insert(x);
+    if (k == 0) {
+      render_delay_buffer->Reset();
+    }
+    render_delay_buffer->PrepareCaptureCall();
   }
+  const auto& render_buffer = render_delay_buffer->GetRenderBuffer();
 
   for (size_t j = 0; j < G.re.size(); ++j) {
     G.re[j] = j / 10001.f;
@@ -153,8 +159,8 @@
 TEST(AdaptiveFirFilter, FilterAdaptationSse2Optimizations) {
   bool use_sse2 = (WebRtc_GetCPUInfo(kSSE2) != 0);
   if (use_sse2) {
-    RenderBuffer render_buffer(Aec3Optimization::kNone, 3, 12,
-                               std::vector<size_t>(1, 12));
+    std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
+        RenderDelayBuffer::Create(EchoCanceller3Config(), 3));
     Random random_generator(42U);
     std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
     FftData S_C;
@@ -172,7 +178,12 @@
 
     for (size_t k = 0; k < 500; ++k) {
       RandomizeSampleVector(&random_generator, x[0]);
-      render_buffer.Insert(x);
+      render_delay_buffer->Insert(x);
+      if (k == 0) {
+        render_delay_buffer->Reset();
+      }
+      render_delay_buffer->PrepareCaptureCall();
+      const auto& render_buffer = render_delay_buffer->GetRenderBuffer();
 
       ApplyFilter_SSE2(render_buffer, H_SSE2, &S_SSE2);
       ApplyFilter(render_buffer, H_C, &S_C);
@@ -264,10 +275,10 @@
 TEST(AdaptiveFirFilter, NullFilterOutput) {
   ApmDataDumper data_dumper(42);
   AdaptiveFirFilter filter(9, DetectOptimization(), &data_dumper);
-  RenderBuffer render_buffer(Aec3Optimization::kNone, 3,
-                             filter.SizePartitions(),
-                             std::vector<size_t>(1, filter.SizePartitions()));
-  EXPECT_DEATH(filter.Filter(render_buffer, nullptr), "");
+  std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
+      RenderDelayBuffer::Create(EchoCanceller3Config(), 3));
+  EXPECT_DEATH(filter.Filter(render_delay_buffer->GetRenderBuffer(), nullptr),
+               "");
 }
 
 #endif
@@ -295,11 +306,13 @@
 TEST(AdaptiveFirFilter, FilterAndAdapt) {
   constexpr size_t kNumBlocksToProcess = 500;
   ApmDataDumper data_dumper(42);
-  AdaptiveFirFilter filter(9, DetectOptimization(), &data_dumper);
+  AdaptiveFirFilter filter(kAdaptiveFilterLength, DetectOptimization(),
+                           &data_dumper);
   Aec3Fft fft;
-  RenderBuffer render_buffer(Aec3Optimization::kNone, 3,
-                             filter.SizePartitions(),
-                             std::vector<size_t>(1, filter.SizePartitions()));
+  EchoCanceller3Config config;
+  config.delay.min_echo_path_delay_blocks = 0;
+  std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
+      RenderDelayBuffer::Create(config, 3));
   ShadowFilterUpdateGain gain;
   Random random_generator(42U);
   std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
@@ -345,7 +358,13 @@
       x_hp_filter.Process(x[0]);
       y_hp_filter.Process(y);
 
-      render_buffer.Insert(x);
+      render_delay_buffer->Insert(x);
+      if (k == 0) {
+        render_delay_buffer->Reset();
+      }
+      render_delay_buffer->PrepareCaptureCall();
+      const auto& render_buffer = render_delay_buffer->GetRenderBuffer();
+
       render_signal_analyzer.Update(render_buffer, aec_state.FilterDelay());
 
       filter.Filter(render_buffer, &S);
@@ -363,7 +382,8 @@
       gain.Compute(render_buffer, render_signal_analyzer, E,
                    filter.SizePartitions(), false, &G);
       filter.Adapt(render_buffer, G);
-      aec_state.HandleEchoPathChange(EchoPathVariability(false, false));
+      aec_state.HandleEchoPathChange(EchoPathVariability(
+          false, EchoPathVariability::DelayAdjustment::kNone, false));
       aec_state.Update(filter.FilterFrequencyResponse(),
                        filter.FilterImpulseResponse(), true, rtc::nullopt,
                        render_buffer, E2_main, Y2, x[0], s, false);
diff --git a/modules/audio_processing/aec3/aec3_common.h b/modules/audio_processing/aec3/aec3_common.h
index 3e80442..aa6ffd9 100644
--- a/modules/audio_processing/aec3/aec3_common.h
+++ b/modules/audio_processing/aec3/aec3_common.h
@@ -42,6 +42,7 @@
 constexpr int kUnknownDelayRenderWindowSize = 30;
 constexpr int kAdaptiveFilterTimeDomainLength =
     kAdaptiveFilterLength * kFftLengthBy2;
+constexpr int kRenderTransferQueueSizeFrames = 100;
 
 constexpr size_t kMaxNumBands = 3;
 constexpr size_t kSubFrameLength = 80;
@@ -52,11 +53,6 @@
 constexpr size_t kMatchedFilterAlignmentShiftSizeSubBlocks =
     kMatchedFilterWindowSizeSubBlocks * 3 / 4;
 
-constexpr size_t kMinEchoPathDelayBlocks = 5;
-constexpr size_t kMaxApiCallsJitterBlocks = 26;
-constexpr size_t kRenderTransferQueueSize = kMaxApiCallsJitterBlocks / 2;
-static_assert(2 * kRenderTransferQueueSize >= kMaxApiCallsJitterBlocks,
-              "Requirement to ensure buffer overflow detection");
 
 constexpr size_t kEchoPathChangeConvergenceBlocks = 2 * kNumBlocksPerSecond;
 
@@ -83,9 +79,8 @@
 
 constexpr size_t GetRenderDelayBufferSize(size_t down_sampling_factor,
                                           size_t num_matched_filters) {
-  return (3 *
-          GetDownSampledBufferSize(down_sampling_factor, num_matched_filters)) /
-         (4 * kBlockSize / down_sampling_factor);
+  return GetDownSampledBufferSize(down_sampling_factor, num_matched_filters) /
+         (kBlockSize / down_sampling_factor);
 }
 
 // Detects what kind of optimizations to use for the code.
diff --git a/modules/audio_processing/aec3/aec_state.cc b/modules/audio_processing/aec3/aec_state.cc
index 9349e47..58859d8 100644
--- a/modules/audio_processing/aec3/aec_state.cc
+++ b/modules/audio_processing/aec3/aec_state.cc
@@ -64,28 +64,45 @@
 
 void AecState::HandleEchoPathChange(
     const EchoPathVariability& echo_path_variability) {
-  if (echo_path_variability.AudioPathChanged()) {
+  const auto full_reset = [&]() {
     blocks_since_last_saturation_ = kUnknownDelayRenderWindowSize + 1;
     usable_linear_estimate_ = false;
     echo_leakage_detected_ = false;
     capture_signal_saturation_ = false;
     echo_saturation_ = false;
     max_render_.fill(0.f);
+    force_zero_gain_counter_ = 0;
+    blocks_with_filter_adaptation_ = 0;
+    blocks_with_strong_render_ = 0;
+    initial_state_ = true;
+    capture_block_counter_ = 0;
+    linear_echo_estimate_ = false;
+    sufficient_filter_updates_ = false;
+    render_received_ = false;
+    force_zero_gain_ = true;
+  };
 
-    if (echo_path_variability.delay_change) {
-      force_zero_gain_counter_ = 0;
-      blocks_with_filter_adaptation_ = 0;
-      blocks_with_strong_render_ = 0;
-      initial_state_ = true;
-      linear_echo_estimate_ = false;
-      sufficient_filter_updates_ = false;
-      render_received_ = false;
-      force_zero_gain_ = true;
-      capture_block_counter_ = 0;
-    }
-    if (echo_path_variability.gain_change) {
-      capture_block_counter_ = kNumBlocksPerSecond;
-    }
+  // TODO(peah): Refine the reset scheme according to the type of gain and
+  // delay adjustment.
+  if (echo_path_variability.gain_change) {
+    full_reset();
+  }
+
+  if (echo_path_variability.delay_change !=
+      EchoPathVariability::DelayAdjustment::kBufferReadjustment) {
+    full_reset();
+  } else if (echo_path_variability.delay_change !=
+             EchoPathVariability::DelayAdjustment::kBufferFlush) {
+    full_reset();
+
+  } else if (echo_path_variability.delay_change !=
+             EchoPathVariability::DelayAdjustment::kDelayReset) {
+    full_reset();
+  } else if (echo_path_variability.delay_change !=
+             EchoPathVariability::DelayAdjustment::kNewDetectedDelay) {
+    full_reset();
+  } else if (echo_path_variability.gain_change) {
+    capture_block_counter_ = kNumBlocksPerSecond;
   }
 }
 
diff --git a/modules/audio_processing/aec3/aec_state_unittest.cc b/modules/audio_processing/aec3/aec_state_unittest.cc
index 34b877b..fe09086 100644
--- a/modules/audio_processing/aec3/aec_state_unittest.cc
+++ b/modules/audio_processing/aec3/aec_state_unittest.cc
@@ -10,6 +10,8 @@
 
 #include "modules/audio_processing/aec3/aec_state.h"
 
+#include "modules/audio_processing/aec3/aec3_fft.h"
+#include "modules/audio_processing/aec3/render_delay_buffer.h"
 #include "modules/audio_processing/logging/apm_data_dumper.h"
 #include "test/gtest.h"
 
@@ -18,14 +20,17 @@
 // Verify the general functionality of AecState
 TEST(AecState, NormalUsage) {
   ApmDataDumper data_dumper(42);
-  AecState state(EchoCanceller3Config{});
-  RenderBuffer render_buffer(Aec3Optimization::kNone, 3, 30,
-                             std::vector<size_t>(1, 30));
+  EchoCanceller3Config config;
+  AecState state(config);
+  std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
+      RenderDelayBuffer::Create(config, 3));
   std::array<float, kFftLengthBy2Plus1> E2_main = {};
   std::array<float, kFftLengthBy2Plus1> Y2 = {};
   std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
-  EchoPathVariability echo_path_variability(false, false);
+  EchoPathVariability echo_path_variability(
+      false, EchoPathVariability::DelayAdjustment::kNone, false);
   std::array<float, kBlockSize> s;
+  Aec3Fft fft;
   s.fill(100.f);
 
   std::vector<std::array<float, kFftLengthBy2Plus1>>
@@ -44,44 +49,53 @@
   // Verify that linear AEC usability is false when the filter is diverged and
   // there is no external delay reported.
   state.Update(diverged_filter_frequency_response, impulse_response, true,
-               rtc::nullopt, render_buffer, E2_main, Y2, x[0], s, false);
+               rtc::nullopt, render_delay_buffer->GetRenderBuffer(), E2_main,
+               Y2, x[0], s, false);
   EXPECT_FALSE(state.UsableLinearEstimate());
 
   // Verify that linear AEC usability is true when the filter is converged
   std::fill(x[0].begin(), x[0].end(), 101.f);
   for (int k = 0; k < 3000; ++k) {
     state.Update(converged_filter_frequency_response, impulse_response, true, 2,
-                 render_buffer, E2_main, Y2, x[0], s, false);
+                 render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0], s,
+                 false);
   }
   EXPECT_TRUE(state.UsableLinearEstimate());
 
   // Verify that linear AEC usability becomes false after an echo path change is
   // reported
-  state.HandleEchoPathChange(EchoPathVariability(true, false));
+  state.HandleEchoPathChange(EchoPathVariability(
+      true, EchoPathVariability::DelayAdjustment::kNone, false));
   state.Update(converged_filter_frequency_response, impulse_response, true, 2,
-               render_buffer, E2_main, Y2, x[0], s, false);
+               render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0], s,
+               false);
   EXPECT_FALSE(state.UsableLinearEstimate());
 
   // Verify that the active render detection works as intended.
   std::fill(x[0].begin(), x[0].end(), 101.f);
-  state.HandleEchoPathChange(EchoPathVariability(true, true));
+  state.HandleEchoPathChange(EchoPathVariability(
+      true, EchoPathVariability::DelayAdjustment::kNewDetectedDelay, false));
   state.Update(converged_filter_frequency_response, impulse_response, true, 2,
-               render_buffer, E2_main, Y2, x[0], s, false);
+               render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0], s,
+               false);
   EXPECT_FALSE(state.ActiveRender());
 
   for (int k = 0; k < 1000; ++k) {
     state.Update(converged_filter_frequency_response, impulse_response, true, 2,
-                 render_buffer, E2_main, Y2, x[0], s, false);
+                 render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0], s,
+                 false);
   }
   EXPECT_TRUE(state.ActiveRender());
 
   // Verify that echo leakage is properly reported.
   state.Update(converged_filter_frequency_response, impulse_response, true, 2,
-               render_buffer, E2_main, Y2, x[0], s, false);
+               render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0], s,
+               false);
   EXPECT_FALSE(state.EchoLeakageDetected());
 
   state.Update(converged_filter_frequency_response, impulse_response, true, 2,
-               render_buffer, E2_main, Y2, x[0], s, true);
+               render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0], s,
+               true);
   EXPECT_TRUE(state.EchoLeakageDetected());
 
   // Verify that the ERL is properly estimated
@@ -90,14 +104,20 @@
   }
 
   x[0][0] = 5000.f;
-  for (size_t k = 0; k < render_buffer.Buffer().size(); ++k) {
-    render_buffer.Insert(x);
+  for (size_t k = 0; k < render_delay_buffer->GetRenderBuffer().Buffer().size();
+       ++k) {
+    render_delay_buffer->Insert(x);
+    if (k == 0) {
+      render_delay_buffer->Reset();
+    }
+    render_delay_buffer->PrepareCaptureCall();
   }
 
   Y2.fill(10.f * 10000.f * 10000.f);
   for (size_t k = 0; k < 1000; ++k) {
     state.Update(converged_filter_frequency_response, impulse_response, true, 2,
-                 render_buffer, E2_main, Y2, x[0], s, false);
+                 render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0], s,
+                 false);
   }
 
   ASSERT_TRUE(state.UsableLinearEstimate());
@@ -113,7 +133,8 @@
   Y2.fill(10.f * E2_main[0]);
   for (size_t k = 0; k < 1000; ++k) {
     state.Update(converged_filter_frequency_response, impulse_response, true, 2,
-                 render_buffer, E2_main, Y2, x[0], s, false);
+                 render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0], s,
+                 false);
   }
   ASSERT_TRUE(state.UsableLinearEstimate());
   {
@@ -133,7 +154,8 @@
   Y2.fill(5.f * E2_main[0]);
   for (size_t k = 0; k < 1000; ++k) {
     state.Update(converged_filter_frequency_response, impulse_response, true, 2,
-                 render_buffer, E2_main, Y2, x[0], s, false);
+                 render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0], s,
+                 false);
   }
 
   ASSERT_TRUE(state.UsableLinearEstimate());
@@ -154,13 +176,15 @@
 // Verifies the delay for a converged filter is correctly identified.
 TEST(AecState, ConvergedFilterDelay) {
   constexpr int kFilterLength = 10;
-  AecState state(EchoCanceller3Config{});
-  RenderBuffer render_buffer(Aec3Optimization::kNone, 3, 30,
-                             std::vector<size_t>(1, 30));
+  EchoCanceller3Config config;
+  AecState state(config);
+  std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
+      RenderDelayBuffer::Create(config, 3));
   std::array<float, kFftLengthBy2Plus1> E2_main;
   std::array<float, kFftLengthBy2Plus1> Y2;
   std::array<float, kBlockSize> x;
-  EchoPathVariability echo_path_variability(false, false);
+  EchoPathVariability echo_path_variability(
+      false, EchoPathVariability::DelayAdjustment::kNone, false);
   std::array<float, kBlockSize> s;
   s.fill(100.f);
   x.fill(0.f);
@@ -180,7 +204,8 @@
     frequency_response[k][0] = 0.f;
     state.HandleEchoPathChange(echo_path_variability);
     state.Update(frequency_response, impulse_response, true, rtc::nullopt,
-                 render_buffer, E2_main, Y2, x, s, false);
+                 render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x, s,
+                 false);
     EXPECT_TRUE(k == (kFilterLength - 1) || state.FilterDelay());
     if (k != (kFilterLength - 1)) {
       EXPECT_EQ(k, state.FilterDelay());
@@ -190,7 +215,10 @@
 
 // Verify that the externally reported delay is properly reported and converted.
 TEST(AecState, ExternalDelay) {
-  AecState state(EchoCanceller3Config{});
+  EchoCanceller3Config config;
+  AecState state(config);
+  std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
+      RenderDelayBuffer::Create(config, 3));
   std::array<float, kFftLengthBy2Plus1> E2_main;
   std::array<float, kFftLengthBy2Plus1> E2_shadow;
   std::array<float, kFftLengthBy2Plus1> Y2;
@@ -201,8 +229,7 @@
   E2_shadow.fill(0.f);
   Y2.fill(0.f);
   x.fill(0.f);
-  RenderBuffer render_buffer(Aec3Optimization::kNone, 3, 30,
-                             std::vector<size_t>(1, 30));
+
   std::vector<std::array<float, kFftLengthBy2Plus1>> frequency_response(
       kAdaptiveFilterLength);
   for (auto& v : frequency_response) {
@@ -213,18 +240,22 @@
   impulse_response.fill(0.f);
 
   for (size_t k = 0; k < frequency_response.size() - 1; ++k) {
-    state.HandleEchoPathChange(EchoPathVariability(false, false));
+    state.HandleEchoPathChange(EchoPathVariability(
+        false, EchoPathVariability::DelayAdjustment::kNone, false));
     state.Update(frequency_response, impulse_response, true, k * kBlockSize + 5,
-                 render_buffer, E2_main, Y2, x, s, false);
+                 render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x, s,
+                 false);
     EXPECT_TRUE(state.ExternalDelay());
     EXPECT_EQ(k, state.ExternalDelay());
   }
 
   // Verify that the externally reported delay is properly unset when it is no
   // longer present.
-  state.HandleEchoPathChange(EchoPathVariability(false, false));
+  state.HandleEchoPathChange(EchoPathVariability(
+      false, EchoPathVariability::DelayAdjustment::kNone, false));
   state.Update(frequency_response, impulse_response, true, rtc::nullopt,
-               render_buffer, E2_main, Y2, x, s, false);
+               render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x, s,
+               false);
   EXPECT_FALSE(state.ExternalDelay());
 }
 
diff --git a/modules/audio_processing/aec3/block_processor.cc b/modules/audio_processing/aec3/block_processor.cc
index f0b9630..f12e7ee 100644
--- a/modules/audio_processing/aec3/block_processor.cc
+++ b/modules/audio_processing/aec3/block_processor.cc
@@ -25,7 +25,8 @@
 
 class BlockProcessorImpl final : public BlockProcessor {
  public:
-  BlockProcessorImpl(int sample_rate_hz,
+  BlockProcessorImpl(const EchoCanceller3Config& config,
+                     int sample_rate_hz,
                      std::unique_ptr<RenderDelayBuffer> render_buffer,
                      std::unique_ptr<RenderDelayController> delay_controller,
                      std::unique_ptr<EchoRemover> echo_remover);
@@ -44,31 +45,35 @@
 
  private:
   static int instance_count_;
-  bool no_capture_data_received_ = true;
-  bool no_render_data_received_ = true;
   std::unique_ptr<ApmDataDumper> data_dumper_;
+  const EchoCanceller3Config config_;
+  bool capture_properly_started_ = false;
+  bool render_properly_started_ = false;
   const size_t sample_rate_hz_;
   std::unique_ptr<RenderDelayBuffer> render_buffer_;
   std::unique_ptr<RenderDelayController> delay_controller_;
   std::unique_ptr<EchoRemover> echo_remover_;
   BlockProcessorMetrics metrics_;
-  bool render_buffer_overrun_occurred_ = false;
+  RenderDelayBuffer::BufferingEvent render_event_;
   RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(BlockProcessorImpl);
 };
 
 int BlockProcessorImpl::instance_count_ = 0;
 
 BlockProcessorImpl::BlockProcessorImpl(
+    const EchoCanceller3Config& config,
     int sample_rate_hz,
     std::unique_ptr<RenderDelayBuffer> render_buffer,
     std::unique_ptr<RenderDelayController> delay_controller,
     std::unique_ptr<EchoRemover> echo_remover)
     : data_dumper_(
           new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
+      config_(config),
       sample_rate_hz_(sample_rate_hz),
       render_buffer_(std::move(render_buffer)),
       delay_controller_(std::move(delay_controller)),
-      echo_remover_(std::move(echo_remover)) {
+      echo_remover_(std::move(echo_remover)),
+      render_event_(RenderDelayBuffer::BufferingEvent::kNone) {
   RTC_DCHECK(ValidFullBandRate(sample_rate_hz_));
 }
 
@@ -87,67 +92,93 @@
                         &(*capture_block)[0][0],
                         LowestBandRate(sample_rate_hz_), 1);
 
-  // Do not start processing until render data has been buffered as that will
-  // cause the buffers to be wrongly aligned.
-  no_capture_data_received_ = false;
-  if (no_render_data_received_) {
+  if (render_properly_started_) {
+    if (!capture_properly_started_) {
+      capture_properly_started_ = true;
+      render_buffer_->Reset();
+    }
+  } else {
+    // If no render data has yet arrived, do not process the capture signal.
     return;
   }
 
+  EchoPathVariability echo_path_variability(
+      echo_path_gain_change, EchoPathVariability::DelayAdjustment::kNone,
+      false);
+
+  if (render_event_ == RenderDelayBuffer::BufferingEvent::kRenderOverrun &&
+      render_properly_started_) {
+    echo_path_variability.delay_change =
+        EchoPathVariability::DelayAdjustment::kBufferFlush;
+    delay_controller_->Reset();
+    render_buffer_->Reset();
+    RTC_LOG(LS_WARNING) << "Reset due to render buffer overrun.";
+  }
+
+  // Update the render buffers with any newly arrived render blocks and prepare
+  // the render buffers for reading the render data corresponding to the current
+  // capture block.
+  render_event_ = render_buffer_->PrepareCaptureCall();
+  RTC_DCHECK(RenderDelayBuffer::BufferingEvent::kRenderOverrun !=
+             render_event_);
+  if (render_event_ == RenderDelayBuffer::BufferingEvent::kRenderUnderrun) {
+    echo_path_variability.delay_change =
+        EchoPathVariability::DelayAdjustment::kBufferReadjustment;
+    delay_controller_->Reset();
+    render_buffer_->Reset();
+  } else if (render_event_ == RenderDelayBuffer::BufferingEvent::kApiCallSkew) {
+    // There have been too many render calls in a row. Reset to avoid noncausal
+    // echo.
+    echo_path_variability.delay_change =
+        EchoPathVariability::DelayAdjustment::kDelayReset;
+    delay_controller_->Reset();
+    render_buffer_->Reset();
+    capture_properly_started_ = false;
+    render_properly_started_ = false;
+  }
+
   data_dumper_->DumpWav("aec3_processblock_capture_input2", kBlockSize,
                         &(*capture_block)[0][0],
                         LowestBandRate(sample_rate_hz_), 1);
 
-  bool render_buffer_underrun = false;
-  if (render_buffer_overrun_occurred_) {
-    // Reset the render buffers and the alignment functionality when there has
-    // been a render buffer overrun as the buffer alignment may be noncausal.
-    delay_controller_->Reset();
-    render_buffer_->Reset();
-    RTC_LOG(LS_WARNING) << "Reset due to detected render buffer overrun.";
-  }
-
-  // Update the render buffers with new render data, filling the buffers with
-  // empty blocks when there is no render data available.
-  render_buffer_underrun = !render_buffer_->UpdateBuffers();
-  if (render_buffer_underrun) {
-    RTC_LOG(LS_WARNING) << "Render API jitter buffer underrun.";
-  }
-
   // Compute and and apply the render delay required to achieve proper signal
   // alignment.
-  const size_t old_delay = render_buffer_->Delay();
-  const size_t new_delay = delay_controller_->GetDelay(
+  const size_t estimated_delay = delay_controller_->GetDelay(
       render_buffer_->GetDownsampledRenderBuffer(), (*capture_block)[0]);
+  const size_t new_delay =
+      std::min(render_buffer_->MaxDelay(), estimated_delay);
 
-  bool delay_change;
-  if (new_delay >= kMinEchoPathDelayBlocks) {
+  bool delay_change = render_buffer_->Delay() != new_delay;
+  if (delay_change && new_delay >= config_.delay.min_echo_path_delay_blocks) {
+    echo_path_variability.delay_change =
+        EchoPathVariability::DelayAdjustment::kNewDetectedDelay;
     render_buffer_->SetDelay(new_delay);
-    const size_t achieved_delay = render_buffer_->Delay();
-    delay_change = old_delay != achieved_delay || old_delay != new_delay ||
-                   render_buffer_overrun_occurred_;
-
-    // Inform the delay controller of the actually set delay to allow it to
-    // properly react to a non-feasible delay.
-    delay_controller_->SetDelay(achieved_delay);
-  } else {
+    RTC_DCHECK_EQ(render_buffer_->Delay(), new_delay);
+    delay_controller_->SetDelay(new_delay);
+  } else if (delay_change &&
+             new_delay < config_.delay.min_echo_path_delay_blocks) {
+    // A noncausal delay has been detected. This can only happen if there is
+    // clockdrift, an audio pipeline issue has occurred or the specified minimum
+    // delay is too short. Perform a full reset.
+    echo_path_variability.delay_change =
+        EchoPathVariability::DelayAdjustment::kDelayReset;
     delay_controller_->Reset();
     render_buffer_->Reset();
-    delay_change = true;
+    capture_properly_started_ = false;
+    render_properly_started_ = false;
     RTC_LOG(LS_WARNING) << "Reset due to noncausal delay.";
   }
 
   // Remove the echo from the capture signal.
   echo_remover_->ProcessCapture(
-      delay_controller_->AlignmentHeadroomSamples(),
-      EchoPathVariability(echo_path_gain_change, delay_change),
+      delay_controller_->AlignmentHeadroomSamples(), echo_path_variability,
       capture_signal_saturation, render_buffer_->GetRenderBuffer(),
       capture_block);
 
   // Update the metrics.
-  metrics_.UpdateCapture(render_buffer_underrun);
+  metrics_.UpdateCapture(false);
 
-  render_buffer_overrun_occurred_ = false;
+  render_event_ = RenderDelayBuffer::BufferingEvent::kNone;
 }
 
 void BlockProcessorImpl::BufferRender(
@@ -158,23 +189,15 @@
                         static_cast<int>(BlockProcessorApiCall::kRender));
   data_dumper_->DumpWav("aec3_processblock_render_input", kBlockSize,
                         &block[0][0], LowestBandRate(sample_rate_hz_), 1);
-
-  no_render_data_received_ = false;
-
-  // Do not start buffer render data until capture data has been received as
-  // that data may give a false alignment.
-  if (no_capture_data_received_) {
-    return;
-  }
-
   data_dumper_->DumpWav("aec3_processblock_render_input2", kBlockSize,
                         &block[0][0], LowestBandRate(sample_rate_hz_), 1);
 
-  // Buffer the render data.
-  render_buffer_overrun_occurred_ = !render_buffer_->Insert(block);
+  render_event_ = render_buffer_->Insert(block);
 
-  // Update the metrics.
-  metrics_.UpdateRender(render_buffer_overrun_occurred_);
+  metrics_.UpdateRender(render_event_ !=
+                        RenderDelayBuffer::BufferingEvent::kNone);
+
+  render_properly_started_ = true;
 }
 
 void BlockProcessorImpl::UpdateEchoLeakageStatus(bool leakage_detected) {
@@ -191,12 +214,8 @@
 
 BlockProcessor* BlockProcessor::Create(const EchoCanceller3Config& config,
                                        int sample_rate_hz) {
-  std::unique_ptr<RenderDelayBuffer> render_buffer(RenderDelayBuffer::Create(
-      NumBandsForRate(sample_rate_hz), config.delay.down_sampling_factor,
-      GetDownSampledBufferSize(config.delay.down_sampling_factor,
-                               config.delay.num_filters),
-      GetRenderDelayBufferSize(config.delay.down_sampling_factor,
-                               config.delay.num_filters)));
+  std::unique_ptr<RenderDelayBuffer> render_buffer(
+      RenderDelayBuffer::Create(config, NumBandsForRate(sample_rate_hz)));
   std::unique_ptr<RenderDelayController> delay_controller(
       RenderDelayController::Create(config, sample_rate_hz));
   std::unique_ptr<EchoRemover> echo_remover(
@@ -223,9 +242,9 @@
     std::unique_ptr<RenderDelayBuffer> render_buffer,
     std::unique_ptr<RenderDelayController> delay_controller,
     std::unique_ptr<EchoRemover> echo_remover) {
-  return new BlockProcessorImpl(sample_rate_hz, std::move(render_buffer),
-                                std::move(delay_controller),
-                                std::move(echo_remover));
+  return new BlockProcessorImpl(
+      config, sample_rate_hz, std::move(render_buffer),
+      std::move(delay_controller), std::move(echo_remover));
 }
 
 }  // namespace webrtc
diff --git a/modules/audio_processing/aec3/block_processor_unittest.cc b/modules/audio_processing/aec3/block_processor_unittest.cc
index 18d1f65..1d97002 100644
--- a/modules/audio_processing/aec3/block_processor_unittest.cc
+++ b/modules/audio_processing/aec3/block_processor_unittest.cc
@@ -115,7 +115,7 @@
             new StrictMock<webrtc::test::MockRenderDelayBuffer>(rate));
     EXPECT_CALL(*render_delay_buffer_mock, Insert(_))
         .Times(kNumBlocks)
-        .WillRepeatedly(Return(true));
+        .WillRepeatedly(Return(RenderDelayBuffer::BufferingEvent::kNone));
     EXPECT_CALL(*render_delay_buffer_mock, IsBlockAvailable())
         .Times(kNumBlocks)
         .WillRepeatedly(Return(true));
@@ -160,11 +160,12 @@
 
     EXPECT_CALL(*render_delay_buffer_mock, Insert(_))
         .Times(kNumBlocks - 1)
-        .WillRepeatedly(Return(true));
+        .WillRepeatedly(Return(RenderDelayBuffer::BufferingEvent::kNone));
     EXPECT_CALL(*render_delay_buffer_mock, IsBlockAvailable())
         .Times(kNumBlocks)
         .WillRepeatedly(Return(true));
-    EXPECT_CALL(*render_delay_buffer_mock, UpdateBuffers()).Times(kNumBlocks);
+    EXPECT_CALL(*render_delay_buffer_mock, PrepareCaptureCall())
+        .Times(kNumBlocks);
     EXPECT_CALL(*render_delay_buffer_mock, SetDelay(9)).Times(AtLeast(1));
     EXPECT_CALL(*render_delay_buffer_mock, Delay())
         .Times(kNumBlocks)
diff --git a/modules/audio_processing/aec3/comfort_noise_generator_unittest.cc b/modules/audio_processing/aec3/comfort_noise_generator_unittest.cc
index 46da3ec..d7e9407 100644
--- a/modules/audio_processing/aec3/comfort_noise_generator_unittest.cc
+++ b/modules/audio_processing/aec3/comfort_noise_generator_unittest.cc
@@ -24,7 +24,7 @@
 
 float Power(const FftData& N) {
   std::array<float, kFftLengthBy2Plus1> N2;
-  N.Spectrum(Aec3Optimization::kNone, &N2);
+  N.Spectrum(Aec3Optimization::kNone, N2);
   return std::accumulate(N2.begin(), N2.end(), 0.f) / N2.size();
 }
 
diff --git a/modules/audio_processing/aec3/downsampled_render_buffer.cc b/modules/audio_processing/aec3/downsampled_render_buffer.cc
index efc733b..cd6ae48 100644
--- a/modules/audio_processing/aec3/downsampled_render_buffer.cc
+++ b/modules/audio_processing/aec3/downsampled_render_buffer.cc
@@ -13,7 +13,9 @@
 namespace webrtc {
 
 DownsampledRenderBuffer::DownsampledRenderBuffer(size_t downsampled_buffer_size)
-    : buffer(downsampled_buffer_size, 0.f) {}
+    : size(downsampled_buffer_size), buffer(downsampled_buffer_size, 0.f) {
+  std::fill(buffer.begin(), buffer.end(), 0.f);
+}
 
 DownsampledRenderBuffer::~DownsampledRenderBuffer() = default;
 
diff --git a/modules/audio_processing/aec3/downsampled_render_buffer.h b/modules/audio_processing/aec3/downsampled_render_buffer.h
index 531852a..c656846 100644
--- a/modules/audio_processing/aec3/downsampled_render_buffer.h
+++ b/modules/audio_processing/aec3/downsampled_render_buffer.h
@@ -14,6 +14,7 @@
 #include <vector>
 
 #include "modules/audio_processing/aec3/aec3_common.h"
+#include "rtc_base/checks.h"
 
 namespace webrtc {
 
@@ -21,8 +22,31 @@
 struct DownsampledRenderBuffer {
   explicit DownsampledRenderBuffer(size_t downsampled_buffer_size);
   ~DownsampledRenderBuffer();
+
+  size_t IncIndex(size_t index) {
+    return index < (buffer.size() - 1) ? index + 1 : 0;
+  }
+
+  size_t DecIndex(size_t index) {
+    return index > 0 ? index - 1 : buffer.size() - 1;
+  }
+
+  size_t OffsetIndex(size_t index, int offset) {
+    RTC_DCHECK_GE(buffer.size(), offset);
+    return (buffer.size() + index + offset) % buffer.size();
+  }
+
+  void UpdateWriteIndex(int offset) { write = OffsetIndex(write, offset); }
+  void IncWriteIndex() { write = IncIndex(write); }
+  void DecWriteIndex() { write = DecIndex(write); }
+  void UpdateReadIndex(int offset) { read = OffsetIndex(read, offset); }
+  void IncReadIndex() { read = IncIndex(read); }
+  void DecReadIndex() { read = DecIndex(read); }
+
+  size_t size;
   std::vector<float> buffer;
-  int position = 0;
+  int write = 0;
+  int read = 0;
 };
 
 }  // namespace webrtc
diff --git a/modules/audio_processing/aec3/echo_canceller3.cc b/modules/audio_processing/aec3/echo_canceller3.cc
index 491faa0..100ed27 100644
--- a/modules/audio_processing/aec3/echo_canceller3.cc
+++ b/modules/audio_processing/aec3/echo_canceller3.cc
@@ -203,11 +203,13 @@
 EchoCanceller3::EchoCanceller3(const EchoCanceller3Config& config,
                                int sample_rate_hz,
                                bool use_highpass_filter)
-    : EchoCanceller3(sample_rate_hz,
+    : EchoCanceller3(config,
+                     sample_rate_hz,
                      use_highpass_filter,
                      std::unique_ptr<BlockProcessor>(
                          BlockProcessor::Create(config, sample_rate_hz))) {}
-EchoCanceller3::EchoCanceller3(int sample_rate_hz,
+EchoCanceller3::EchoCanceller3(const EchoCanceller3Config& config,
+                               int sample_rate_hz,
                                bool use_highpass_filter,
                                std::unique_ptr<BlockProcessor> block_processor)
     : data_dumper_(
@@ -219,7 +221,7 @@
       capture_blocker_(num_bands_),
       render_blocker_(num_bands_),
       render_transfer_queue_(
-          kRenderTransferQueueSize,
+          kRenderTransferQueueSizeFrames,
           std::vector<std::vector<float>>(
               num_bands_,
               std::vector<float>(frame_length_, 0.f)),
diff --git a/modules/audio_processing/aec3/echo_canceller3.h b/modules/audio_processing/aec3/echo_canceller3.h
index 475bacb..d136c1e 100644
--- a/modules/audio_processing/aec3/echo_canceller3.h
+++ b/modules/audio_processing/aec3/echo_canceller3.h
@@ -67,7 +67,8 @@
                  int sample_rate_hz,
                  bool use_highpass_filter);
   // Testing c-tor that is used only for testing purposes.
-  EchoCanceller3(int sample_rate_hz,
+  EchoCanceller3(const EchoCanceller3Config& config,
+                 int sample_rate_hz,
                  bool use_highpass_filter,
                  std::unique_ptr<BlockProcessor> block_processor);
   ~EchoCanceller3() override;
diff --git a/modules/audio_processing/aec3/echo_canceller3_unittest.cc b/modules/audio_processing/aec3/echo_canceller3_unittest.cc
index 75de48b..d54295f 100644
--- a/modules/audio_processing/aec3/echo_canceller3_unittest.cc
+++ b/modules/audio_processing/aec3/echo_canceller3_unittest.cc
@@ -160,7 +160,7 @@
   // output.
   void RunCaptureTransportVerificationTest() {
     EchoCanceller3 aec3(
-        sample_rate_hz_, false,
+        EchoCanceller3Config(), sample_rate_hz_, false,
         std::unique_ptr<BlockProcessor>(
             new CaptureTransportVerificationProcessor(num_bands_)));
 
@@ -185,7 +185,7 @@
   // block processor.
   void RunRenderTransportVerificationTest() {
     EchoCanceller3 aec3(
-        sample_rate_hz_, false,
+        EchoCanceller3Config(), sample_rate_hz_, false,
         std::unique_ptr<BlockProcessor>(
             new RenderTransportVerificationProcessor(num_bands_)));
 
@@ -249,7 +249,7 @@
         break;
     }
 
-    EchoCanceller3 aec3(sample_rate_hz_, false,
+    EchoCanceller3 aec3(EchoCanceller3Config(), sample_rate_hz_, false,
                         std::move(block_processor_mock));
 
     for (size_t frame_index = 0; frame_index < kNumFramesToProcess;
@@ -331,7 +331,7 @@
       } break;
     }
 
-    EchoCanceller3 aec3(sample_rate_hz_, false,
+    EchoCanceller3 aec3(EchoCanceller3Config(), sample_rate_hz_, false,
                         std::move(block_processor_mock));
 
     for (size_t frame_index = 0; frame_index < kNumFramesToProcess;
@@ -420,7 +420,7 @@
       } break;
     }
 
-    EchoCanceller3 aec3(sample_rate_hz_, false,
+    EchoCanceller3 aec3(EchoCanceller3Config(), sample_rate_hz_, false,
                         std::move(block_processor_mock));
     for (size_t frame_index = 0; frame_index < kNumFramesToProcess;
          ++frame_index) {
@@ -458,12 +458,13 @@
   // This test verifies that the swapqueue is able to handle jitter in the
   // capture and render API calls.
   void RunRenderSwapQueueVerificationTest() {
+    const EchoCanceller3Config config;
     EchoCanceller3 aec3(
-        sample_rate_hz_, false,
+        config, sample_rate_hz_, false,
         std::unique_ptr<BlockProcessor>(
             new RenderTransportVerificationProcessor(num_bands_)));
 
-    for (size_t frame_index = 0; frame_index < kRenderTransferQueueSize;
+    for (size_t frame_index = 0; frame_index < kRenderTransferQueueSizeFrames;
          ++frame_index) {
       if (sample_rate_hz_ > 16000) {
         render_buffer_.SplitIntoFrequencyBands();
@@ -478,7 +479,7 @@
       aec3.AnalyzeRender(&render_buffer_);
     }
 
-    for (size_t frame_index = 0; frame_index < kRenderTransferQueueSize;
+    for (size_t frame_index = 0; frame_index < kRenderTransferQueueSizeFrames;
          ++frame_index) {
       aec3.AnalyzeCapture(&capture_buffer_);
       if (sample_rate_hz_ > 16000) {
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 2dbdb1c..e51287b 100644
--- a/modules/audio_processing/aec3/echo_path_delay_estimator_unittest.cc
+++ b/modules/audio_processing/aec3/echo_path_delay_estimator_unittest.cc
@@ -16,6 +16,7 @@
 
 #include "modules/audio_processing/aec3/aec3_common.h"
 #include "modules/audio_processing/aec3/render_delay_buffer.h"
+#include "modules/audio_processing/include/audio_processing.h"
 #include "modules/audio_processing/logging/apm_data_dumper.h"
 #include "modules/audio_processing/test/echo_canceller_test_tools.h"
 #include "rtc_base/random.h"
@@ -24,9 +25,10 @@
 namespace webrtc {
 namespace {
 
-std::string ProduceDebugText(size_t delay) {
+std::string ProduceDebugText(size_t delay, size_t down_sampling_factor) {
   std::ostringstream ss;
   ss << "Delay: " << delay;
+  ss << ", Down sampling factor: " << down_sampling_factor;
   return ss.str();
 }
 
@@ -37,12 +39,7 @@
   ApmDataDumper data_dumper(0);
   EchoCanceller3Config config;
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(
-          3, config.delay.down_sampling_factor,
-          GetDownSampledBufferSize(config.delay.down_sampling_factor,
-                                   config.delay.num_filters),
-          GetRenderDelayBufferSize(config.delay.down_sampling_factor,
-                                   config.delay.num_filters)));
+      RenderDelayBuffer::Create(config, 3));
   EchoPathDelayEstimator estimator(&data_dumper, config);
   std::vector<std::vector<float>> render(3, std::vector<float>(kBlockSize));
   std::vector<float> capture(kBlockSize);
@@ -66,14 +63,22 @@
     config.delay.down_sampling_factor = down_sampling_factor;
     config.delay.num_filters = 10;
     for (size_t delay_samples : {30, 64, 150, 200, 800, 4000}) {
-      SCOPED_TRACE(ProduceDebugText(delay_samples));
+      SCOPED_TRACE(ProduceDebugText(delay_samples, down_sampling_factor));
+
+      config.delay.min_echo_path_delay_blocks = 0;
+      while ((config.delay.min_echo_path_delay_blocks + 1) * kBlockSize <
+                 delay_samples &&
+             config.delay.min_echo_path_delay_blocks + 1 <= 5) {
+        ++config.delay.min_echo_path_delay_blocks;
+      }
+
+      const int delay_estimate_offset =
+          std::max<int>(std::min(config.delay.api_call_jitter_blocks,
+                                 config.delay.min_echo_path_delay_blocks) -
+                            1,
+                        0);
       std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-          RenderDelayBuffer::Create(
-              3, config.delay.down_sampling_factor,
-              GetDownSampledBufferSize(config.delay.down_sampling_factor,
-                                       config.delay.num_filters),
-              GetRenderDelayBufferSize(config.delay.down_sampling_factor,
-                                       config.delay.num_filters)));
+          RenderDelayBuffer::Create(config, 3));
       DelayBuffer<float> signal_delay_buffer(delay_samples);
       EchoPathDelayEstimator estimator(&data_dumper, config);
 
@@ -82,21 +87,29 @@
         RandomizeSampleVector(&random_generator, render[0]);
         signal_delay_buffer.Delay(render[0], capture);
         render_delay_buffer->Insert(render);
-        render_delay_buffer->UpdateBuffers();
+
+        if (k == 0) {
+          render_delay_buffer->Reset();
+        }
+
+        render_delay_buffer->PrepareCaptureCall();
         estimated_delay_samples = estimator.EstimateDelay(
             render_delay_buffer->GetDownsampledRenderBuffer(), capture);
       }
+
       if (estimated_delay_samples) {
         // Due to the internal down-sampling done inside the delay estimator
         // the estimated delay cannot be expected to be exact to the true delay.
-        EXPECT_NEAR(delay_samples, *estimated_delay_samples,
-                    config.delay.down_sampling_factor);
+        EXPECT_NEAR(
+            delay_samples,
+            *estimated_delay_samples + delay_estimate_offset * kBlockSize,
+            config.delay.down_sampling_factor);
       } else {
         ADD_FAILURE();
       }
-    }
   }
 }
+}
 
 // Verifies that the delay estimator does not produce delay estimates too
 // quickly.
@@ -107,19 +120,14 @@
   std::vector<float> capture(kBlockSize);
   ApmDataDumper data_dumper(0);
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(
-          3, config.delay.down_sampling_factor,
-          GetDownSampledBufferSize(config.delay.down_sampling_factor,
-                                   config.delay.num_filters),
-          GetRenderDelayBufferSize(config.delay.down_sampling_factor,
-                                   config.delay.num_filters)));
+      RenderDelayBuffer::Create(config, 3));
 
   EchoPathDelayEstimator estimator(&data_dumper, config);
   for (size_t k = 0; k < 19; ++k) {
     RandomizeSampleVector(&random_generator, render[0]);
     std::copy(render[0].begin(), render[0].end(), capture.begin());
     render_delay_buffer->Insert(render);
-    render_delay_buffer->UpdateBuffers();
+    render_delay_buffer->PrepareCaptureCall();
     EXPECT_FALSE(estimator.EstimateDelay(
         render_delay_buffer->GetDownsampledRenderBuffer(), capture));
   }
@@ -135,12 +143,7 @@
   ApmDataDumper data_dumper(0);
   EchoPathDelayEstimator estimator(&data_dumper, config);
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(
-          3, config.delay.down_sampling_factor,
-          GetDownSampledBufferSize(config.delay.down_sampling_factor,
-                                   config.delay.num_filters),
-          GetRenderDelayBufferSize(config.delay.down_sampling_factor,
-                                   config.delay.num_filters)));
+      RenderDelayBuffer::Create(EchoCanceller3Config(), 3));
   for (size_t k = 0; k < 100; ++k) {
     RandomizeSampleVector(&random_generator, render[0]);
     for (auto& render_k : render[0]) {
@@ -148,7 +151,7 @@
     }
     std::copy(render[0].begin(), render[0].end(), capture.begin());
     render_delay_buffer->Insert(render);
-    render_delay_buffer->UpdateBuffers();
+    render_delay_buffer->PrepareCaptureCall();
     EXPECT_FALSE(estimator.EstimateDelay(
         render_delay_buffer->GetDownsampledRenderBuffer(), capture));
   }
@@ -164,17 +167,12 @@
   ApmDataDumper data_dumper(0);
   EchoPathDelayEstimator estimator(&data_dumper, config);
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(
-          3, config.delay.down_sampling_factor,
-          GetDownSampledBufferSize(config.delay.down_sampling_factor,
-                                   config.delay.num_filters),
-          GetRenderDelayBufferSize(config.delay.down_sampling_factor,
-                                   config.delay.num_filters)));
+      RenderDelayBuffer::Create(config, 3));
   for (size_t k = 0; k < 100; ++k) {
     RandomizeSampleVector(&random_generator, render[0]);
     RandomizeSampleVector(&random_generator, capture);
     render_delay_buffer->Insert(render);
-    render_delay_buffer->UpdateBuffers();
+    render_delay_buffer->PrepareCaptureCall();
     EXPECT_FALSE(estimator.EstimateDelay(
         render_delay_buffer->GetDownsampledRenderBuffer(), capture));
   }
@@ -190,12 +188,7 @@
   EchoCanceller3Config config;
   EchoPathDelayEstimator estimator(&data_dumper, config);
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(
-          3, config.delay.down_sampling_factor,
-          GetDownSampledBufferSize(config.delay.down_sampling_factor,
-                                   config.delay.num_filters),
-          GetRenderDelayBufferSize(config.delay.down_sampling_factor,
-                                   config.delay.num_filters)));
+      RenderDelayBuffer::Create(config, 3));
   std::vector<float> capture(kBlockSize);
   EXPECT_DEATH(estimator.EstimateDelay(
                    render_delay_buffer->GetDownsampledRenderBuffer(), capture),
@@ -210,12 +203,7 @@
   EchoCanceller3Config config;
   EchoPathDelayEstimator estimator(&data_dumper, config);
   std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-      RenderDelayBuffer::Create(
-          3, config.delay.down_sampling_factor,
-          GetDownSampledBufferSize(config.delay.down_sampling_factor,
-                                   config.delay.num_filters),
-          GetRenderDelayBufferSize(config.delay.down_sampling_factor,
-                                   config.delay.num_filters)));
+      RenderDelayBuffer::Create(config, 3));
   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_path_variability.cc b/modules/audio_processing/aec3/echo_path_variability.cc
index f63a830..0ae9cff 100644
--- a/modules/audio_processing/aec3/echo_path_variability.cc
+++ b/modules/audio_processing/aec3/echo_path_variability.cc
@@ -12,7 +12,11 @@
 
 namespace webrtc {
 
-EchoPathVariability::EchoPathVariability(bool gain_change, bool delay_change)
-    : gain_change(gain_change), delay_change(delay_change) {}
+EchoPathVariability::EchoPathVariability(bool gain_change,
+                                         DelayAdjustment delay_change,
+                                         bool clock_drift)
+    : gain_change(gain_change),
+      delay_change(delay_change),
+      clock_drift(clock_drift) {}
 
 }  // namespace webrtc
diff --git a/modules/audio_processing/aec3/echo_path_variability.h b/modules/audio_processing/aec3/echo_path_variability.h
index 55915d5..adf0d7a 100644
--- a/modules/audio_processing/aec3/echo_path_variability.h
+++ b/modules/audio_processing/aec3/echo_path_variability.h
@@ -14,11 +14,24 @@
 namespace webrtc {
 
 struct EchoPathVariability {
-  EchoPathVariability(bool gain_change, bool delay_change);
+  enum class DelayAdjustment {
+    kNone,
+    kBufferReadjustment,
+    kBufferFlush,
+    kDelayReset,
+    kNewDetectedDelay
+  };
 
-  bool AudioPathChanged() const { return gain_change || delay_change; }
+  EchoPathVariability(bool gain_change,
+                      DelayAdjustment delay_change,
+                      bool clock_drift);
+
+  bool AudioPathChanged() const {
+    return gain_change || delay_change != DelayAdjustment::kNone;
+  }
   bool gain_change;
-  bool delay_change;
+  DelayAdjustment delay_change;
+  bool clock_drift;
 };
 
 }  // namespace webrtc
diff --git a/modules/audio_processing/aec3/echo_path_variability_unittest.cc b/modules/audio_processing/aec3/echo_path_variability_unittest.cc
index 9a1df78..b1795ed 100644
--- a/modules/audio_processing/aec3/echo_path_variability_unittest.cc
+++ b/modules/audio_processing/aec3/echo_path_variability_unittest.cc
@@ -15,25 +15,35 @@
 
 TEST(EchoPathVariability, CorrectBehavior) {
   // Test correct passing and reporting of the gain change information.
-  EchoPathVariability v(true, true);
+  EchoPathVariability v(
+      true, EchoPathVariability::DelayAdjustment::kNewDetectedDelay, false);
   EXPECT_TRUE(v.gain_change);
-  EXPECT_TRUE(v.delay_change);
+  EXPECT_TRUE(v.delay_change ==
+              EchoPathVariability::DelayAdjustment::kNewDetectedDelay);
   EXPECT_TRUE(v.AudioPathChanged());
+  EXPECT_FALSE(v.clock_drift);
 
-  v = EchoPathVariability(true, false);
+  v = EchoPathVariability(true, EchoPathVariability::DelayAdjustment::kNone,
+                          false);
   EXPECT_TRUE(v.gain_change);
-  EXPECT_FALSE(v.delay_change);
+  EXPECT_TRUE(v.delay_change == EchoPathVariability::DelayAdjustment::kNone);
   EXPECT_TRUE(v.AudioPathChanged());
+  EXPECT_FALSE(v.clock_drift);
 
-  v = EchoPathVariability(false, true);
+  v = EchoPathVariability(
+      false, EchoPathVariability::DelayAdjustment::kNewDetectedDelay, false);
   EXPECT_FALSE(v.gain_change);
-  EXPECT_TRUE(v.delay_change);
+  EXPECT_TRUE(v.delay_change ==
+              EchoPathVariability::DelayAdjustment::kNewDetectedDelay);
   EXPECT_TRUE(v.AudioPathChanged());
+  EXPECT_FALSE(v.clock_drift);
 
-  v = EchoPathVariability(false, false);
+  v = EchoPathVariability(false, EchoPathVariability::DelayAdjustment::kNone,
+                          false);
   EXPECT_FALSE(v.gain_change);
-  EXPECT_FALSE(v.delay_change);
+  EXPECT_TRUE(v.delay_change == EchoPathVariability::DelayAdjustment::kNone);
   EXPECT_FALSE(v.AudioPathChanged());
+  EXPECT_FALSE(v.clock_drift);
 }
 
 }  // namespace webrtc
diff --git a/modules/audio_processing/aec3/echo_remover.cc b/modules/audio_processing/aec3/echo_remover.cc
index 9adcec5..fc72207 100644
--- a/modules/audio_processing/aec3/echo_remover.cc
+++ b/modules/audio_processing/aec3/echo_remover.cc
@@ -174,7 +174,7 @@
   // Compute spectra.
   fft_.ZeroPaddedFft(y0, &Y);
   LinearEchoPower(E_main, Y, &S2_linear);
-  Y.Spectrum(optimization_, &Y2);
+  Y.Spectrum(optimization_, Y2);
 
   // Update the AEC state information.
   aec_state_.Update(subtractor_.FilterFrequencyResponse(),
diff --git a/modules/audio_processing/aec3/echo_remover_metrics_unittest.cc b/modules/audio_processing/aec3/echo_remover_metrics_unittest.cc
index e209710..2b30a74 100644
--- a/modules/audio_processing/aec3/echo_remover_metrics_unittest.cc
+++ b/modules/audio_processing/aec3/echo_remover_metrics_unittest.cc
@@ -65,7 +65,7 @@
   Aec3Fft fft;
   x.fill(1000.f);
   fft.ZeroPaddedFft(x, &X);
-  X.Spectrum(Aec3Optimization::kNone, &X2);
+  X.Spectrum(Aec3Optimization::kNone, X2);
 
   float offset = -10.f * log10(32768.f * 32768.f);
   EXPECT_NEAR(offset, -90.3f, 0.1f);
diff --git a/modules/audio_processing/aec3/echo_remover_unittest.cc b/modules/audio_processing/aec3/echo_remover_unittest.cc
index 24b50e8..f035f4f 100644
--- a/modules/audio_processing/aec3/echo_remover_unittest.cc
+++ b/modules/audio_processing/aec3/echo_remover_unittest.cc
@@ -39,9 +39,6 @@
   return ss.str();
 }
 
-constexpr size_t kDownSamplingFactor = 4;
-constexpr size_t kNumMatchedFilters = 4;
-
 }  // namespace
 
 // Verifies the basic API call sequence
@@ -51,22 +48,23 @@
     std::unique_ptr<EchoRemover> remover(
         EchoRemover::Create(EchoCanceller3Config(), rate));
     std::unique_ptr<RenderDelayBuffer> render_buffer(RenderDelayBuffer::Create(
-        NumBandsForRate(rate), kDownSamplingFactor,
-        GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters),
-        GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters)));
+        EchoCanceller3Config(), NumBandsForRate(rate)));
 
     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 ? true : false);
+      EchoPathVariability echo_path_variability(
+          k % 3 == 0 ? true : false,
+          k % 5 == 0 ? EchoPathVariability::DelayAdjustment::kNewDetectedDelay
+                     : EchoPathVariability::DelayAdjustment::kNone,
+          false);
       rtc::Optional<size_t> echo_path_delay_samples =
           (k % 6 == 0 ? rtc::Optional<size_t>(k * 10)
                       : rtc::nullopt);
       render_buffer->Insert(render);
-      render_buffer->UpdateBuffers();
+      render_buffer->PrepareCaptureCall();
       remover->ProcessCapture(echo_path_delay_samples, echo_path_variability,
                               k % 2 == 0 ? true : false,
                               render_buffer->GetRenderBuffer(), &capture);
@@ -92,12 +90,11 @@
     std::unique_ptr<EchoRemover> remover(
         EchoRemover::Create(EchoCanceller3Config(), rate));
     std::unique_ptr<RenderDelayBuffer> render_buffer(RenderDelayBuffer::Create(
-        NumBandsForRate(rate), kDownSamplingFactor,
-        GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters),
-        GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters)));
+        EchoCanceller3Config(), NumBandsForRate(rate)));
     std::vector<std::vector<float>> capture(
         NumBandsForRate(rate), std::vector<float>(kBlockSize - 1, 0.f));
-    EchoPathVariability echo_path_variability(false, false);
+    EchoPathVariability echo_path_variability(
+        false, EchoPathVariability::DelayAdjustment::kNone, false);
     rtc::Optional<size_t> echo_path_delay_samples;
     EXPECT_DEATH(remover->ProcessCapture(
                      echo_path_delay_samples, echo_path_variability, false,
@@ -115,13 +112,12 @@
     std::unique_ptr<EchoRemover> remover(
         EchoRemover::Create(EchoCanceller3Config(), rate));
     std::unique_ptr<RenderDelayBuffer> render_buffer(RenderDelayBuffer::Create(
-        NumBandsForRate(rate), kDownSamplingFactor,
-        GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters),
-        GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters)));
+        EchoCanceller3Config(), NumBandsForRate(rate)));
     std::vector<std::vector<float>> capture(
         NumBandsForRate(rate == 48000 ? 16000 : rate + 16000),
         std::vector<float>(kBlockSize, 0.f));
-    EchoPathVariability echo_path_variability(false, false);
+    EchoPathVariability echo_path_variability(
+        false, EchoPathVariability::DelayAdjustment::kNone, false);
     rtc::Optional<size_t> echo_path_delay_samples;
     EXPECT_DEATH(remover->ProcessCapture(
                      echo_path_delay_samples, echo_path_variability, false,
@@ -134,11 +130,10 @@
 TEST(EchoRemover, NullCapture) {
   std::unique_ptr<EchoRemover> remover(
       EchoRemover::Create(EchoCanceller3Config(), 8000));
-  std::unique_ptr<RenderDelayBuffer> render_buffer(RenderDelayBuffer::Create(
-      3, kDownSamplingFactor,
-      GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters),
-      GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters)));
-  EchoPathVariability echo_path_variability(false, false);
+  std::unique_ptr<RenderDelayBuffer> render_buffer(
+      RenderDelayBuffer::Create(EchoCanceller3Config(), 3));
+  EchoPathVariability echo_path_variability(
+      false, EchoPathVariability::DelayAdjustment::kNone, false);
   rtc::Optional<size_t> echo_path_delay_samples;
   EXPECT_DEATH(
       remover->ProcessCapture(echo_path_delay_samples, echo_path_variability,
@@ -158,17 +153,18 @@
                                       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, false);
+    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));
-      std::unique_ptr<EchoRemover> remover(
-          EchoRemover::Create(EchoCanceller3Config(), rate));
+      EchoCanceller3Config config;
+      config.delay.min_echo_path_delay_blocks = 0;
+      std::unique_ptr<EchoRemover> remover(EchoRemover::Create(config, rate));
       std::unique_ptr<RenderDelayBuffer> render_buffer(
-          RenderDelayBuffer::Create(
-              NumBandsForRate(rate), kDownSamplingFactor,
-              GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters),
-              GetRenderDelayBufferSize(kDownSamplingFactor,
-                                       kNumMatchedFilters)));
+          RenderDelayBuffer::Create(config, NumBandsForRate(rate)));
+      if (delay_samples != render_buffer->Delay() * kBlockSize) {
+        render_buffer->SetDelay(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));
@@ -196,7 +192,7 @@
         }
 
         render_buffer->Insert(x);
-        render_buffer->UpdateBuffers();
+        render_buffer->PrepareCaptureCall();
 
         remover->ProcessCapture(delay_samples, echo_path_variability, false,
                                 render_buffer->GetRenderBuffer(), &y);
diff --git a/modules/audio_processing/aec3/erl_estimator.cc b/modules/audio_processing/aec3/erl_estimator.cc
index 3f12ba4..b2849db 100644
--- a/modules/audio_processing/aec3/erl_estimator.cc
+++ b/modules/audio_processing/aec3/erl_estimator.cc
@@ -31,9 +31,10 @@
 
 ErlEstimator::~ErlEstimator() = default;
 
-void ErlEstimator::Update(
-    const std::array<float, kFftLengthBy2Plus1>& render_spectrum,
-    const std::array<float, kFftLengthBy2Plus1>& capture_spectrum) {
+void ErlEstimator::Update(rtc::ArrayView<const float> render_spectrum,
+                          rtc::ArrayView<const float> capture_spectrum) {
+  RTC_DCHECK_EQ(kFftLengthBy2Plus1, render_spectrum.size());
+  RTC_DCHECK_EQ(kFftLengthBy2Plus1, capture_spectrum.size());
   const auto& X2 = render_spectrum;
   const auto& Y2 = capture_spectrum;
 
diff --git a/modules/audio_processing/aec3/erl_estimator.h b/modules/audio_processing/aec3/erl_estimator.h
index 24b3f4b..215c22e 100644
--- a/modules/audio_processing/aec3/erl_estimator.h
+++ b/modules/audio_processing/aec3/erl_estimator.h
@@ -13,6 +13,7 @@
 
 #include <array>
 
+#include "api/array_view.h"
 #include "modules/audio_processing/aec3/aec3_common.h"
 #include "rtc_base/constructormagic.h"
 
@@ -25,8 +26,8 @@
   ~ErlEstimator();
 
   // Updates the ERL estimate.
-  void Update(const std::array<float, kFftLengthBy2Plus1>& render_spectrum,
-              const std::array<float, kFftLengthBy2Plus1>& capture_spectrum);
+  void Update(rtc::ArrayView<const float> render_spectrum,
+              rtc::ArrayView<const float> capture_spectrum);
 
   // Returns the most recent ERL estimate.
   const std::array<float, kFftLengthBy2Plus1>& Erl() const { return erl_; }
diff --git a/modules/audio_processing/aec3/erle_estimator.cc b/modules/audio_processing/aec3/erle_estimator.cc
index 385e6dd..0e4cbe1 100644
--- a/modules/audio_processing/aec3/erle_estimator.cc
+++ b/modules/audio_processing/aec3/erle_estimator.cc
@@ -31,10 +31,12 @@
 
 ErleEstimator::~ErleEstimator() = default;
 
-void ErleEstimator::Update(
-    const std::array<float, kFftLengthBy2Plus1>& render_spectrum,
-    const std::array<float, kFftLengthBy2Plus1>& capture_spectrum,
-    const std::array<float, kFftLengthBy2Plus1>& subtractor_spectrum) {
+void ErleEstimator::Update(rtc::ArrayView<const float> render_spectrum,
+                           rtc::ArrayView<const float> capture_spectrum,
+                           rtc::ArrayView<const float> subtractor_spectrum) {
+  RTC_DCHECK_EQ(kFftLengthBy2Plus1, render_spectrum.size());
+  RTC_DCHECK_EQ(kFftLengthBy2Plus1, capture_spectrum.size());
+  RTC_DCHECK_EQ(kFftLengthBy2Plus1, subtractor_spectrum.size());
   const auto& X2 = render_spectrum;
   const auto& Y2 = capture_spectrum;
   const auto& E2 = subtractor_spectrum;
diff --git a/modules/audio_processing/aec3/erle_estimator.h b/modules/audio_processing/aec3/erle_estimator.h
index d88b11b..cb9fce6 100644
--- a/modules/audio_processing/aec3/erle_estimator.h
+++ b/modules/audio_processing/aec3/erle_estimator.h
@@ -13,6 +13,7 @@
 
 #include <array>
 
+#include "api/array_view.h"
 #include "modules/audio_processing/aec3/aec3_common.h"
 #include "rtc_base/constructormagic.h"
 
@@ -25,9 +26,9 @@
   ~ErleEstimator();
 
   // Updates the ERLE estimate.
-  void Update(const std::array<float, kFftLengthBy2Plus1>& render_spectrum,
-              const std::array<float, kFftLengthBy2Plus1>& capture_spectrum,
-              const std::array<float, kFftLengthBy2Plus1>& subtractor_spectrum);
+  void Update(rtc::ArrayView<const float> render_spectrum,
+              rtc::ArrayView<const float> capture_spectrum,
+              rtc::ArrayView<const float> subtractor_spectrum);
 
   // Returns the most recent ERLE estimate.
   const std::array<float, kFftLengthBy2Plus1>& Erle() const { return erle_; }
diff --git a/modules/audio_processing/aec3/fft_buffer.cc b/modules/audio_processing/aec3/fft_buffer.cc
new file mode 100644
index 0000000..133d77b
--- /dev/null
+++ b/modules/audio_processing/aec3/fft_buffer.cc
@@ -0,0 +1,23 @@
+/*
+ *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "modules/audio_processing/aec3/fft_buffer.h"
+
+namespace webrtc {
+
+FftBuffer::FftBuffer(size_t size) : buffer(size) {
+  for (auto& b : buffer) {
+    b.Clear();
+  }
+}
+
+FftBuffer::~FftBuffer() = default;
+
+}  // namespace webrtc
diff --git a/modules/audio_processing/aec3/fft_buffer.h b/modules/audio_processing/aec3/fft_buffer.h
new file mode 100644
index 0000000..42fbb4a
--- /dev/null
+++ b/modules/audio_processing/aec3/fft_buffer.h
@@ -0,0 +1,54 @@
+/*
+ *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef MODULES_AUDIO_PROCESSING_AEC3_FFT_BUFFER_H_
+#define MODULES_AUDIO_PROCESSING_AEC3_FFT_BUFFER_H_
+
+#include <vector>
+
+#include "modules/audio_processing/aec3/fft_data.h"
+#include "rtc_base/checks.h"
+
+namespace webrtc {
+
+// Struct for bundling a circular buffer of FftData objects together with the
+// read and write indices.
+struct FftBuffer {
+  explicit FftBuffer(size_t size);
+  ~FftBuffer();
+
+  size_t IncIndex(size_t index) {
+    return index < buffer.size() - 1 ? index + 1 : 0;
+  }
+
+  size_t DecIndex(size_t index) {
+    return index > 0 ? index - 1 : buffer.size() - 1;
+  }
+
+  size_t OffsetIndex(size_t index, int offset) {
+    RTC_DCHECK_GE(buffer.size(), offset);
+    return (buffer.size() + index + offset) % buffer.size();
+  }
+
+  void UpdateWriteIndex(int offset) { write = OffsetIndex(write, offset); }
+  void IncWriteIndex() { write = IncIndex(write); }
+  void DecWriteIndex() { write = DecIndex(write); }
+  void UpdateReadIndex(int offset) { read = OffsetIndex(read, offset); }
+  void IncReadIndex() { read = IncIndex(read); }
+  void DecReadIndex() { read = DecIndex(read); }
+
+  std::vector<FftData> buffer;
+  size_t write = 0;
+  size_t read = 0;
+};
+
+}  // namespace webrtc
+
+#endif  // MODULES_AUDIO_PROCESSING_AEC3_FFT_BUFFER_H_
diff --git a/modules/audio_processing/aec3/fft_data.h b/modules/audio_processing/aec3/fft_data.h
index a5c51bf..59511b5 100644
--- a/modules/audio_processing/aec3/fft_data.h
+++ b/modules/audio_processing/aec3/fft_data.h
@@ -40,8 +40,8 @@
 
   // Computes the power spectrum of the data.
   void Spectrum(Aec3Optimization optimization,
-                std::array<float, kFftLengthBy2Plus1>* power_spectrum) const {
-    RTC_DCHECK(power_spectrum);
+                rtc::ArrayView<float> power_spectrum) const {
+    RTC_DCHECK_EQ(kFftLengthBy2Plus1, power_spectrum.size());
     switch (optimization) {
 #if defined(WEBRTC_ARCH_X86_FAMILY)
       case Aec3Optimization::kSse2: {
@@ -53,16 +53,14 @@
           const __m128 ii = _mm_mul_ps(i, i);
           const __m128 rr = _mm_mul_ps(r, r);
           const __m128 rrii = _mm_add_ps(rr, ii);
-          _mm_storeu_ps(&(*power_spectrum)[k], rrii);
+          _mm_storeu_ps(&power_spectrum[k], rrii);
         }
-        (*power_spectrum)[kFftLengthBy2] =
-            re[kFftLengthBy2] * re[kFftLengthBy2] +
-            im[kFftLengthBy2] * im[kFftLengthBy2];
+        power_spectrum[kFftLengthBy2] = re[kFftLengthBy2] * re[kFftLengthBy2] +
+                                        im[kFftLengthBy2] * im[kFftLengthBy2];
       } break;
 #endif
       default:
-        std::transform(re.begin(), re.end(), im.begin(),
-                       power_spectrum->begin(),
+        std::transform(re.begin(), re.end(), im.begin(), power_spectrum.begin(),
                        [](float a, float b) { return a * a + b * b; });
     }
   }
diff --git a/modules/audio_processing/aec3/fft_data_unittest.cc b/modules/audio_processing/aec3/fft_data_unittest.cc
index d969744..8fc5ca7 100644
--- a/modules/audio_processing/aec3/fft_data_unittest.cc
+++ b/modules/audio_processing/aec3/fft_data_unittest.cc
@@ -34,8 +34,8 @@
 
     std::array<float, kFftLengthBy2Plus1> spectrum;
     std::array<float, kFftLengthBy2Plus1> spectrum_sse2;
-    x.Spectrum(Aec3Optimization::kNone, &spectrum);
-    x.Spectrum(Aec3Optimization::kSse2, &spectrum_sse2);
+    x.Spectrum(Aec3Optimization::kNone, spectrum);
+    x.Spectrum(Aec3Optimization::kSse2, spectrum_sse2);
     EXPECT_EQ(spectrum, spectrum_sse2);
   }
 }
@@ -102,7 +102,7 @@
   }
 
   std::array<float, kFftLengthBy2Plus1> spectrum;
-  x.Spectrum(Aec3Optimization::kNone, &spectrum);
+  x.Spectrum(Aec3Optimization::kNone, spectrum);
 
   EXPECT_EQ(x.re[0] * x.re[0], spectrum[0]);
   EXPECT_EQ(x.re[spectrum.size() - 1] * x.re[spectrum.size() - 1],
diff --git a/modules/audio_processing/aec3/main_filter_update_gain.cc b/modules/audio_processing/aec3/main_filter_update_gain.cc
index 1dd2a20..45253cd 100644
--- a/modules/audio_processing/aec3/main_filter_update_gain.cc
+++ b/modules/audio_processing/aec3/main_filter_update_gain.cc
@@ -37,7 +37,9 @@
 
 MainFilterUpdateGain::~MainFilterUpdateGain() {}
 
-void MainFilterUpdateGain::HandleEchoPathChange() {
+void MainFilterUpdateGain::HandleEchoPathChange(
+    const EchoPathVariability& echo_path_variability) {
+  // TODO(peah): Add even-specific behavior.
   H_error_.fill(kHErrorInitial);
   poor_excitation_counter_ = kPoorExcitationCounterInitial;
   call_counter_ = 0;
@@ -57,7 +59,7 @@
   const auto& E2_shadow = subtractor_output.E2_shadow;
   FftData* G = gain_fft;
   const size_t size_partitions = filter.SizePartitions();
-  const auto& X2 = render_buffer.SpectralSum(size_partitions);
+  auto X2 = render_buffer.SpectralSum(size_partitions);
   const auto& erl = filter.Erl();
 
   ++call_counter_;
diff --git a/modules/audio_processing/aec3/main_filter_update_gain.h b/modules/audio_processing/aec3/main_filter_update_gain.h
index 756a5d0..92ec02a 100644
--- a/modules/audio_processing/aec3/main_filter_update_gain.h
+++ b/modules/audio_processing/aec3/main_filter_update_gain.h
@@ -16,7 +16,7 @@
 
 #include "modules/audio_processing/aec3/adaptive_fir_filter.h"
 #include "modules/audio_processing/aec3/aec3_common.h"
-#include "modules/audio_processing/aec3/render_buffer.h"
+#include "modules/audio_processing/aec3/echo_path_variability.h"
 #include "modules/audio_processing/aec3/render_signal_analyzer.h"
 #include "modules/audio_processing/aec3/subtractor_output.h"
 #include "rtc_base/constructormagic.h"
@@ -32,7 +32,7 @@
   ~MainFilterUpdateGain();
 
   // Takes action in the case of a known echo path change.
-  void HandleEchoPathChange();
+  void HandleEchoPathChange(const EchoPathVariability& echo_path_variability);
 
   // Computes the gain.
   void Compute(const RenderBuffer& render_buffer,
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 203731a..b59273d 100644
--- a/modules/audio_processing/aec3/main_filter_update_gain_unittest.cc
+++ b/modules/audio_processing/aec3/main_filter_update_gain_unittest.cc
@@ -16,7 +16,7 @@
 
 #include "modules/audio_processing/aec3/adaptive_fir_filter.h"
 #include "modules/audio_processing/aec3/aec_state.h"
-#include "modules/audio_processing/aec3/render_buffer.h"
+#include "modules/audio_processing/aec3/render_delay_buffer.h"
 #include "modules/audio_processing/aec3/render_signal_analyzer.h"
 #include "modules/audio_processing/aec3/shadow_filter_update_gain.h"
 #include "modules/audio_processing/aec3/subtractor_output.h"
@@ -40,12 +40,9 @@
                          std::array<float, kBlockSize>* y_last_block,
                          FftData* G_last_block) {
   ApmDataDumper data_dumper(42);
-  AdaptiveFirFilter main_filter(9, DetectOptimization(), &data_dumper);
-  AdaptiveFirFilter shadow_filter(9, DetectOptimization(), &data_dumper);
+  AdaptiveFirFilter main_filter(12, DetectOptimization(), &data_dumper);
+  AdaptiveFirFilter shadow_filter(12, DetectOptimization(), &data_dumper);
   Aec3Fft fft;
-  RenderBuffer render_buffer(
-      Aec3Optimization::kNone, 3, main_filter.SizePartitions(),
-      std::vector<size_t>(1, main_filter.SizePartitions()));
   std::array<float, kBlockSize> x_old;
   x_old.fill(0.f);
   ShadowFilterUpdateGain shadow_gain;
@@ -53,7 +50,11 @@
   Random random_generator(42U);
   std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
   std::vector<float> y(kBlockSize, 0.f);
-  AecState aec_state(EchoCanceller3Config{});
+  EchoCanceller3Config config;
+  config.delay.min_echo_path_delay_blocks = 0;
+  std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
+      RenderDelayBuffer::Create(config, 3));
+  AecState aec_state(config);
   RenderSignalAnalyzer render_signal_analyzer;
   std::array<float, kFftLength> s_scratch;
   std::array<float, kBlockSize> s;
@@ -92,11 +93,18 @@
       RandomizeSampleVector(&random_generator, x[0]);
     }
     delay_buffer.Delay(x[0], y);
-    render_buffer.Insert(x);
-    render_signal_analyzer.Update(render_buffer, aec_state.FilterDelay());
+
+    render_delay_buffer->Insert(x);
+    if (k == 0) {
+      render_delay_buffer->Reset();
+    }
+    render_delay_buffer->PrepareCaptureCall();
+
+    render_signal_analyzer.Update(render_delay_buffer->GetRenderBuffer(),
+                                  aec_state.FilterDelay());
 
     // Apply the main filter.
-    main_filter.Filter(render_buffer, &S);
+    main_filter.Filter(render_delay_buffer->GetRenderBuffer(), &S);
     fft.Ifft(S, &s_scratch);
     std::transform(y.begin(), y.end(), s_scratch.begin() + kFftLengthBy2,
                    e_main.begin(),
@@ -109,7 +117,7 @@
     }
 
     // Apply the shadow filter.
-    shadow_filter.Filter(render_buffer, &S);
+    shadow_filter.Filter(render_delay_buffer->GetRenderBuffer(), &S);
     fft.Ifft(S, &s_scratch);
     std::transform(y.begin(), y.end(), s_scratch.begin() + kFftLengthBy2,
                    e_shadow.begin(),
@@ -119,24 +127,28 @@
     fft.ZeroPaddedFft(e_shadow, &E_shadow);
 
     // Compute spectra for future use.
-    E_main.Spectrum(Aec3Optimization::kNone, &output.E2_main);
-    E_shadow.Spectrum(Aec3Optimization::kNone, &output.E2_shadow);
+    E_main.Spectrum(Aec3Optimization::kNone, output.E2_main);
+    E_shadow.Spectrum(Aec3Optimization::kNone, output.E2_shadow);
 
     // Adapt the shadow filter.
-    shadow_gain.Compute(render_buffer, render_signal_analyzer, E_shadow,
+    shadow_gain.Compute(render_delay_buffer->GetRenderBuffer(),
+                        render_signal_analyzer, E_shadow,
                         shadow_filter.SizePartitions(), saturation, &G);
-    shadow_filter.Adapt(render_buffer, G);
+    shadow_filter.Adapt(render_delay_buffer->GetRenderBuffer(), G);
 
     // Adapt the main filter
-    main_gain.Compute(render_buffer, render_signal_analyzer, output,
-                      main_filter, saturation, &G);
-    main_filter.Adapt(render_buffer, G);
+    main_gain.Compute(render_delay_buffer->GetRenderBuffer(),
+                      render_signal_analyzer, output, main_filter, saturation,
+                      &G);
+    main_filter.Adapt(render_delay_buffer->GetRenderBuffer(), G);
 
     // Update the delay.
-    aec_state.HandleEchoPathChange(EchoPathVariability(false, false));
+    aec_state.HandleEchoPathChange(EchoPathVariability(
+        false, EchoPathVariability::DelayAdjustment::kNone, false));
     aec_state.Update(main_filter.FilterFrequencyResponse(),
                      main_filter.FilterImpulseResponse(), true, rtc::nullopt,
-                     render_buffer, E2_main, Y2, x[0], s, false);
+                     render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0],
+                     s, false);
   }
 
   std::copy(e_main.begin(), e_main.end(), e_last_block->begin());
@@ -158,16 +170,16 @@
 // Verifies that the check for non-null output gain parameter works.
 TEST(MainFilterUpdateGain, NullDataOutputGain) {
   ApmDataDumper data_dumper(42);
-  AdaptiveFirFilter filter(9, DetectOptimization(), &data_dumper);
-  RenderBuffer render_buffer(Aec3Optimization::kNone, 3,
-                             filter.SizePartitions(),
-                             std::vector<size_t>(1, filter.SizePartitions()));
+  AdaptiveFirFilter filter(kAdaptiveFilterLength, DetectOptimization(),
+                           &data_dumper);
+  std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
+      RenderDelayBuffer::Create(EchoCanceller3Config(), 3));
   RenderSignalAnalyzer analyzer;
   SubtractorOutput output;
   MainFilterUpdateGain gain;
-  EXPECT_DEATH(
-      gain.Compute(render_buffer, analyzer, output, filter, false, nullptr),
-      "");
+  EXPECT_DEATH(gain.Compute(render_delay_buffer->GetRenderBuffer(), analyzer,
+                            output, filter, false, nullptr),
+               "");
 }
 
 #endif
@@ -214,9 +226,9 @@
   RunFilterUpdateTest(300, 65, blocks_with_echo_path_changes,
                       blocks_with_saturation, false, &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.));
@@ -256,8 +268,8 @@
   RunFilterUpdateTest(201, 65, blocks_with_echo_path_changes,
                       blocks_with_saturation, false, &e, &y, &G_b);
 
-  G_a.Spectrum(Aec3Optimization::kNone, &G_a_power);
-  G_b.Spectrum(Aec3Optimization::kNone, &G_b_power);
+  G_a.Spectrum(Aec3Optimization::kNone, G_a_power);
+  G_b.Spectrum(Aec3Optimization::kNone, G_b_power);
 
   EXPECT_LT(std::accumulate(G_a_power.begin(), G_a_power.end(), 0.),
             std::accumulate(G_b_power.begin(), G_b_power.end(), 0.));
@@ -276,13 +288,13 @@
   std::array<float, kFftLengthBy2Plus1> G_a_power;
   std::array<float, kFftLengthBy2Plus1> G_b_power;
 
-  RunFilterUpdateTest(99, 65, blocks_with_echo_path_changes,
-                      blocks_with_saturation, false, &e, &y, &G_a);
   RunFilterUpdateTest(100, 65, blocks_with_echo_path_changes,
+                      blocks_with_saturation, false, &e, &y, &G_a);
+  RunFilterUpdateTest(101, 65, blocks_with_echo_path_changes,
                       blocks_with_saturation, false, &e, &y, &G_b);
 
-  G_a.Spectrum(Aec3Optimization::kNone, &G_a_power);
-  G_b.Spectrum(Aec3Optimization::kNone, &G_b_power);
+  G_a.Spectrum(Aec3Optimization::kNone, G_a_power);
+  G_b.Spectrum(Aec3Optimization::kNone, G_b_power);
 
   EXPECT_LT(std::accumulate(G_a_power.begin(), G_a_power.end(), 0.),
             std::accumulate(G_b_power.begin(), G_b_power.end(), 0.));
diff --git a/modules/audio_processing/aec3/matched_filter.cc b/modules/audio_processing/aec3/matched_filter.cc
index d5e6a28..52bb371 100644
--- a/modules/audio_processing/aec3/matched_filter.cc
+++ b/modules/audio_processing/aec3/matched_filter.cc
@@ -338,7 +338,7 @@
     bool filters_updated = false;
 
     size_t x_start_index =
-        (render_buffer.position + alignment_shift + sub_block_size_ - 1) %
+        (render_buffer.read + alignment_shift + sub_block_size_ - 1) %
         render_buffer.buffer.size();
 
     switch (optimization_) {
diff --git a/modules/audio_processing/aec3/matched_filter_unittest.cc b/modules/audio_processing/aec3/matched_filter_unittest.cc
index 0600419..aec6882 100644
--- a/modules/audio_processing/aec3/matched_filter_unittest.cc
+++ b/modules/audio_processing/aec3/matched_filter_unittest.cc
@@ -151,20 +151,24 @@
       MatchedFilter filter(&data_dumper, DetectOptimization(), sub_block_size,
                            kWindowSizeSubBlocks, kNumMatchedFilters,
                            kAlignmentShiftSubBlocks, 150);
+      EchoCanceller3Config config;
+      config.delay.down_sampling_factor = down_sampling_factor;
+      config.delay.num_filters = kNumMatchedFilters;
+      config.delay.min_echo_path_delay_blocks = 0;
+
       std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-          RenderDelayBuffer::Create(
-              3, down_sampling_factor,
-              GetDownSampledBufferSize(down_sampling_factor,
-                                       kNumMatchedFilters),
-              GetRenderDelayBufferSize(down_sampling_factor,
-                                       kNumMatchedFilters)));
+          RenderDelayBuffer::Create(config, 3));
 
       // Analyze the correlation between render and capture.
-      for (size_t k = 0; k < (300 + delay_samples / sub_block_size); ++k) {
+      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);
         render_delay_buffer->Insert(render);
-        render_delay_buffer->UpdateBuffers();
+        if (k == 0) {
+          render_delay_buffer->Reset();
+        }
+
+        render_delay_buffer->PrepareCaptureCall();
         std::array<float, kBlockSize> downsampled_capture_data;
         rtc::ArrayView<float> downsampled_capture(
             downsampled_capture_data.data(), sub_block_size);
@@ -179,7 +183,7 @@
       // Find which lag estimate should be the most accurate.
       rtc::Optional<size_t> expected_most_accurate_lag_estimate;
       size_t alignment_shift_sub_blocks = 0;
-      for (size_t k = 0; k < kNumMatchedFilters; ++k) {
+      for (size_t k = 0; k < config.delay.num_filters; ++k) {
         if ((alignment_shift_sub_blocks + 3 * kWindowSizeSubBlocks / 4) *
                 sub_block_size >
             delay_samples) {
@@ -236,6 +240,9 @@
 TEST(MatchedFilter, LagNotReliableForUncorrelatedRenderAndCapture) {
   Random random_generator(42U);
   for (auto down_sampling_factor : kDownSamplingFactors) {
+    EchoCanceller3Config config;
+    config.delay.down_sampling_factor = down_sampling_factor;
+    config.delay.num_filters = kNumMatchedFilters;
     const size_t sub_block_size = kBlockSize / down_sampling_factor;
 
     std::vector<std::vector<float>> render(3,
@@ -245,11 +252,7 @@
     std::fill(capture.begin(), capture.end(), 0.f);
     ApmDataDumper data_dumper(0);
     std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-        RenderDelayBuffer::Create(
-            3, down_sampling_factor,
-            GetDownSampledBufferSize(down_sampling_factor, kNumMatchedFilters),
-            GetRenderDelayBufferSize(down_sampling_factor,
-                                     kNumMatchedFilters)));
+        RenderDelayBuffer::Create(config, 3));
     MatchedFilter filter(&data_dumper, DetectOptimization(), sub_block_size,
                          kWindowSizeSubBlocks, kNumMatchedFilters,
                          kAlignmentShiftSubBlocks, 150);
@@ -289,11 +292,7 @@
                          kWindowSizeSubBlocks, kNumMatchedFilters,
                          kAlignmentShiftSubBlocks, 150);
     std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-        RenderDelayBuffer::Create(
-            3, down_sampling_factor,
-            GetDownSampledBufferSize(down_sampling_factor, kNumMatchedFilters),
-            GetRenderDelayBufferSize(down_sampling_factor,
-                                     kNumMatchedFilters)));
+        RenderDelayBuffer::Create(EchoCanceller3Config(), 3));
     Decimator capture_decimator(down_sampling_factor);
 
     // Analyze the correlation between render and capture.
diff --git a/modules/audio_processing/aec3/matrix_buffer.cc b/modules/audio_processing/aec3/matrix_buffer.cc
new file mode 100644
index 0000000..23a5ad4
--- /dev/null
+++ b/modules/audio_processing/aec3/matrix_buffer.cc
@@ -0,0 +1,31 @@
+/*
+ *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "modules/audio_processing/aec3/matrix_buffer.h"
+
+#include "modules/audio_processing/aec3/aec3_common.h"
+
+namespace webrtc {
+
+MatrixBuffer::MatrixBuffer(size_t size, size_t height, size_t width)
+    : size(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);
+    }
+  }
+}
+
+MatrixBuffer::~MatrixBuffer() = default;
+
+}  // namespace webrtc
diff --git a/modules/audio_processing/aec3/matrix_buffer.h b/modules/audio_processing/aec3/matrix_buffer.h
new file mode 100644
index 0000000..67980ba
--- /dev/null
+++ b/modules/audio_processing/aec3/matrix_buffer.h
@@ -0,0 +1,54 @@
+/*
+ *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef MODULES_AUDIO_PROCESSING_AEC3_MATRIX_BUFFER_H_
+#define MODULES_AUDIO_PROCESSING_AEC3_MATRIX_BUFFER_H_
+
+#include <vector>
+
+#include "rtc_base/checks.h"
+
+namespace webrtc {
+
+// Struct for bundling a circular buffer of two dimensional vector objects
+// together with the read and write indices.
+struct MatrixBuffer {
+  MatrixBuffer(size_t size, size_t height, size_t width);
+  ~MatrixBuffer();
+
+  size_t IncIndex(size_t index) {
+    return index < buffer.size() - 1 ? index + 1 : 0;
+  }
+
+  size_t DecIndex(size_t index) {
+    return index > 0 ? index - 1 : buffer.size() - 1;
+  }
+
+  size_t OffsetIndex(size_t index, int offset) {
+    RTC_DCHECK_GE(buffer.size(), offset);
+    return (buffer.size() + index + offset) % buffer.size();
+  }
+
+  void UpdateWriteIndex(int offset) { write = OffsetIndex(write, offset); }
+  void IncWriteIndex() { write = IncIndex(write); }
+  void DecWriteIndex() { write = DecIndex(write); }
+  void UpdateReadIndex(int offset) { read = OffsetIndex(read, offset); }
+  void IncReadIndex() { read = IncIndex(read); }
+  void DecReadIndex() { read = DecIndex(read); }
+
+  size_t size;
+  std::vector<std::vector<std::vector<float>>> buffer;
+  size_t write = 0;
+  size_t read = 0;
+};
+
+}  // namespace webrtc
+
+#endif  // MODULES_AUDIO_PROCESSING_AEC3_MATRIX_BUFFER_H_
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 6b58709..02ac631 100644
--- a/modules/audio_processing/aec3/mock/mock_render_delay_buffer.h
+++ b/modules/audio_processing/aec3/mock/mock_render_delay_buffer.h
@@ -25,10 +25,15 @@
 class MockRenderDelayBuffer : public RenderDelayBuffer {
  public:
   explicit MockRenderDelayBuffer(int sample_rate_hz)
-      : render_buffer_(Aec3Optimization::kNone,
-                       NumBandsForRate(sample_rate_hz),
-                       GetRenderDelayBufferSize(4, 4),
-                       std::vector<size_t>(1, kAdaptiveFilterLength)),
+      : block_buffer_(GetRenderDelayBufferSize(4, 4),
+                      NumBandsForRate(sample_rate_hz),
+                      kBlockSize),
+        spectrum_buffer_(block_buffer_.buffer.size(), kFftLengthBy2Plus1),
+        fft_buffer_(block_buffer_.buffer.size()),
+        render_buffer_(kAdaptiveFilterLength,
+                       &block_buffer_,
+                       &spectrum_buffer_,
+                       &fft_buffer_),
         downsampled_render_buffer_(GetDownSampledBufferSize(4, 4)) {
     ON_CALL(*this, GetRenderBuffer())
         .WillByDefault(
@@ -40,11 +45,14 @@
   virtual ~MockRenderDelayBuffer() = default;
 
   MOCK_METHOD0(Reset, void());
-  MOCK_METHOD1(Insert, bool(const std::vector<std::vector<float>>& block));
-  MOCK_METHOD0(UpdateBuffers, bool());
+  MOCK_METHOD1(Insert,
+               RenderDelayBuffer::BufferingEvent(
+                   const std::vector<std::vector<float>>& block));
+  MOCK_METHOD0(PrepareCaptureCall, RenderDelayBuffer::BufferingEvent());
   MOCK_METHOD1(SetDelay, void(size_t delay));
   MOCK_CONST_METHOD0(Delay, size_t());
   MOCK_CONST_METHOD0(MaxDelay, size_t());
+  MOCK_CONST_METHOD0(MaxApiJitter, size_t());
   MOCK_CONST_METHOD0(IsBlockAvailable, bool());
   MOCK_CONST_METHOD0(GetRenderBuffer, const RenderBuffer&());
   MOCK_CONST_METHOD0(GetDownsampledRenderBuffer,
@@ -55,6 +63,9 @@
   const DownsampledRenderBuffer& FakeGetDownsampledRenderBuffer() const {
     return downsampled_render_buffer_;
   }
+  MatrixBuffer block_buffer_;
+  VectorBuffer spectrum_buffer_;
+  FftBuffer fft_buffer_;
   RenderBuffer render_buffer_;
   DownsampledRenderBuffer downsampled_render_buffer_;
 };
diff --git a/modules/audio_processing/aec3/render_buffer.cc b/modules/audio_processing/aec3/render_buffer.cc
index fa86ea6..60b50ce 100644
--- a/modules/audio_processing/aec3/render_buffer.cc
+++ b/modules/audio_processing/aec3/render_buffer.cc
@@ -17,21 +17,18 @@
 
 namespace webrtc {
 
-RenderBuffer::RenderBuffer(Aec3Optimization optimization,
-                           size_t num_bands,
-                           size_t num_partitions,
-                           const std::vector<size_t> num_ffts_for_spectral_sums)
-    : optimization_(optimization),
-      fft_buffer_(num_partitions),
-      spectrum_buffer_(num_partitions, std::array<float, kFftLengthBy2Plus1>()),
-      spectral_sums_(num_ffts_for_spectral_sums.size(),
-                     std::array<float, kFftLengthBy2Plus1>()),
-      last_block_(num_bands, std::vector<float>(kBlockSize, 0.f)),
-      fft_() {
-  // Current implementation only allows a maximum of one spectral sum lengths.
-  RTC_DCHECK_EQ(1, num_ffts_for_spectral_sums.size());
-  spectral_sums_length_ = num_ffts_for_spectral_sums[0];
-  RTC_DCHECK_GE(fft_buffer_.size(), spectral_sums_length_);
+RenderBuffer::RenderBuffer(size_t num_ffts_for_spectral_sums,
+                           MatrixBuffer* block_buffer,
+                           VectorBuffer* spectrum_buffer,
+                           FftBuffer* fft_buffer)
+    : block_buffer_(block_buffer),
+      spectrum_buffer_(spectrum_buffer),
+      fft_buffer_(fft_buffer),
+      spectral_sums_length_(num_ffts_for_spectral_sums) {
+  RTC_DCHECK(block_buffer_);
+  RTC_DCHECK(spectrum_buffer_);
+  RTC_DCHECK(fft_buffer_);
+  RTC_DCHECK_GE(fft_buffer_->buffer.size(), spectral_sums_length_);
 
   Clear();
 }
@@ -39,56 +36,17 @@
 RenderBuffer::~RenderBuffer() = default;
 
 void RenderBuffer::Clear() {
-  position_ = 0;
-  for (auto& sum : spectral_sums_) {
-    sum.fill(0.f);
-  }
-
-  for (auto& spectrum : spectrum_buffer_) {
-    spectrum.fill(0.f);
-  }
-
-  for (auto& fft : fft_buffer_) {
-    fft.Clear();
-  }
-
-  for (auto& b : last_block_) {
-    std::fill(b.begin(), b.end(), 0.f);
-  }
+  spectral_sums_.fill(0.f);
 }
 
-void RenderBuffer::Insert(const std::vector<std::vector<float>>& block) {
-  // Compute the FFT of the data in the lowest band.
-  FftData X;
-  fft_.PaddedFft(block[0], last_block_[0], &X);
-
-  // Copy the last render frame.
-  RTC_DCHECK_EQ(last_block_.size(), block.size());
-  for (size_t k = 0; k < block.size(); ++k) {
-    RTC_DCHECK_EQ(last_block_[k].size(), block[k].size());
-    std::copy(block[k].begin(), block[k].end(), last_block_[k].begin());
-  }
-
-  // Insert X into the buffer.
-  position_ = position_ > 0 ? position_ - 1 : fft_buffer_.size() - 1;
-  fft_buffer_[position_].Assign(X);
-
-  // Compute and insert the spectrum for the FFT into the spectrum buffer.
-  X.Spectrum(optimization_, &spectrum_buffer_[position_]);
-
-  // Pre-compute and cache the spectral sums.
-  std::copy(spectrum_buffer_[position_].begin(),
-            spectrum_buffer_[position_].end(), spectral_sums_[0].begin());
-  size_t position = (position_ + 1) % fft_buffer_.size();
-  for (size_t j = 1; j < spectral_sums_length_; ++j) {
-    const std::array<float, kFftLengthBy2Plus1>& spectrum =
-        spectrum_buffer_[position];
-
-    for (size_t k = 0; k < spectral_sums_[0].size(); ++k) {
-      spectral_sums_[0][k] += spectrum[k];
+void RenderBuffer::UpdateSpectralSum() {
+  std::fill(spectral_sums_.begin(), spectral_sums_.end(), 0.f);
+  size_t position = spectrum_buffer_->read;
+  for (size_t j = 0; j < spectral_sums_length_; ++j) {
+    for (size_t k = 0; k < spectral_sums_.size(); ++k) {
+      spectral_sums_[k] += spectrum_buffer_->buffer[position][k];
     }
-
-    position = position < (fft_buffer_.size() - 1) ? position + 1 : 0;
+    position = spectrum_buffer_->IncIndex(position);
   }
 }
 
diff --git a/modules/audio_processing/aec3/render_buffer.h b/modules/audio_processing/aec3/render_buffer.h
index 3288ff3..aa132b8 100644
--- a/modules/audio_processing/aec3/render_buffer.h
+++ b/modules/audio_processing/aec3/render_buffer.h
@@ -11,12 +11,14 @@
 #ifndef MODULES_AUDIO_PROCESSING_AEC3_RENDER_BUFFER_H_
 #define MODULES_AUDIO_PROCESSING_AEC3_RENDER_BUFFER_H_
 
+#include <array>
 #include <memory>
-#include <vector>
 
 #include "api/array_view.h"
-#include "modules/audio_processing/aec3/aec3_fft.h"
+#include "modules/audio_processing/aec3/fft_buffer.h"
 #include "modules/audio_processing/aec3/fft_data.h"
+#include "modules/audio_processing/aec3/matrix_buffer.h"
+#include "modules/audio_processing/aec3/vector_buffer.h"
 #include "rtc_base/constructormagic.h"
 
 namespace webrtc {
@@ -24,55 +26,48 @@
 // Provides a buffer of the render data for the echo remover.
 class RenderBuffer {
  public:
-  // The constructor takes, besides from the other parameters, a vector
-  // containing the number of FFTs that will be included in the spectral sums in
-  // the call to SpectralSum.
-  RenderBuffer(Aec3Optimization optimization,
-               size_t num_bands,
-               size_t size,
-               const std::vector<size_t> num_ffts_for_spectral_sums);
+  RenderBuffer(size_t num_ffts_for_spectral_sums,
+               MatrixBuffer* block_buffer,
+               VectorBuffer* spectrum_buffer,
+               FftBuffer* fft_buffer);
   ~RenderBuffer();
 
   // Clears the buffer.
   void Clear();
 
   // Insert a block into the buffer.
-  void Insert(const std::vector<std::vector<float>>& block);
+  void UpdateSpectralSum();
 
   // Gets the last inserted block.
   const std::vector<std::vector<float>>& MostRecentBlock() const {
-    return last_block_;
+    return block_buffer_->buffer[block_buffer_->read];
   }
 
-  // Get the spectrum from one of the FFTs in the buffer
-  const std::array<float, kFftLengthBy2Plus1>& Spectrum(
-      size_t buffer_offset_ffts) const {
-    return spectrum_buffer_[(position_ + buffer_offset_ffts) %
-                            fft_buffer_.size()];
+  // Get the spectrum from one of the FFTs in the buffer.
+  rtc::ArrayView<const float> Spectrum(size_t buffer_offset_ffts) const {
+    size_t position = spectrum_buffer_->OffsetIndex(spectrum_buffer_->read,
+                                                    buffer_offset_ffts);
+    return spectrum_buffer_->buffer[position];
   }
 
   // Returns the sum of the spectrums for a certain number of FFTs.
-  const std::array<float, kFftLengthBy2Plus1>& SpectralSum(
-      size_t num_ffts) const {
+  rtc::ArrayView<const float> SpectralSum(size_t num_ffts) const {
     RTC_DCHECK_EQ(spectral_sums_length_, num_ffts);
-    return spectral_sums_[0];
+    return spectral_sums_;
   }
 
   // Returns the circular buffer.
-  rtc::ArrayView<const FftData> Buffer() const { return fft_buffer_; }
+  rtc::ArrayView<const FftData> Buffer() const { return fft_buffer_->buffer; }
 
-  // Returns the current position in the circular buffer
-  size_t Position() const { return position_; }
+  // Returns the current position in the circular buffer.
+  size_t Position() const { return fft_buffer_->read; }
 
  private:
-  const Aec3Optimization optimization_;
-  std::vector<FftData> fft_buffer_;
-  std::vector<std::array<float, kFftLengthBy2Plus1>> spectrum_buffer_;
-  size_t spectral_sums_length_;
-  std::vector<std::array<float, kFftLengthBy2Plus1>> spectral_sums_;
-  size_t position_ = 0;
-  std::vector<std::vector<float>> last_block_;
-  const Aec3Fft fft_;
+  const MatrixBuffer* const block_buffer_;
+  VectorBuffer* spectrum_buffer_;
+  const FftBuffer* const fft_buffer_;
+  const size_t spectral_sums_length_;
+  std::array<float, kFftLengthBy2Plus1> spectral_sums_;
   RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RenderBuffer);
 };
 
diff --git a/modules/audio_processing/aec3/render_buffer_unittest.cc b/modules/audio_processing/aec3/render_buffer_unittest.cc
index 1498f4e..989cb27 100644
--- a/modules/audio_processing/aec3/render_buffer_unittest.cc
+++ b/modules/audio_processing/aec3/render_buffer_unittest.cc
@@ -20,25 +20,25 @@
 
 #if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
 
-// Verifies the check for the provided numbers of Ffts to include in the
-// spectral sum.
-TEST(RenderBuffer, TooLargeNumberOfSpectralSums) {
-  EXPECT_DEATH(
-      RenderBuffer(Aec3Optimization::kNone, 3, 1, std::vector<size_t>(2, 1)),
-      "");
+// Verifies the check for non-null fft buffer.
+TEST(RenderBuffer, NullExternalFftBuffer) {
+  MatrixBuffer block_buffer(10, 3, kBlockSize);
+  VectorBuffer spectrum_buffer(10, kFftLengthBy2Plus1);
+  EXPECT_DEATH(RenderBuffer(1, &block_buffer, &spectrum_buffer, nullptr), "");
 }
 
-TEST(RenderBuffer, TooSmallNumberOfSpectralSums) {
-  EXPECT_DEATH(
-      RenderBuffer(Aec3Optimization::kNone, 3, 1, std::vector<size_t>()), "");
+// Verifies the check for non-null spectrum buffer.
+TEST(RenderBuffer, NullExternalSpectrumBuffer) {
+  FftBuffer fft_buffer(10);
+  MatrixBuffer block_buffer(10, 3, kBlockSize);
+  EXPECT_DEATH(RenderBuffer(1, &block_buffer, nullptr, &fft_buffer), "");
 }
 
-// Verifies the feasibility check for the provided number of Ffts to include in
-// the spectral.
-TEST(RenderBuffer, FeasibleNumberOfFftsInSum) {
-  EXPECT_DEATH(
-      RenderBuffer(Aec3Optimization::kNone, 3, 1, std::vector<size_t>(1, 2)),
-      "");
+// Verifies the check for non-null block buffer.
+TEST(RenderBuffer, NullExternalBlockBuffer) {
+  FftBuffer fft_buffer(10);
+  VectorBuffer spectrum_buffer(10, kFftLengthBy2Plus1);
+  EXPECT_DEATH(RenderBuffer(1, nullptr, &spectrum_buffer, &fft_buffer), "");
 }
 
 #endif
diff --git a/modules/audio_processing/aec3/render_delay_buffer.cc b/modules/audio_processing/aec3/render_delay_buffer.cc
index d2ead63..9640131 100644
--- a/modules/audio_processing/aec3/render_delay_buffer.cc
+++ b/modules/audio_processing/aec3/render_delay_buffer.cc
@@ -14,9 +14,12 @@
 #include <algorithm>
 
 #include "modules/audio_processing/aec3/aec3_common.h"
+#include "modules/audio_processing/aec3/aec3_fft.h"
 #include "modules/audio_processing/aec3/block_processor.h"
 #include "modules/audio_processing/aec3/decimator.h"
+#include "modules/audio_processing/aec3/fft_buffer.h"
 #include "modules/audio_processing/aec3/fft_data.h"
+#include "modules/audio_processing/aec3/matrix_buffer.h"
 #include "rtc_base/atomicops.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/constructormagic.h"
@@ -25,191 +28,161 @@
 namespace webrtc {
 namespace {
 
-class ApiCallJitterBuffer {
- public:
-  explicit ApiCallJitterBuffer(size_t num_bands) {
-    buffer_.fill(std::vector<std::vector<float>>(
-        num_bands, std::vector<float>(kBlockSize, 0.f)));
-  }
-
-  ~ApiCallJitterBuffer() = default;
-
-  void Reset() {
-    size_ = 0;
-    last_insert_index_ = 0;
-  }
-
-  void Insert(const std::vector<std::vector<float>>& block) {
-    RTC_DCHECK_LT(size_, buffer_.size());
-    last_insert_index_ = (last_insert_index_ + 1) % buffer_.size();
-    RTC_DCHECK_EQ(buffer_[last_insert_index_].size(), block.size());
-    RTC_DCHECK_EQ(buffer_[last_insert_index_][0].size(), block[0].size());
-    for (size_t k = 0; k < block.size(); ++k) {
-      std::copy(block[k].begin(), block[k].end(),
-                buffer_[last_insert_index_][k].begin());
-    }
-    ++size_;
-  }
-
-  void Remove(std::vector<std::vector<float>>* block) {
-    RTC_DCHECK_LT(0, size_);
-    --size_;
-    const size_t extract_index =
-        (last_insert_index_ - size_ + buffer_.size()) % buffer_.size();
-    for (size_t k = 0; k < block->size(); ++k) {
-      std::copy(buffer_[extract_index][k].begin(),
-                buffer_[extract_index][k].end(), (*block)[k].begin());
-    }
-  }
-
-  size_t Size() const { return size_; }
-  bool Full() const { return size_ >= (buffer_.size()); }
-  bool Empty() const { return size_ == 0; }
-
- private:
-  std::array<std::vector<std::vector<float>>, kMaxApiCallsJitterBlocks> buffer_;
-  size_t size_ = 0;
-  int last_insert_index_ = 0;
-};
+constexpr int kBufferHeadroom = kAdaptiveFilterLength;
 
 class RenderDelayBufferImpl final : public RenderDelayBuffer {
  public:
-  RenderDelayBufferImpl(size_t num_bands,
-                        size_t down_sampling_factor,
-                        size_t downsampled_render_buffer_size,
-                        size_t render_delay_buffer_size);
+  RenderDelayBufferImpl(const EchoCanceller3Config& config, size_t num_bands);
   ~RenderDelayBufferImpl() override;
 
   void Reset() override;
-  bool Insert(const std::vector<std::vector<float>>& block) override;
-  bool UpdateBuffers() override;
+  BufferingEvent Insert(const std::vector<std::vector<float>>& block) override;
+  BufferingEvent PrepareCaptureCall() override;
   void SetDelay(size_t delay) override;
   size_t Delay() const override { return delay_; }
-
-  const RenderBuffer& GetRenderBuffer() const override { return fft_buffer_; }
+  size_t MaxDelay() const override {
+    return blocks_.buffer.size() - 1 - kBufferHeadroom;
+  }
+  size_t MaxApiJitter() const override { return max_api_jitter_; }
+  const RenderBuffer& GetRenderBuffer() const override {
+    return echo_remover_buffer_;
+  }
 
   const DownsampledRenderBuffer& GetDownsampledRenderBuffer() const override {
-    return downsampled_render_buffer_;
+    return low_rate_;
   }
 
  private:
   static int instance_count_;
   std::unique_ptr<ApmDataDumper> data_dumper_;
   const Aec3Optimization optimization_;
-  const size_t down_sampling_factor_;
-  const size_t sub_block_size_;
-  std::vector<std::vector<std::vector<float>>> buffer_;
-  size_t delay_ = 0;
-  size_t last_insert_index_ = 0;
-  RenderBuffer fft_buffer_;
-  DownsampledRenderBuffer downsampled_render_buffer_;
+  const size_t api_call_jitter_blocks_;
+  const size_t min_echo_path_delay_blocks_;
+  const int sub_block_size_;
+  MatrixBuffer blocks_;
+  VectorBuffer spectra_;
+  FftBuffer ffts_;
+  size_t delay_;
+  int max_api_jitter_ = 0;
+  int render_surplus_ = 0;
+  bool first_reset_occurred_ = false;
+  RenderBuffer echo_remover_buffer_;
+  DownsampledRenderBuffer low_rate_;
   Decimator render_decimator_;
-  ApiCallJitterBuffer api_call_jitter_buffer_;
   const std::vector<std::vector<float>> zero_block_;
+  const Aec3Fft fft_;
+  size_t capture_call_counter_ = 0;
+  std::vector<float> render_ds_;
+  int render_calls_in_a_row_ = 0;
+
+  void UpdateBuffersWithLatestBlock(size_t previous_write);
+  void IncreaseRead();
+  void IncreaseInsert();
+
   RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RenderDelayBufferImpl);
 };
 
 int RenderDelayBufferImpl::instance_count_ = 0;
 
-RenderDelayBufferImpl::RenderDelayBufferImpl(
-    size_t num_bands,
-    size_t down_sampling_factor,
-    size_t downsampled_render_buffer_size,
-    size_t render_delay_buffer_size)
+RenderDelayBufferImpl::RenderDelayBufferImpl(const EchoCanceller3Config& config,
+                                             size_t num_bands)
     : data_dumper_(
           new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
       optimization_(DetectOptimization()),
-      down_sampling_factor_(down_sampling_factor),
-      sub_block_size_(down_sampling_factor_ > 0
-                          ? kBlockSize / down_sampling_factor
-                          : kBlockSize),
-      buffer_(
-          render_delay_buffer_size,
-          std::vector<std::vector<float>>(num_bands,
-                                          std::vector<float>(kBlockSize, 0.f))),
-      fft_buffer_(
-          optimization_,
-          num_bands,
-          std::max(kUnknownDelayRenderWindowSize, kAdaptiveFilterLength),
-          std::vector<size_t>(1, kAdaptiveFilterLength)),
-      downsampled_render_buffer_(downsampled_render_buffer_size),
-      render_decimator_(down_sampling_factor_),
-      api_call_jitter_buffer_(num_bands),
-      zero_block_(num_bands, std::vector<float>(kBlockSize, 0.f)) {
-  RTC_DCHECK_LT(buffer_.size(), downsampled_render_buffer_.buffer.size());
+      api_call_jitter_blocks_(config.delay.api_call_jitter_blocks),
+      min_echo_path_delay_blocks_(config.delay.min_echo_path_delay_blocks),
+      sub_block_size_(
+          static_cast<int>(config.delay.down_sampling_factor > 0
+                               ? kBlockSize / config.delay.down_sampling_factor
+                               : kBlockSize)),
+      blocks_(GetRenderDelayBufferSize(config.delay.down_sampling_factor,
+                                       config.delay.num_filters),
+              num_bands,
+              kBlockSize),
+      spectra_(blocks_.buffer.size(), kFftLengthBy2Plus1),
+      ffts_(blocks_.buffer.size()),
+      delay_(min_echo_path_delay_blocks_),
+      echo_remover_buffer_(kAdaptiveFilterLength, &blocks_, &spectra_, &ffts_),
+      low_rate_(GetDownSampledBufferSize(config.delay.down_sampling_factor,
+                                         config.delay.num_filters)),
+      render_decimator_(config.delay.down_sampling_factor),
+      zero_block_(num_bands, std::vector<float>(kBlockSize, 0.f)),
+      fft_(),
+      render_ds_(sub_block_size_, 0.f) {
+  RTC_DCHECK_EQ(blocks_.buffer.size(), ffts_.buffer.size());
+  RTC_DCHECK_EQ(spectra_.buffer.size(), ffts_.buffer.size());
+  Reset();
+  first_reset_occurred_ = false;
 }
 
 RenderDelayBufferImpl::~RenderDelayBufferImpl() = default;
 
 void RenderDelayBufferImpl::Reset() {
-  // Empty all data in the buffers.
-  delay_ = 0;
-  last_insert_index_ = 0;
-  downsampled_render_buffer_.position = 0;
-  std::fill(downsampled_render_buffer_.buffer.begin(),
-            downsampled_render_buffer_.buffer.end(), 0.f);
-  fft_buffer_.Clear();
-  api_call_jitter_buffer_.Reset();
-  for (auto& c : buffer_) {
-    for (auto& b : c) {
-      std::fill(b.begin(), b.end(), 0.f);
-    }
-  }
+  delay_ = min_echo_path_delay_blocks_;
+  const int offset1 = std::max<int>(
+      std::min(api_call_jitter_blocks_, min_echo_path_delay_blocks_), 1);
+  const int offset2 = static_cast<int>(delay_ + offset1);
+  const int offset3 = offset1 * sub_block_size_;
+  low_rate_.read = low_rate_.OffsetIndex(low_rate_.write, offset3);
+  blocks_.read = blocks_.OffsetIndex(blocks_.write, -offset2);
+  spectra_.read = spectra_.OffsetIndex(spectra_.write, offset2);
+  ffts_.read = ffts_.OffsetIndex(ffts_.write, offset2);
+  render_surplus_ = 0;
+  first_reset_occurred_ = true;
 }
 
-bool RenderDelayBufferImpl::Insert(
+RenderDelayBuffer::BufferingEvent RenderDelayBufferImpl::Insert(
     const std::vector<std::vector<float>>& block) {
-  RTC_DCHECK_EQ(block.size(), buffer_[0].size());
-  RTC_DCHECK_EQ(block[0].size(), buffer_[0][0].size());
+  RTC_DCHECK_EQ(block.size(), blocks_.buffer[0].size());
+  RTC_DCHECK_EQ(block[0].size(), blocks_.buffer[0][0].size());
+  BufferingEvent event = BufferingEvent::kNone;
 
-  if (api_call_jitter_buffer_.Full()) {
-    // Report buffer overrun and let the caller handle the overrun.
-    return false;
+  ++render_surplus_;
+  if (first_reset_occurred_) {
+    ++render_calls_in_a_row_;
+    max_api_jitter_ = std::max(max_api_jitter_, render_calls_in_a_row_);
   }
-  api_call_jitter_buffer_.Insert(block);
 
-  return true;
+  const size_t previous_write = blocks_.write;
+  IncreaseInsert();
+
+  if (low_rate_.read == low_rate_.write || blocks_.read == blocks_.write) {
+    // Render overrun due to more render data being inserted than read. Discard
+    // the oldest render data.
+    event = BufferingEvent::kRenderOverrun;
+    IncreaseRead();
+  }
+
+  for (size_t k = 0; k < block.size(); ++k) {
+    std::copy(block[k].begin(), block[k].end(),
+              blocks_.buffer[blocks_.write][k].begin());
+  }
+
+  UpdateBuffersWithLatestBlock(previous_write);
+  return event;
 }
 
-bool RenderDelayBufferImpl::UpdateBuffers() {
-  bool underrun = true;
-  // Update the buffers with a new block if such is available, otherwise insert
-  // a block of silence.
-  if (api_call_jitter_buffer_.Size() > 0) {
-    last_insert_index_ = (last_insert_index_ + 1) % buffer_.size();
-    api_call_jitter_buffer_.Remove(&buffer_[last_insert_index_]);
-    underrun = false;
-  }
+RenderDelayBuffer::BufferingEvent RenderDelayBufferImpl::PrepareCaptureCall() {
+  BufferingEvent event = BufferingEvent::kNone;
+  render_calls_in_a_row_ = 0;
 
-  downsampled_render_buffer_.position =
-      (downsampled_render_buffer_.position - sub_block_size_ +
-       downsampled_render_buffer_.buffer.size()) %
-      downsampled_render_buffer_.buffer.size();
-
-  rtc::ArrayView<const float> input(
-      underrun ? zero_block_[0].data() : buffer_[last_insert_index_][0].data(),
-      kBlockSize);
-  rtc::ArrayView<float> output(downsampled_render_buffer_.buffer.data() +
-                                   downsampled_render_buffer_.position,
-                               sub_block_size_);
-  data_dumper_->DumpWav("aec3_render_decimator_input", input.size(),
-                        input.data(), 16000, 1);
-  render_decimator_.Decimate(input, output);
-  data_dumper_->DumpWav("aec3_render_decimator_output", output.size(),
-                        output.data(), 16000 / down_sampling_factor_, 1);
-  for (size_t k = 0; k < output.size() / 2; ++k) {
-    float tmp = output[k];
-    output[k] = output[output.size() - 1 - k];
-    output[output.size() - 1 - k] = tmp;
-  }
-
-  if (underrun) {
-    fft_buffer_.Insert(zero_block_);
+  if (low_rate_.read == low_rate_.write || blocks_.read == blocks_.write) {
+    event = BufferingEvent::kRenderUnderrun;
   } else {
-    fft_buffer_.Insert(buffer_[(last_insert_index_ - delay_ + buffer_.size()) %
-                               buffer_.size()]);
+    IncreaseRead();
   }
-  return !underrun;
+  --render_surplus_;
+
+  echo_remover_buffer_.UpdateSpectralSum();
+
+  if (render_surplus_ >= static_cast<int>(api_call_jitter_blocks_)) {
+    event = BufferingEvent::kApiCallSkew;
+    RTC_LOG(LS_WARNING) << "Api call skew detected at " << capture_call_counter_
+                        << ".";
+  }
+
+  ++capture_call_counter_;
+  return event;
 }
 
 void RenderDelayBufferImpl::SetDelay(size_t delay) {
@@ -217,37 +190,51 @@
     return;
   }
 
-  // If there is a new delay set, clear the fft buffer.
-  fft_buffer_.Clear();
-
-  if ((buffer_.size() - 1) < delay) {
-    // If the desired delay is larger than the delay buffer, shorten the delay
-    // buffer size to achieve the desired alignment with the available buffer
-    // size.
-    downsampled_render_buffer_.position =
-        (downsampled_render_buffer_.position +
-         sub_block_size_ * (delay - (buffer_.size() - 1))) %
-        downsampled_render_buffer_.buffer.size();
-
-    last_insert_index_ =
-        (last_insert_index_ - (delay - (buffer_.size() - 1)) + buffer_.size()) %
-        buffer_.size();
-    delay_ = buffer_.size() - 1;
-  } else {
-    delay_ = delay;
+  const int delta_delay = static_cast<int>(delay_) - static_cast<int>(delay);
+  delay_ = delay;
+  if (delay_ > MaxDelay()) {
+    delay_ = std::min(MaxDelay(), delay);
+    RTC_NOTREACHED();
   }
+
+  // Recompute the read indices according to the set delay.
+  blocks_.UpdateReadIndex(delta_delay);
+  spectra_.UpdateReadIndex(-delta_delay);
+  ffts_.UpdateReadIndex(-delta_delay);
 }
 
+void RenderDelayBufferImpl::UpdateBuffersWithLatestBlock(
+    size_t previous_write) {
+  render_decimator_.Decimate(blocks_.buffer[blocks_.write][0], render_ds_);
+  std::copy(render_ds_.rbegin(), render_ds_.rend(),
+            low_rate_.buffer.begin() + low_rate_.write);
+
+  fft_.PaddedFft(blocks_.buffer[blocks_.write][0],
+                 blocks_.buffer[previous_write][0], &ffts_.buffer[ffts_.write]);
+
+  ffts_.buffer[ffts_.write].Spectrum(optimization_,
+                                     spectra_.buffer[spectra_.write]);
+};
+
+void RenderDelayBufferImpl::IncreaseRead() {
+  low_rate_.UpdateReadIndex(-sub_block_size_);
+  blocks_.IncReadIndex();
+  spectra_.DecReadIndex();
+  ffts_.DecReadIndex();
+};
+
+void RenderDelayBufferImpl::IncreaseInsert() {
+  low_rate_.UpdateWriteIndex(-sub_block_size_);
+  blocks_.IncWriteIndex();
+  spectra_.DecWriteIndex();
+  ffts_.DecWriteIndex();
+};
+
 }  // namespace
 
-RenderDelayBuffer* RenderDelayBuffer::Create(
-    size_t num_bands,
-    size_t down_sampling_factor,
-    size_t downsampled_render_buffer_size,
-    size_t render_delay_buffer_size) {
-  return new RenderDelayBufferImpl(num_bands, down_sampling_factor,
-                                   downsampled_render_buffer_size,
-                                   render_delay_buffer_size);
+RenderDelayBuffer* RenderDelayBuffer::Create(const EchoCanceller3Config& config,
+                                             size_t num_bands) {
+  return new RenderDelayBufferImpl(config, num_bands);
 }
 
 }  // namespace webrtc
diff --git a/modules/audio_processing/aec3/render_delay_buffer.h b/modules/audio_processing/aec3/render_delay_buffer.h
index 8f5de40..0261e84 100644
--- a/modules/audio_processing/aec3/render_delay_buffer.h
+++ b/modules/audio_processing/aec3/render_delay_buffer.h
@@ -20,6 +20,7 @@
 #include "modules/audio_processing/aec3/downsampled_render_buffer.h"
 #include "modules/audio_processing/aec3/fft_data.h"
 #include "modules/audio_processing/aec3/render_buffer.h"
+#include "modules/audio_processing/include/audio_processing.h"
 
 namespace webrtc {
 
@@ -27,22 +28,28 @@
 // extracted with a specified delay.
 class RenderDelayBuffer {
  public:
-  static RenderDelayBuffer* Create(size_t num_bands,
-                                   size_t down_sampling_factor,
-                                   size_t downsampled_render_buffer_size,
-                                   size_t render_delay_buffer_size);
+  enum class BufferingEvent {
+    kNone,
+    kRenderUnderrun,
+    kRenderOverrun,
+    kApiCallSkew,
+    kRenderDataLost
+  };
+
+  static RenderDelayBuffer* Create(const EchoCanceller3Config& config,
+                                   size_t num_bands);
   virtual ~RenderDelayBuffer() = default;
 
-  // Resets the buffer data.
+  // Resets the buffer alignment.
   virtual void Reset() = 0;
 
-  // Inserts a block into the buffer and returns true if the insert is
-  // successful.
-  virtual bool Insert(const std::vector<std::vector<float>>& block) = 0;
+  // Inserts a block into the buffer.
+  virtual BufferingEvent Insert(
+      const std::vector<std::vector<float>>& block) = 0;
 
   // Updates the buffers one step based on the specified buffer delay. Returns
-  // true if there was no overrun, otherwise returns false.
-  virtual bool UpdateBuffers() = 0;
+  // an enum indicating whether there was a special event that occurred.
+  virtual BufferingEvent PrepareCaptureCall() = 0;
 
   // Sets the buffer delay.
   virtual void SetDelay(size_t delay) = 0;
@@ -50,6 +57,12 @@
   // Gets the buffer delay.
   virtual size_t Delay() const = 0;
 
+  // Gets the buffer delay.
+  virtual size_t MaxDelay() const = 0;
+
+  // Gets the observed jitter in the render and capture call sequence.
+  virtual size_t MaxApiJitter() const = 0;
+
   // Returns the render buffer for the echo remover.
   virtual const RenderBuffer& GetRenderBuffer() const = 0;
 
diff --git a/modules/audio_processing/aec3/render_delay_buffer_unittest.cc b/modules/audio_processing/aec3/render_delay_buffer_unittest.cc
index 3e0abea..9b99a8e 100644
--- a/modules/audio_processing/aec3/render_delay_buffer_unittest.cc
+++ b/modules/audio_processing/aec3/render_delay_buffer_unittest.cc
@@ -30,49 +30,50 @@
   return ss.str();
 }
 
-constexpr size_t kDownSamplingFactor = 4;
-constexpr size_t kNumMatchedFilters = 4;
-
 }  // namespace
 
 // 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(
-        NumBandsForRate(rate), kDownSamplingFactor,
-        GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters),
-        GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters)));
+    std::unique_ptr<RenderDelayBuffer> delay_buffer(
+        RenderDelayBuffer::Create(config, NumBandsForRate(rate)));
     std::vector<std::vector<float>> block_to_insert(
         NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
-    for (size_t k = 0; k < kMaxApiCallsJitterBlocks; ++k) {
-      EXPECT_TRUE(delay_buffer->Insert(block_to_insert));
+    for (size_t k = 0; k < 10; ++k) {
+      EXPECT_EQ(RenderDelayBuffer::BufferingEvent::kNone,
+                delay_buffer->Insert(block_to_insert));
     }
-    EXPECT_FALSE(delay_buffer->Insert(block_to_insert));
+    for (size_t k = 0; k < 1000; ++k) {
+      delay_buffer->Insert(block_to_insert);
+    }
+
+    EXPECT_EQ(RenderDelayBuffer::BufferingEvent::kRenderOverrun,
+              delay_buffer->Insert(block_to_insert));
   }
 }
 
 // Verifies that the check for available block works.
 TEST(RenderDelayBuffer, AvailableBlock) {
   constexpr size_t kNumBands = 1;
-  std::unique_ptr<RenderDelayBuffer> delay_buffer(RenderDelayBuffer::Create(
-      kNumBands, kDownSamplingFactor,
-      GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters),
-      GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters)));
+  std::unique_ptr<RenderDelayBuffer> delay_buffer(
+      RenderDelayBuffer::Create(EchoCanceller3Config(), kNumBands));
   std::vector<std::vector<float>> input_block(
       kNumBands, std::vector<float>(kBlockSize, 1.f));
-  EXPECT_TRUE(delay_buffer->Insert(input_block));
-  delay_buffer->UpdateBuffers();
+  EXPECT_EQ(RenderDelayBuffer::BufferingEvent::kNone,
+            delay_buffer->Insert(input_block));
+  delay_buffer->PrepareCaptureCall();
 }
 
 // Verifies the SetDelay method.
 TEST(RenderDelayBuffer, SetDelay) {
-  std::unique_ptr<RenderDelayBuffer> delay_buffer(RenderDelayBuffer::Create(
-      1, kDownSamplingFactor,
-      GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters),
-      GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters)));
-  EXPECT_EQ(0u, delay_buffer->Delay());
-  for (size_t delay = 0; delay < 20; ++delay) {
+  EchoCanceller3Config config;
+  std::unique_ptr<RenderDelayBuffer> delay_buffer(
+      RenderDelayBuffer::Create(config, 1));
+  EXPECT_EQ(config.delay.min_echo_path_delay_blocks, delay_buffer->Delay());
+  for (size_t delay = config.delay.min_echo_path_delay_blocks + 1; delay < 20;
+       ++delay) {
     delay_buffer->SetDelay(delay);
     EXPECT_EQ(delay, delay_buffer->Delay());
   }
@@ -84,10 +85,8 @@
 // TODO(peah): Re-enable the test once the issue with memory leaks during DEATH
 // tests on test bots has been fixed.
 TEST(RenderDelayBuffer, DISABLED_WrongDelay) {
-  std::unique_ptr<RenderDelayBuffer> delay_buffer(RenderDelayBuffer::Create(
-      3, kDownSamplingFactor,
-      GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters),
-      GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters)));
+  std::unique_ptr<RenderDelayBuffer> delay_buffer(
+      RenderDelayBuffer::Create(EchoCanceller3Config(), 3));
   EXPECT_DEATH(delay_buffer->SetDelay(21), "");
 }
 
@@ -96,9 +95,7 @@
   for (auto rate : {16000, 32000, 48000}) {
     SCOPED_TRACE(ProduceDebugText(rate));
     std::unique_ptr<RenderDelayBuffer> delay_buffer(RenderDelayBuffer::Create(
-        NumBandsForRate(rate), kDownSamplingFactor,
-        GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters),
-        GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters)));
+        EchoCanceller3Config(), NumBandsForRate(rate)));
     std::vector<std::vector<float>> block_to_insert(
         NumBandsForRate(rate < 48000 ? rate + 16000 : 16000),
         std::vector<float>(kBlockSize, 0.f));
@@ -110,10 +107,8 @@
 TEST(RenderDelayBuffer, WrongBlockLength) {
   for (auto rate : {8000, 16000, 32000, 48000}) {
     SCOPED_TRACE(ProduceDebugText(rate));
-    std::unique_ptr<RenderDelayBuffer> delay_buffer(RenderDelayBuffer::Create(
-        3, kDownSamplingFactor,
-        GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters),
-        GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters)));
+    std::unique_ptr<RenderDelayBuffer> delay_buffer(
+        RenderDelayBuffer::Create(EchoCanceller3Config(), 3));
     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), "");
diff --git a/modules/audio_processing/aec3/render_delay_controller.cc b/modules/audio_processing/aec3/render_delay_controller.cc
index 2c1f263..05121f2 100644
--- a/modules/audio_processing/aec3/render_delay_controller.cc
+++ b/modules/audio_processing/aec3/render_delay_controller.cc
@@ -41,8 +41,10 @@
  private:
   static int instance_count_;
   std::unique_ptr<ApmDataDumper> data_dumper_;
+  const size_t min_echo_path_delay_;
   const size_t default_delay_;
   size_t delay_;
+  EchoPathDelayEstimator delay_estimator_;
   size_t blocks_since_last_delay_estimate_ = 300000;
   int echo_path_delay_samples_;
   size_t align_call_counter_ = 0;
@@ -50,7 +52,6 @@
   std::vector<float> capture_delay_buffer_;
   int capture_delay_buffer_index_ = 0;
   RenderDelayControllerMetrics metrics_;
-  EchoPathDelayEstimator delay_estimator_;
   RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RenderDelayControllerImpl);
 };
 
@@ -78,12 +79,15 @@
     int sample_rate_hz)
     : data_dumper_(
           new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
+      min_echo_path_delay_(config.delay.min_echo_path_delay_blocks),
       default_delay_(
-          std::max(config.delay.default_delay, kMinEchoPathDelayBlocks)),
+          std::max(config.delay.default_delay, min_echo_path_delay_)),
       delay_(default_delay_),
+      delay_estimator_(data_dumper_.get(), config),
       echo_path_delay_samples_(default_delay_ * kBlockSize),
-      capture_delay_buffer_(kBlockSize * (kMaxApiCallsJitterBlocks + 2), 0.f),
-      delay_estimator_(data_dumper_.get(), config) {
+      capture_delay_buffer_(
+          kBlockSize * (config.delay.api_call_jitter_blocks + 2),
+          0.f) {
   RTC_DCHECK(ValidFullBandRate(sample_rate_hz));
   delay_estimator_.LogDelayEstimationProperties(sample_rate_hz,
                                                 capture_delay_buffer_.size());
diff --git a/modules/audio_processing/aec3/render_delay_controller_unittest.cc b/modules/audio_processing/aec3/render_delay_controller_unittest.cc
index 2e36d22..0f5432b 100644
--- a/modules/audio_processing/aec3/render_delay_controller_unittest.cc
+++ b/modules/audio_processing/aec3/render_delay_controller_unittest.cc
@@ -47,22 +47,20 @@
 // Verifies the output of GetDelay when there are no AnalyzeRender calls.
 TEST(RenderDelayController, NoRenderSignal) {
   std::vector<float> block(kBlockSize, 0.f);
+  EchoCanceller3Config config;
   for (size_t num_matched_filters = 4; num_matched_filters == 10;
        num_matched_filters++) {
     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}) {
         SCOPED_TRACE(ProduceDebugText(rate));
         std::unique_ptr<RenderDelayBuffer> delay_buffer(
-            RenderDelayBuffer::Create(
-                NumBandsForRate(rate), down_sampling_factor,
-                GetDownSampledBufferSize(down_sampling_factor,
-                                         num_matched_filters),
-                GetRenderDelayBufferSize(down_sampling_factor,
-                                         num_matched_filters)));
+            RenderDelayBuffer::Create(config, NumBandsForRate(rate)));
         std::unique_ptr<RenderDelayController> delay_controller(
-            RenderDelayController::Create(EchoCanceller3Config(), rate));
+            RenderDelayController::Create(config, rate));
         for (size_t k = 0; k < 100; ++k) {
-          EXPECT_EQ(kMinEchoPathDelayBlocks,
+          EXPECT_EQ(config.delay.min_echo_path_delay_blocks,
                     delay_controller->GetDelay(
                         delay_buffer->GetDownsampledRenderBuffer(), block));
         }
@@ -78,26 +76,24 @@
   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::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-            RenderDelayBuffer::Create(
-                NumBandsForRate(rate), down_sampling_factor,
-                GetDownSampledBufferSize(down_sampling_factor,
-                                         num_matched_filters),
-                GetRenderDelayBufferSize(down_sampling_factor,
-                                         num_matched_filters)));
+            RenderDelayBuffer::Create(config, NumBandsForRate(rate)));
         std::unique_ptr<RenderDelayController> delay_controller(
             RenderDelayController::Create(EchoCanceller3Config(), rate));
         for (size_t k = 0; k < 10; ++k) {
           render_delay_buffer->Insert(render_block);
-          render_delay_buffer->UpdateBuffers();
+          render_delay_buffer->PrepareCaptureCall();
           delay_blocks = delay_controller->GetDelay(
               render_delay_buffer->GetDownsampledRenderBuffer(), capture_block);
         }
         EXPECT_FALSE(delay_controller->AlignmentHeadroomSamples());
-        EXPECT_EQ(kMinEchoPathDelayBlocks, delay_blocks);
+        EXPECT_EQ(config.delay.min_echo_path_delay_blocks, delay_blocks);
       }
     }
   }
@@ -112,6 +108,10 @@
   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));
@@ -119,20 +119,15 @@
         for (size_t delay_samples : {15, 50, 150, 200, 800, 4000}) {
           SCOPED_TRACE(ProduceDebugText(rate, delay_samples));
           std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-              RenderDelayBuffer::Create(
-                  NumBandsForRate(rate), down_sampling_factor,
-                  GetDownSampledBufferSize(down_sampling_factor,
-                                           num_matched_filters),
-                  GetRenderDelayBufferSize(down_sampling_factor,
-                                           num_matched_filters)));
+              RenderDelayBuffer::Create(config, NumBandsForRate(rate)));
           std::unique_ptr<RenderDelayController> delay_controller(
-              RenderDelayController::Create(EchoCanceller3Config(), rate));
+              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->UpdateBuffers();
+            render_delay_buffer->PrepareCaptureCall();
             delay_blocks = delay_controller->GetDelay(
                 render_delay_buffer->GetDownsampledRenderBuffer(),
                 capture_block);
@@ -164,6 +159,9 @@
   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));
@@ -173,12 +171,7 @@
         for (int delay_samples : {-15, -50, -150, -200}) {
           SCOPED_TRACE(ProduceDebugText(rate, -delay_samples));
           std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-              RenderDelayBuffer::Create(
-                  NumBandsForRate(rate), down_sampling_factor,
-                  GetDownSampledBufferSize(down_sampling_factor,
-                                           num_matched_filters),
-                  GetRenderDelayBufferSize(down_sampling_factor,
-                                           num_matched_filters)));
+              RenderDelayBuffer::Create(config, NumBandsForRate(rate)));
           std::unique_ptr<RenderDelayController> delay_controller(
               RenderDelayController::Create(EchoCanceller3Config(), rate));
           DelayBuffer<float> signal_delay_buffer(-delay_samples);
@@ -187,7 +180,7 @@
             RandomizeSampleVector(&random_generator, capture_block[0]);
             signal_delay_buffer.Delay(capture_block[0], render_block[0]);
             render_delay_buffer->Insert(render_block);
-            render_delay_buffer->UpdateBuffers();
+            render_delay_buffer->PrepareCaptureCall();
             delay_blocks = delay_controller->GetDelay(
                 render_delay_buffer->GetDownsampledRenderBuffer(),
                 capture_block[0]);
@@ -212,6 +205,9 @@
   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));
@@ -219,28 +215,25 @@
           size_t delay_blocks = 0;
           SCOPED_TRACE(ProduceDebugText(rate, delay_samples));
           std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-              RenderDelayBuffer::Create(
-                  NumBandsForRate(rate), down_sampling_factor,
-                  GetDownSampledBufferSize(down_sampling_factor,
-                                           num_matched_filters),
-                  GetRenderDelayBufferSize(down_sampling_factor,
-                                           num_matched_filters)));
+              RenderDelayBuffer::Create(config, NumBandsForRate(rate)));
           std::unique_ptr<RenderDelayController> delay_controller(
-              RenderDelayController::Create(EchoCanceller3Config(), rate));
+              RenderDelayController::Create(config, rate));
           DelayBuffer<float> signal_delay_buffer(delay_samples);
           for (size_t j = 0; j < (1000 + delay_samples / kBlockSize) /
-                                         kMaxApiCallsJitterBlocks +
+                                         config.delay.api_call_jitter_blocks +
                                      1;
                ++j) {
             std::vector<std::vector<float>> capture_block_buffer;
-            for (size_t k = 0; k < (kMaxApiCallsJitterBlocks - 1); ++k) {
+            for (size_t k = 0; k < (config.delay.api_call_jitter_blocks - 1);
+                 ++k) {
               RandomizeSampleVector(&random_generator, render_block[0]);
               signal_delay_buffer.Delay(render_block[0], capture_block);
               capture_block_buffer.push_back(capture_block);
               render_delay_buffer->Insert(render_block);
             }
-            for (size_t k = 0; k < (kMaxApiCallsJitterBlocks - 1); ++k) {
-              render_delay_buffer->UpdateBuffers();
+            for (size_t k = 0; k < (config.delay.api_call_jitter_blocks - 1);
+                 ++k) {
+              render_delay_buffer->PrepareCaptureCall();
               delay_blocks = delay_controller->GetDelay(
                   render_delay_buffer->GetDownsampledRenderBuffer(),
                   capture_block_buffer[k]);
@@ -275,17 +268,16 @@
   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}) {
         SCOPED_TRACE(ProduceDebugText(rate));
         std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-            RenderDelayBuffer::Create(
-                NumBandsForRate(rate), down_sampling_factor,
-                GetDownSampledBufferSize(down_sampling_factor,
-                                         num_matched_filters),
-                GetRenderDelayBufferSize(down_sampling_factor,
-                                         num_matched_filters)));
+            RenderDelayBuffer::Create(config, NumBandsForRate(rate)));
+
         std::unique_ptr<RenderDelayController> delay_controller(
-            RenderDelayController::Create(EchoCanceller3Config(), rate));
+            RenderDelayController::Create(config, rate));
         EXPECT_FALSE(delay_controller->AlignmentHeadroomSamples());
       }
     }
@@ -297,12 +289,11 @@
 // Verifies the check for the capture signal block size.
 TEST(RenderDelayController, WrongCaptureSize) {
   std::vector<float> block(kBlockSize - 1, 0.f);
+  EchoCanceller3Config config;
   for (auto rate : {8000, 16000, 32000, 48000}) {
     SCOPED_TRACE(ProduceDebugText(rate));
     std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-        RenderDelayBuffer::Create(NumBandsForRate(rate), 4,
-                                  GetDownSampledBufferSize(4, 4),
-                                  GetRenderDelayBufferSize(4, 4)));
+        RenderDelayBuffer::Create(config, NumBandsForRate(rate)));
     EXPECT_DEATH(
         std::unique_ptr<RenderDelayController>(
             RenderDelayController::Create(EchoCanceller3Config(), rate))
@@ -318,10 +309,9 @@
 TEST(RenderDelayController, DISABLED_WrongSampleRate) {
   for (auto rate : {-1, 0, 8001, 16001}) {
     SCOPED_TRACE(ProduceDebugText(rate));
+    EchoCanceller3Config config;
     std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
-        RenderDelayBuffer::Create(NumBandsForRate(rate), 4,
-                                  GetDownSampledBufferSize(4, 4),
-                                  GetRenderDelayBufferSize(4, 4)));
+        RenderDelayBuffer::Create(config, NumBandsForRate(rate)));
     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 22aa352..b5a3ce1 100644
--- a/modules/audio_processing/aec3/render_signal_analyzer.cc
+++ b/modules/audio_processing/aec3/render_signal_analyzer.cc
@@ -30,8 +30,8 @@
     return;
   }
 
-  const std::array<float, kFftLengthBy2Plus1>& X2 =
-      render_buffer.Spectrum(*delay_partitions);
+  rtc::ArrayView<const float> X2 = render_buffer.Spectrum(*delay_partitions);
+  RTC_DCHECK_EQ(kFftLengthBy2Plus1, X2.size());
 
   for (size_t k = 1; k < (X2.size() - 1); ++k) {
     (*narrow_band_counters)[k - 1] = X2[k] > 3 * std::max(X2[k - 1], X2[k + 1])
diff --git a/modules/audio_processing/aec3/render_signal_analyzer_unittest.cc b/modules/audio_processing/aec3/render_signal_analyzer_unittest.cc
index 7e01f3f..eba28cf 100644
--- a/modules/audio_processing/aec3/render_signal_analyzer_unittest.cc
+++ b/modules/audio_processing/aec3/render_signal_analyzer_unittest.cc
@@ -18,7 +18,7 @@
 #include "modules/audio_processing/aec3/aec3_common.h"
 #include "modules/audio_processing/aec3/aec3_fft.h"
 #include "modules/audio_processing/aec3/fft_data.h"
-#include "modules/audio_processing/aec3/render_buffer.h"
+#include "modules/audio_processing/aec3/render_delay_buffer.h"
 #include "modules/audio_processing/test/echo_canceller_test_tools.h"
 #include "rtc_base/random.h"
 #include "test/gtest.h"
@@ -58,18 +58,22 @@
   Random random_generator(42U);
   std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
   std::array<float, kBlockSize> x_old;
-  FftData X;
-  Aec3Fft fft;
-  RenderBuffer render_buffer(Aec3Optimization::kNone, 3, 1,
-                             std::vector<size_t>(1, 1));
+  std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
+      RenderDelayBuffer::Create(EchoCanceller3Config(), 3));
   std::array<float, kFftLengthBy2Plus1> mask;
   x_old.fill(0.f);
 
   for (size_t k = 0; k < 100; ++k) {
     RandomizeSampleVector(&random_generator, x[0]);
-    fft.PaddedFft(x[0], x_old, &X);
-    render_buffer.Insert(x);
-    analyzer.Update(render_buffer, 0);
+
+    render_delay_buffer->Insert(x);
+    if (k == 0) {
+      render_delay_buffer->Reset();
+    }
+    render_delay_buffer->PrepareCaptureCall();
+
+    analyzer.Update(render_delay_buffer->GetRenderBuffer(),
+                    rtc::Optional<size_t>(0));
   }
 
   mask.fill(1.f);
@@ -86,8 +90,11 @@
   std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
   std::array<float, kBlockSize> x_old;
   Aec3Fft fft;
-  RenderBuffer render_buffer(Aec3Optimization::kNone, 3, 1,
-                             std::vector<size_t>(1, 1));
+  EchoCanceller3Config config;
+  config.delay.min_echo_path_delay_blocks = 0;
+  std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
+      RenderDelayBuffer::Create(config, 3));
+
   std::array<float, kFftLengthBy2Plus1> mask;
   x_old.fill(0.f);
   constexpr int kSinusFrequencyBin = 32;
@@ -97,9 +104,15 @@
     for (size_t k = 0; k < 100; ++k) {
       ProduceSinusoid(16000, 16000 / 2 * kSinusFrequencyBin / kFftLengthBy2,
                       &sample_counter, x[0]);
-      render_buffer.Insert(x);
-      analyzer.Update(render_buffer, known_delay ? rtc::Optional<size_t>(0)
-                                                 : rtc::nullopt);
+
+      render_delay_buffer->Insert(x);
+      if (k == 0) {
+        render_delay_buffer->Reset();
+      }
+      render_delay_buffer->PrepareCaptureCall();
+
+      analyzer.Update(render_delay_buffer->GetRenderBuffer(),
+                      known_delay ? rtc::Optional<size_t>(0) : rtc::nullopt);
     }
   };
 
diff --git a/modules/audio_processing/aec3/residual_echo_estimator_unittest.cc b/modules/audio_processing/aec3/residual_echo_estimator_unittest.cc
index b85bc1d..92669c8 100644
--- a/modules/audio_processing/aec3/residual_echo_estimator_unittest.cc
+++ b/modules/audio_processing/aec3/residual_echo_estimator_unittest.cc
@@ -12,6 +12,7 @@
 
 #include "modules/audio_processing/aec3/aec3_fft.h"
 #include "modules/audio_processing/aec3/aec_state.h"
+#include "modules/audio_processing/aec3/render_delay_buffer.h"
 #include "modules/audio_processing/include/audio_processing.h"
 #include "modules/audio_processing/test/echo_canceller_test_tools.h"
 #include "rtc_base/random.h"
@@ -23,38 +24,43 @@
 
 // Verifies that the check for non-null output residual echo power works.
 TEST(ResidualEchoEstimator, NullResidualEchoPowerOutput) {
-  AecState aec_state(EchoCanceller3Config{});
-  RenderBuffer render_buffer(Aec3Optimization::kNone, 3, 10,
-                             std::vector<size_t>(1, 10));
+  EchoCanceller3Config config;
+  AecState aec_state(config);
+  std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
+      RenderDelayBuffer::Create(config, 3));
   std::vector<std::array<float, kFftLengthBy2Plus1>> H2;
   std::array<float, kFftLengthBy2Plus1> S2_linear;
   std::array<float, kFftLengthBy2Plus1> Y2;
   EXPECT_DEATH(ResidualEchoEstimator(EchoCanceller3Config{})
-                   .Estimate(aec_state, render_buffer, S2_linear, Y2, nullptr),
+                   .Estimate(aec_state, render_delay_buffer->GetRenderBuffer(),
+                             S2_linear, Y2, nullptr),
                "");
 }
 
 #endif
 
-TEST(ResidualEchoEstimator, BasicTest) {
-  ResidualEchoEstimator estimator(EchoCanceller3Config{});
+// 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) {
   EchoCanceller3Config config;
   config.ep_strength.default_len = 0.f;
+  config.delay.min_echo_path_delay_blocks = 0;
+  ResidualEchoEstimator estimator(config);
   AecState aec_state(config);
-  RenderBuffer render_buffer(Aec3Optimization::kNone, 3, 10,
-                             std::vector<size_t>(1, 10));
+  std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
+      RenderDelayBuffer::Create(config, 3));
+
   std::array<float, kFftLengthBy2Plus1> E2_main;
   std::array<float, kFftLengthBy2Plus1> E2_shadow;
   std::array<float, kFftLengthBy2Plus1> S2_linear;
   std::array<float, kFftLengthBy2Plus1> S2_fallback;
   std::array<float, kFftLengthBy2Plus1> Y2;
   std::array<float, kFftLengthBy2Plus1> R2;
-  EchoPathVariability echo_path_variability(false, false);
+  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::array<float, kFftLengthBy2Plus1>> H2(10);
   Random random_generator(42U);
-  FftData X;
-  std::array<float, kBlockSize> x_old;
   std::array<float, kBlockSize> s;
   Aec3Fft fft;
 
@@ -76,17 +82,21 @@
   S2_fallback.fill(kLevel);
   Y2.fill(kLevel);
 
-  for (int k = 0; k < 2000; ++k) {
+  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; });
-    fft.PaddedFft(x[0], x_old, &X);
-    render_buffer.Insert(x);
+    render_delay_buffer->Insert(x);
+    if (k == 0) {
+      render_delay_buffer->Reset();
+    }
+    render_delay_buffer->PrepareCaptureCall();
 
     aec_state.HandleEchoPathChange(echo_path_variability);
-    aec_state.Update(H2, h, true, 2, render_buffer, E2_main, Y2, x[0], s,
-                     false);
+    aec_state.Update(H2, h, true, 2, render_delay_buffer->GetRenderBuffer(),
+                     E2_main, Y2, x[0], s, false);
 
-    estimator.Estimate(aec_state, render_buffer, S2_linear, Y2, &R2);
+    estimator.Estimate(aec_state, render_delay_buffer->GetRenderBuffer(),
+                       S2_linear, Y2, &R2);
   }
   std::for_each(R2.begin(), R2.end(),
                 [&](float a) { EXPECT_NEAR(kLevel, a, 0.1f); });
diff --git a/modules/audio_processing/aec3/shadow_filter_update_gain.cc b/modules/audio_processing/aec3/shadow_filter_update_gain.cc
index db393a7..464a026 100644
--- a/modules/audio_processing/aec3/shadow_filter_update_gain.cc
+++ b/modules/audio_processing/aec3/shadow_filter_update_gain.cc
@@ -51,7 +51,7 @@
   constexpr float kNoiseGatePower = 220075344.f;
   constexpr float kMuFixed = .5f;
   std::array<float, kFftLengthBy2Plus1> mu;
-  const auto& X2 = render_buffer.SpectralSum(size_partitions);
+  auto X2 = render_buffer.SpectralSum(size_partitions);
   std::transform(X2.begin(), X2.end(), mu.begin(), [&](float a) {
     return a > kNoiseGatePower ? kMuFixed / a : 0.f;
   });
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 b89fc71..41645ae 100644
--- a/modules/audio_processing/aec3/shadow_filter_update_gain_unittest.cc
+++ b/modules/audio_processing/aec3/shadow_filter_update_gain_unittest.cc
@@ -18,6 +18,7 @@
 #include "modules/audio_processing/aec3/adaptive_fir_filter.h"
 #include "modules/audio_processing/aec3/aec3_common.h"
 #include "modules/audio_processing/aec3/aec_state.h"
+#include "modules/audio_processing/aec3/render_delay_buffer.h"
 #include "modules/audio_processing/test/echo_canceller_test_tools.h"
 #include "rtc_base/numerics/safe_minmax.h"
 #include "rtc_base/random.h"
@@ -35,19 +36,22 @@
                          std::array<float, kBlockSize>* y_last_block,
                          FftData* G_last_block) {
   ApmDataDumper data_dumper(42);
-  AdaptiveFirFilter main_filter(9, DetectOptimization(), &data_dumper);
-  AdaptiveFirFilter shadow_filter(9, DetectOptimization(), &data_dumper);
+  AdaptiveFirFilter main_filter(12, DetectOptimization(), &data_dumper);
+  AdaptiveFirFilter shadow_filter(12, DetectOptimization(), &data_dumper);
   Aec3Fft fft;
-  RenderBuffer render_buffer(
-      Aec3Optimization::kNone, 3, main_filter.SizePartitions(),
-      std::vector<size_t>(1, main_filter.SizePartitions()));
+
+  EchoCanceller3Config config;
+  config.delay.min_echo_path_delay_blocks = 0;
+  std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
+      RenderDelayBuffer::Create(config, 3));
+
   std::array<float, kBlockSize> x_old;
   x_old.fill(0.f);
   ShadowFilterUpdateGain shadow_gain;
   Random random_generator(42U);
   std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
   std::vector<float> y(kBlockSize, 0.f);
-  AecState aec_state(EchoCanceller3Config{});
+  AecState aec_state(config);
   RenderSignalAnalyzer render_signal_analyzer;
   std::array<float, kFftLength> s;
   FftData S;
@@ -67,10 +71,17 @@
     // Create the render signal.
     RandomizeSampleVector(&random_generator, x[0]);
     delay_buffer.Delay(x[0], y);
-    render_buffer.Insert(x);
-    render_signal_analyzer.Update(render_buffer, delay_samples / kBlockSize);
 
-    shadow_filter.Filter(render_buffer, &S);
+    render_delay_buffer->Insert(x);
+    if (k == 0) {
+      render_delay_buffer->Reset();
+    }
+    render_delay_buffer->PrepareCaptureCall();
+
+    render_signal_analyzer.Update(render_delay_buffer->GetRenderBuffer(),
+                                  delay_samples / kBlockSize);
+
+    shadow_filter.Filter(render_delay_buffer->GetRenderBuffer(), &S);
     fft.Ifft(S, &s);
     std::transform(y.begin(), y.end(), s.begin() + kFftLengthBy2,
                    e_shadow.begin(),
@@ -79,9 +90,10 @@
                   [](float& a) { a = rtc::SafeClamp(a, -32768.f, 32767.f); });
     fft.ZeroPaddedFft(e_shadow, &E_shadow);
 
-    shadow_gain.Compute(render_buffer, render_signal_analyzer, E_shadow,
+    shadow_gain.Compute(render_delay_buffer->GetRenderBuffer(),
+                        render_signal_analyzer, E_shadow,
                         shadow_filter.SizePartitions(), saturation, &G);
-    shadow_filter.Adapt(render_buffer, G);
+    shadow_filter.Adapt(render_delay_buffer->GetRenderBuffer(), G);
   }
 
   std::copy(e_shadow.begin(), e_shadow.end(), e_last_block->begin());
@@ -103,8 +115,10 @@
 // Verifies that the check for non-null output gain parameter works.
 TEST(ShadowFilterUpdateGain, NullDataOutputGain) {
   ApmDataDumper data_dumper(42);
-  RenderBuffer render_buffer(Aec3Optimization::kNone, 3, 1,
-                             std::vector<size_t>(1, 1));
+  FftBuffer fft_buffer(1);
+  MatrixBuffer block_buffer(fft_buffer.buffer.size(), 3, kBlockSize);
+  VectorBuffer spectrum_buffer(fft_buffer.buffer.size(), kFftLengthBy2Plus1);
+  RenderBuffer render_buffer(1, &block_buffer, &spectrum_buffer, &fft_buffer);
   RenderSignalAnalyzer analyzer;
   FftData E;
   ShadowFilterUpdateGain gain;
@@ -151,9 +165,9 @@
   RunFilterUpdateTest(200, 65, blocks_with_saturation, &e, &y, &G_b);
   RunFilterUpdateTest(300, 65, 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.));
diff --git a/modules/audio_processing/aec3/subtractor.cc b/modules/audio_processing/aec3/subtractor.cc
index b374f49..3c99b98 100644
--- a/modules/audio_processing/aec3/subtractor.cc
+++ b/modules/audio_processing/aec3/subtractor.cc
@@ -59,14 +59,28 @@
 
 void Subtractor::HandleEchoPathChange(
     const EchoPathVariability& echo_path_variability) {
-  use_shadow_filter_frequency_response_ = false;
-  if (echo_path_variability.delay_change) {
+  const auto full_reset = [&]() {
+    use_shadow_filter_frequency_response_ = false;
     main_filter_.HandleEchoPathChange();
     shadow_filter_.HandleEchoPathChange();
-    G_main_.HandleEchoPathChange();
+    G_main_.HandleEchoPathChange(echo_path_variability);
     G_shadow_.HandleEchoPathChange();
     converged_filter_ = false;
     converged_filter_counter_ = 0;
+  };
+
+  // TODO(peah): Add delay-change specific reset behavior.
+  if ((echo_path_variability.delay_change ==
+       EchoPathVariability::DelayAdjustment::kBufferFlush) ||
+      (echo_path_variability.delay_change ==
+       EchoPathVariability::DelayAdjustment::kDelayReset)) {
+    full_reset();
+  } else if (echo_path_variability.delay_change ==
+             EchoPathVariability::DelayAdjustment::kNewDetectedDelay) {
+    full_reset();
+  } else if (echo_path_variability.delay_change ==
+             EchoPathVariability::DelayAdjustment::kBufferReadjustment) {
+    full_reset();
   }
 }
 
@@ -120,8 +134,8 @@
   }
 
   // Compute spectra for future use.
-  E_main.Spectrum(optimization_, &output->E2_main);
-  E_shadow.Spectrum(optimization_, &output->E2_shadow);
+  E_main.Spectrum(optimization_, output->E2_main);
+  E_shadow.Spectrum(optimization_, output->E2_shadow);
 
   // Update the main filter.
   G_main_.Compute(render_buffer, render_signal_analyzer, *output, main_filter_,
diff --git a/modules/audio_processing/aec3/subtractor_unittest.cc b/modules/audio_processing/aec3/subtractor_unittest.cc
index b10421b..bd8c777 100644
--- a/modules/audio_processing/aec3/subtractor_unittest.cc
+++ b/modules/audio_processing/aec3/subtractor_unittest.cc
@@ -15,6 +15,7 @@
 #include <string>
 
 #include "modules/audio_processing/aec3/aec_state.h"
+#include "modules/audio_processing/aec3/render_delay_buffer.h"
 #include "modules/audio_processing/test/echo_canceller_test_tools.h"
 #include "rtc_base/random.h"
 #include "test/gtest.h"
@@ -32,8 +33,10 @@
   std::vector<float> y(kBlockSize, 0.f);
   std::array<float, kBlockSize> x_old;
   SubtractorOutput output;
-  RenderBuffer render_buffer(Aec3Optimization::kNone, 3, kAdaptiveFilterLength,
-                             std::vector<size_t>(1, kAdaptiveFilterLength));
+  EchoCanceller3Config config;
+  config.delay.min_echo_path_delay_blocks = 0;
+  std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
+      RenderDelayBuffer::Create(config, 3));
   RenderSignalAnalyzer render_signal_analyzer;
   Random random_generator(42U);
   Aec3Fft fft;
@@ -54,23 +57,33 @@
     } else {
       delay_buffer.Delay(x[0], y);
     }
-    render_buffer.Insert(x);
-    render_signal_analyzer.Update(render_buffer, aec_state.FilterDelay());
+    render_delay_buffer->Insert(x);
+    if (k == 0) {
+      render_delay_buffer->Reset();
+    }
+    render_delay_buffer->PrepareCaptureCall();
+    render_signal_analyzer.Update(render_delay_buffer->GetRenderBuffer(),
+                                  aec_state.FilterDelay());
 
     // Handle echo path changes.
     if (std::find(blocks_with_echo_path_changes.begin(),
                   blocks_with_echo_path_changes.end(),
                   k) != blocks_with_echo_path_changes.end()) {
-      subtractor.HandleEchoPathChange(EchoPathVariability(true, true));
+      subtractor.HandleEchoPathChange(EchoPathVariability(
+          true, EchoPathVariability::DelayAdjustment::kNewDetectedDelay,
+          false));
     }
-    subtractor.Process(render_buffer, y, render_signal_analyzer, aec_state,
-                       &output);
+    subtractor.Process(render_delay_buffer->GetRenderBuffer(), y,
+                       render_signal_analyzer, aec_state, &output);
 
-    aec_state.HandleEchoPathChange(EchoPathVariability(false, false));
+    aec_state.HandleEchoPathChange(EchoPathVariability(
+        false, EchoPathVariability::DelayAdjustment::kNone, false));
     aec_state.Update(subtractor.FilterFrequencyResponse(),
                      subtractor.FilterImpulseResponse(),
-                     subtractor.ConvergedFilter(), delay_samples / kBlockSize,
-                     render_buffer, E2_main, Y2, x[0], output.s_main, false);
+                     subtractor.ConvergedFilter(),
+                     rtc::Optional<size_t>(delay_samples / kBlockSize),
+                     render_delay_buffer->GetRenderBuffer(), E2_main, Y2, x[0],
+                     output.s_main, false);
   }
 
   const float output_power = std::inner_product(
@@ -104,12 +117,13 @@
 TEST(Subtractor, DISABLED_NullOutput) {
   ApmDataDumper data_dumper(42);
   Subtractor subtractor(&data_dumper, DetectOptimization());
-  RenderBuffer render_buffer(Aec3Optimization::kNone, 3, kAdaptiveFilterLength,
-                             std::vector<size_t>(1, kAdaptiveFilterLength));
+  std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
+      RenderDelayBuffer::Create(EchoCanceller3Config(), 3));
   RenderSignalAnalyzer render_signal_analyzer;
   std::vector<float> y(kBlockSize, 0.f);
 
-  EXPECT_DEATH(subtractor.Process(render_buffer, y, render_signal_analyzer,
+  EXPECT_DEATH(subtractor.Process(render_delay_buffer->GetRenderBuffer(), y,
+                                  render_signal_analyzer,
                                   AecState(EchoCanceller3Config{}), nullptr),
                "");
 }
@@ -118,13 +132,14 @@
 TEST(Subtractor, WrongCaptureSize) {
   ApmDataDumper data_dumper(42);
   Subtractor subtractor(&data_dumper, DetectOptimization());
-  RenderBuffer render_buffer(Aec3Optimization::kNone, 3, kAdaptiveFilterLength,
-                             std::vector<size_t>(1, kAdaptiveFilterLength));
+  std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
+      RenderDelayBuffer::Create(EchoCanceller3Config(), 3));
   RenderSignalAnalyzer render_signal_analyzer;
   std::vector<float> y(kBlockSize - 1, 0.f);
   SubtractorOutput output;
 
-  EXPECT_DEATH(subtractor.Process(render_buffer, y, render_signal_analyzer,
+  EXPECT_DEATH(subtractor.Process(render_delay_buffer->GetRenderBuffer(), y,
+                                  render_signal_analyzer,
                                   AecState(EchoCanceller3Config{}), &output),
                "");
 }
diff --git a/modules/audio_processing/aec3/suppression_gain_unittest.cc b/modules/audio_processing/aec3/suppression_gain_unittest.cc
index 9fee6a2..2b5e702 100644
--- a/modules/audio_processing/aec3/suppression_gain_unittest.cc
+++ b/modules/audio_processing/aec3/suppression_gain_unittest.cc
@@ -11,7 +11,7 @@
 #include "modules/audio_processing/aec3/suppression_gain.h"
 
 #include "modules/audio_processing/aec3/aec_state.h"
-#include "modules/audio_processing/aec3/render_buffer.h"
+#include "modules/audio_processing/aec3/render_delay_buffer.h"
 #include "modules/audio_processing/aec3/subtractor.h"
 #include "modules/audio_processing/logging/apm_data_dumper.h"
 #include "rtc_base/checks.h"
@@ -57,13 +57,12 @@
   std::array<float, kFftLengthBy2Plus1> g;
   std::array<float, kBlockSize> s;
   std::vector<std::vector<float>> x(1, std::vector<float>(kBlockSize, 0.f));
-  AecState aec_state(EchoCanceller3Config{});
+  EchoCanceller3Config config;
+  AecState aec_state(config);
   ApmDataDumper data_dumper(42);
   Subtractor subtractor(&data_dumper, DetectOptimization());
-  RenderBuffer render_buffer(
-      DetectOptimization(), 1,
-      std::max(kUnknownDelayRenderWindowSize, kAdaptiveFilterLength),
-      std::vector<size_t>(1, kAdaptiveFilterLength));
+  std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
+      RenderDelayBuffer::Create(config, 3));
 
   // Verify the functionality for forcing a zero gain.
   E2.fill(1000000000.f);
@@ -72,7 +71,8 @@
   s.fill(10.f);
   aec_state.Update(
       subtractor.FilterFrequencyResponse(), subtractor.FilterImpulseResponse(),
-      subtractor.ConvergedFilter(), 10, render_buffer, E2, Y2, x[0], s, false);
+      subtractor.ConvergedFilter(), 10, render_delay_buffer->GetRenderBuffer(),
+      E2, Y2, x[0], s, false);
   suppression_gain.GetGain(E2, R2, N2, analyzer, aec_state, x, &high_bands_gain,
                            &g);
   std::for_each(g.begin(), g.end(), [](float a) { EXPECT_FLOAT_EQ(0.f, a); });
@@ -85,17 +85,17 @@
   N2.fill(100.f);
   // Ensure that the gain is no longer forced to zero.
   for (int k = 0; k <= kNumBlocksPerSecond / 5 + 1; ++k) {
-    aec_state.Update(subtractor.FilterFrequencyResponse(),
-                     subtractor.FilterImpulseResponse(),
-                     subtractor.ConvergedFilter(), 10, render_buffer, E2, Y2,
-                     x[0], s, false);
+    aec_state.Update(
+        subtractor.FilterFrequencyResponse(),
+        subtractor.FilterImpulseResponse(), subtractor.ConvergedFilter(), 10,
+        render_delay_buffer->GetRenderBuffer(), E2, Y2, x[0], s, false);
   }
 
   for (int k = 0; k < 100; ++k) {
-    aec_state.Update(subtractor.FilterFrequencyResponse(),
-                     subtractor.FilterImpulseResponse(),
-                     subtractor.ConvergedFilter(), 10, render_buffer, E2, Y2,
-                     x[0], s, false);
+    aec_state.Update(
+        subtractor.FilterFrequencyResponse(),
+        subtractor.FilterImpulseResponse(), subtractor.ConvergedFilter(), 10,
+        render_delay_buffer->GetRenderBuffer(), E2, Y2, x[0], s, false);
     suppression_gain.GetGain(E2, R2, N2, analyzer, aec_state, x,
                              &high_bands_gain, &g);
   }
@@ -108,10 +108,10 @@
   R2.fill(0.1f);
   N2.fill(0.f);
   for (int k = 0; k < 100; ++k) {
-    aec_state.Update(subtractor.FilterFrequencyResponse(),
-                     subtractor.FilterImpulseResponse(),
-                     subtractor.ConvergedFilter(), 10, render_buffer, E2, Y2,
-                     x[0], s, false);
+    aec_state.Update(
+        subtractor.FilterFrequencyResponse(),
+        subtractor.FilterImpulseResponse(), subtractor.ConvergedFilter(), 10,
+        render_delay_buffer->GetRenderBuffer(), E2, Y2, x[0], s, false);
     suppression_gain.GetGain(E2, R2, N2, analyzer, aec_state, x,
                              &high_bands_gain, &g);
   }
diff --git a/modules/audio_processing/aec3/vector_buffer.cc b/modules/audio_processing/aec3/vector_buffer.cc
new file mode 100644
index 0000000..5fd6646
--- /dev/null
+++ b/modules/audio_processing/aec3/vector_buffer.cc
@@ -0,0 +1,26 @@
+/*
+ *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "modules/audio_processing/aec3/vector_buffer.h"
+
+#include "modules/audio_processing/aec3/aec3_common.h"
+
+namespace webrtc {
+
+VectorBuffer::VectorBuffer(size_t size, size_t height)
+    : size(size), buffer(size, std::vector<float>(height, 0.f)) {
+  for (auto& c : buffer) {
+    std::fill(c.begin(), c.end(), 0.f);
+  }
+}
+
+VectorBuffer::~VectorBuffer() = default;
+
+}  // namespace webrtc
diff --git a/modules/audio_processing/aec3/vector_buffer.h b/modules/audio_processing/aec3/vector_buffer.h
new file mode 100644
index 0000000..9dce1ef
--- /dev/null
+++ b/modules/audio_processing/aec3/vector_buffer.h
@@ -0,0 +1,54 @@
+/*
+ *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef MODULES_AUDIO_PROCESSING_AEC3_VECTOR_BUFFER_H_
+#define MODULES_AUDIO_PROCESSING_AEC3_VECTOR_BUFFER_H_
+
+#include <vector>
+
+#include "rtc_base/checks.h"
+
+namespace webrtc {
+
+// Struct for bundling a circular buffer of one dimensional vector objects
+// together with the read and write indices.
+struct VectorBuffer {
+  VectorBuffer(size_t size, size_t height);
+  ~VectorBuffer();
+
+  size_t IncIndex(size_t index) {
+    return index < buffer.size() - 1 ? index + 1 : 0;
+  }
+
+  size_t DecIndex(size_t index) {
+    return index > 0 ? index - 1 : buffer.size() - 1;
+  }
+
+  size_t OffsetIndex(size_t index, int offset) {
+    RTC_DCHECK_GE(buffer.size(), offset);
+    return (buffer.size() + index + offset) % buffer.size();
+  }
+
+  void UpdateWriteIndex(int offset) { write = OffsetIndex(write, offset); }
+  void IncWriteIndex() { write = IncIndex(write); }
+  void DecWriteIndex() { write = DecIndex(write); }
+  void UpdateReadIndex(int offset) { read = OffsetIndex(read, offset); }
+  void IncReadIndex() { read = IncIndex(read); }
+  void DecReadIndex() { read = DecIndex(read); }
+
+  size_t size;
+  std::vector<std::vector<float>> buffer;
+  size_t write = 0;
+  size_t read = 0;
+};
+
+}  // namespace webrtc
+
+#endif  // MODULES_AUDIO_PROCESSING_AEC3_VECTOR_BUFFER_H_
diff --git a/modules/audio_processing/include/audio_processing.h b/modules/audio_processing/include/audio_processing.h
index d3a1ef5..a639851 100644
--- a/modules/audio_processing/include/audio_processing.h
+++ b/modules/audio_processing/include/audio_processing.h
@@ -1154,6 +1154,8 @@
     size_t default_delay = 5;
     size_t down_sampling_factor = 4;
     size_t num_filters = 4;
+    size_t api_call_jitter_blocks = 26;
+    size_t min_echo_path_delay_blocks = 5;
   } delay;
 
   struct Erle {