Robustify the faster alignment in AEC3 to avoid resets

The faster AEC3 alignment introduced recently may in
cases cause the alignment (and the AEC3) to repeatedly
reset. This CL avoids these resets by handling buffer
issues (which are triggering the resets) separately
during the initial coarse alignment phase.



Change-Id: Idf5e2ffda2591906da8060d03ec8ca73cdaedf53
Bug: webrtc:8798,chromium:805815
Reviewed-on: https://webrtc-review.googlesource.com/43480
Commit-Queue: Per Åhgren <peah@webrtc.org>
Reviewed-by: Gustaf Ullberg <gustaf@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#21758}
diff --git a/modules/audio_processing/BUILD.gn b/modules/audio_processing/BUILD.gn
index 181d797..c7d36c6 100644
--- a/modules/audio_processing/BUILD.gn
+++ b/modules/audio_processing/BUILD.gn
@@ -55,6 +55,7 @@
     "aec3/comfort_noise_generator.h",
     "aec3/decimator.cc",
     "aec3/decimator.h",
+    "aec3/delay_estimate.h",
     "aec3/downsampled_render_buffer.cc",
     "aec3/downsampled_render_buffer.h",
     "aec3/echo_canceller3.cc",
diff --git a/modules/audio_processing/aec3/block_processor.cc b/modules/audio_processing/aec3/block_processor.cc
index 4c83a55..24f2c91 100644
--- a/modules/audio_processing/aec3/block_processor.cc
+++ b/modules/audio_processing/aec3/block_processor.cc
@@ -56,6 +56,7 @@
   BlockProcessorMetrics metrics_;
   RenderDelayBuffer::BufferingEvent render_event_;
   size_t capture_call_counter_ = 0;
+  rtc::Optional<DelayEstimate> estimated_delay_;
   RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(BlockProcessorImpl);
 };
 
@@ -126,14 +127,17 @@
   RTC_DCHECK(RenderDelayBuffer::BufferingEvent::kRenderOverrun !=
              render_event_);
   if (render_event_ == RenderDelayBuffer::BufferingEvent::kRenderUnderrun) {
-    echo_path_variability.delay_change =
-        EchoPathVariability::DelayAdjustment::kDelayReset;
-    delay_controller_->Reset();
-    capture_properly_started_ = false;
-    render_properly_started_ = false;
+    if (estimated_delay_ &&
+        estimated_delay_->quality == DelayEstimate::Quality::kRefined) {
+      echo_path_variability.delay_change =
+          EchoPathVariability::DelayAdjustment::kDelayReset;
+      delay_controller_->Reset();
+      capture_properly_started_ = false;
+      render_properly_started_ = false;
 
-    RTC_LOG(LS_WARNING) << "Reset due to render buffer underrrun at block "
-                        << capture_call_counter_;
+      RTC_LOG(LS_WARNING) << "Reset due to render buffer underrrun at block "
+                          << capture_call_counter_;
+    }
   } else if (render_event_ == RenderDelayBuffer::BufferingEvent::kApiCallSkew) {
     // There have been too many render calls in a row. Reset to avoid noncausal
     // echo.
@@ -152,22 +156,23 @@
 
   // Compute and and apply the render delay required to achieve proper signal
   // alignment.
-  rtc::Optional<size_t> estimated_delay = delay_controller_->GetDelay(
+  estimated_delay_ = delay_controller_->GetDelay(
       render_buffer_->GetDownsampledRenderBuffer(), (*capture_block)[0]);
 
-  if (estimated_delay) {
-    bool delay_change = render_buffer_->SetDelay(*estimated_delay);
-
-    if (delay_change) {
-      RTC_LOG(LS_WARNING) << "Delay changed to " << *estimated_delay
-                          << " at block " << capture_call_counter_;
-      if (render_buffer_->CausalDelay()) {
+  if (estimated_delay_) {
+    if (render_buffer_->CausalDelay(estimated_delay_->delay)) {
+      bool delay_change = render_buffer_->SetDelay(estimated_delay_->delay);
+      if (delay_change) {
+        RTC_LOG(LS_WARNING) << "Delay changed to " << estimated_delay_->delay
+                            << " at block " << capture_call_counter_;
         echo_path_variability.delay_change =
             EchoPathVariability::DelayAdjustment::kNewDetectedDelay;
-      } else {
-        // 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.
+      }
+    } else {
+      // A noncausal delay has been detected. This can only happen if there is
+      // clockdrift, an audio pipeline issue has occurred, an unreliable delay
+      // estimate is used or the specified minimum delay is too short.
+      if (estimated_delay_->quality == DelayEstimate::Quality::kRefined) {
         echo_path_variability.delay_change =
             EchoPathVariability::DelayAdjustment::kDelayReset;
         delay_controller_->Reset();
diff --git a/modules/audio_processing/aec3/block_processor_unittest.cc b/modules/audio_processing/aec3/block_processor_unittest.cc
index d23c1df..29b249b 100644
--- a/modules/audio_processing/aec3/block_processor_unittest.cc
+++ b/modules/audio_processing/aec3/block_processor_unittest.cc
@@ -165,8 +165,7 @@
         .Times(kNumBlocks)
         .WillRepeatedly(Return(0));
     EXPECT_CALL(*render_delay_controller_mock, GetDelay(_, _))
-        .Times(kNumBlocks)
-        .WillRepeatedly(Return(9));
+        .Times(kNumBlocks);
     EXPECT_CALL(*echo_remover_mock, ProcessCapture(_, _, _, _))
         .Times(kNumBlocks);
     EXPECT_CALL(*echo_remover_mock, UpdateEchoLeakageStatus(_))
diff --git a/modules/audio_processing/aec3/delay_estimate.h b/modules/audio_processing/aec3/delay_estimate.h
new file mode 100644
index 0000000..c3911f7
--- /dev/null
+++ b/modules/audio_processing/aec3/delay_estimate.h
@@ -0,0 +1,29 @@
+/*
+ *  Copyright (c) 2018 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_DELAY_ESTIMATE_H_
+#define MODULES_AUDIO_PROCESSING_AEC3_DELAY_ESTIMATE_H_
+
+namespace webrtc {
+
+// Stores delay_estimates.
+struct DelayEstimate {
+  enum class Quality { kCoarse, kRefined };
+
+  DelayEstimate(Quality quality, size_t delay)
+      : quality(quality), delay(delay) {}
+
+  Quality quality;
+  size_t delay;
+};
+
+}  // namespace webrtc
+
+#endif  // MODULES_AUDIO_PROCESSING_AEC3_DELAY_ESTIMATE_H_
diff --git a/modules/audio_processing/aec3/echo_path_delay_estimator.cc b/modules/audio_processing/aec3/echo_path_delay_estimator.cc
index 914f2d2..95796d5 100644
--- a/modules/audio_processing/aec3/echo_path_delay_estimator.cc
+++ b/modules/audio_processing/aec3/echo_path_delay_estimator.cc
@@ -48,7 +48,7 @@
   matched_filter_.Reset();
 }
 
-rtc::Optional<size_t> EchoPathDelayEstimator::EstimateDelay(
+rtc::Optional<DelayEstimate> EchoPathDelayEstimator::EstimateDelay(
     const DownsampledRenderBuffer& render_buffer,
     rtc::ArrayView<const float> capture) {
   RTC_DCHECK_EQ(kBlockSize, capture.size());
@@ -64,24 +64,25 @@
                         16000 / down_sampling_factor_, 1);
   matched_filter_.Update(render_buffer, downsampled_capture);
 
-  rtc::Optional<size_t> aggregated_matched_filter_lag =
+  rtc::Optional<DelayEstimate> aggregated_matched_filter_lag =
       matched_filter_lag_aggregator_.Aggregate(
           matched_filter_.GetLagEstimates());
 
   // TODO(peah): Move this logging outside of this class once EchoCanceller3
   // development is done.
-  data_dumper_->DumpRaw("aec3_echo_path_delay_estimator_delay",
-                        aggregated_matched_filter_lag
-                            ? static_cast<int>(*aggregated_matched_filter_lag *
-                                               down_sampling_factor_)
-                            : -1);
+  data_dumper_->DumpRaw(
+      "aec3_echo_path_delay_estimator_delay",
+      aggregated_matched_filter_lag
+          ? static_cast<int>(aggregated_matched_filter_lag->delay *
+                             down_sampling_factor_)
+          : -1);
 
   // Return the detected delay in samples as the aggregated matched filter lag
   // compensated by the down sampling factor for the signal being correlated.
-  return aggregated_matched_filter_lag
-             ? rtc::Optional<size_t>(*aggregated_matched_filter_lag *
-                                     down_sampling_factor_)
-             : rtc::nullopt;
+  if (aggregated_matched_filter_lag) {
+    aggregated_matched_filter_lag->delay *= down_sampling_factor_;
+  }
+  return aggregated_matched_filter_lag;
 }
 
 }  // namespace webrtc
diff --git a/modules/audio_processing/aec3/echo_path_delay_estimator.h b/modules/audio_processing/aec3/echo_path_delay_estimator.h
index 04943ca4..ef0d448 100644
--- a/modules/audio_processing/aec3/echo_path_delay_estimator.h
+++ b/modules/audio_processing/aec3/echo_path_delay_estimator.h
@@ -15,6 +15,7 @@
 
 #include "api/optional.h"
 #include "modules/audio_processing/aec3/decimator.h"
+#include "modules/audio_processing/aec3/delay_estimate.h"
 #include "modules/audio_processing/aec3/downsampled_render_buffer.h"
 #include "modules/audio_processing/aec3/matched_filter.h"
 #include "modules/audio_processing/aec3/matched_filter_lag_aggregator.h"
@@ -36,7 +37,7 @@
   void Reset();
 
   // Produce a delay estimate if such is avaliable.
-  rtc::Optional<size_t> EstimateDelay(
+  rtc::Optional<DelayEstimate> EstimateDelay(
       const DownsampledRenderBuffer& render_buffer,
       rtc::ArrayView<const float> capture);
 
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 8d6488d..dbd7c0b 100644
--- a/modules/audio_processing/aec3/echo_path_delay_estimator_unittest.cc
+++ b/modules/audio_processing/aec3/echo_path_delay_estimator_unittest.cc
@@ -72,7 +72,7 @@
           delay_samples + 2 * config.delay.api_call_jitter_blocks * 64);
       EchoPathDelayEstimator estimator(&data_dumper, config);
 
-      rtc::Optional<size_t> estimated_delay_samples;
+      rtc::Optional<DelayEstimate> estimated_delay_samples;
       for (size_t k = 0; k < (500 + (delay_samples) / kBlockSize); ++k) {
         RandomizeSampleVector(&random_generator, render[0]);
         signal_delay_buffer.Delay(render[0], capture);
@@ -92,7 +92,7 @@
         // 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 -
+                    estimated_delay_samples->delay -
                         (config.delay.api_call_jitter_blocks + 1) * 64,
                     config.delay.down_sampling_factor);
       } else {
diff --git a/modules/audio_processing/aec3/matched_filter_lag_aggregator.cc b/modules/audio_processing/aec3/matched_filter_lag_aggregator.cc
index 0a8d27c..9041924 100644
--- a/modules/audio_processing/aec3/matched_filter_lag_aggregator.cc
+++ b/modules/audio_processing/aec3/matched_filter_lag_aggregator.cc
@@ -30,7 +30,7 @@
   significant_candidate_found_ = false;
 }
 
-rtc::Optional<size_t> MatchedFilterLagAggregator::Aggregate(
+rtc::Optional<DelayEstimate> MatchedFilterLagAggregator::Aggregate(
     rtc::ArrayView<const MatchedFilter::LagEstimate> lag_estimates) {
   // Choose the strongest lag estimate as the best one.
   float best_accuracy = 0.f;
@@ -69,9 +69,9 @@
 
     if (histogram_[candidate] > 25) {
       significant_candidate_found_ = true;
-      return candidate;
+      return DelayEstimate(DelayEstimate::Quality::kRefined, candidate);
     } else if (!significant_candidate_found_) {
-      return candidate;
+      return DelayEstimate(DelayEstimate::Quality::kCoarse, candidate);
     }
   }
   return rtc::nullopt;
diff --git a/modules/audio_processing/aec3/matched_filter_lag_aggregator.h b/modules/audio_processing/aec3/matched_filter_lag_aggregator.h
index 4d72150d..86968bd 100644
--- a/modules/audio_processing/aec3/matched_filter_lag_aggregator.h
+++ b/modules/audio_processing/aec3/matched_filter_lag_aggregator.h
@@ -14,6 +14,7 @@
 #include <vector>
 
 #include "api/optional.h"
+#include "modules/audio_processing/aec3/delay_estimate.h"
 #include "modules/audio_processing/aec3/matched_filter.h"
 #include "rtc_base/constructormagic.h"
 
@@ -32,7 +33,7 @@
   void Reset();
 
   // Aggregates the provided lag estimates.
-  rtc::Optional<size_t> Aggregate(
+  rtc::Optional<DelayEstimate> Aggregate(
       rtc::ArrayView<const MatchedFilter::LagEstimate> lag_estimates);
 
  private:
diff --git a/modules/audio_processing/aec3/matched_filter_lag_aggregator_unittest.cc b/modules/audio_processing/aec3/matched_filter_lag_aggregator_unittest.cc
index ce08f1c..ce303d4 100644
--- a/modules/audio_processing/aec3/matched_filter_lag_aggregator_unittest.cc
+++ b/modules/audio_processing/aec3/matched_filter_lag_aggregator_unittest.cc
@@ -40,9 +40,10 @@
     EXPECT_TRUE(aggregator.Aggregate(lag_estimates));
   }
 
-  rtc::Optional<size_t> aggregated_lag = aggregator.Aggregate(lag_estimates);
+  rtc::Optional<DelayEstimate> aggregated_lag =
+      aggregator.Aggregate(lag_estimates);
   EXPECT_TRUE(aggregated_lag);
-  EXPECT_EQ(kLag1, *aggregated_lag);
+  EXPECT_EQ(kLag1, aggregated_lag->delay);
 
   lag_estimates[0] = MatchedFilter::LagEstimate(0.5f, true, kLag1, true);
   lag_estimates[1] = MatchedFilter::LagEstimate(1.f, true, kLag2, true);
@@ -50,13 +51,13 @@
   for (size_t k = 0; k < kNumLagsBeforeDetection; ++k) {
     aggregated_lag = aggregator.Aggregate(lag_estimates);
     EXPECT_TRUE(aggregated_lag);
-    EXPECT_EQ(kLag1, *aggregated_lag);
+    EXPECT_EQ(kLag1, aggregated_lag->delay);
   }
 
   aggregated_lag = aggregator.Aggregate(lag_estimates);
   aggregated_lag = aggregator.Aggregate(lag_estimates);
   EXPECT_TRUE(aggregated_lag);
-  EXPECT_EQ(kLag2, *aggregated_lag);
+  EXPECT_EQ(kLag2, aggregated_lag->delay);
 }
 
 // Verifies that varying lag estimates causes lag estimates to not be deemed
@@ -67,7 +68,7 @@
   std::vector<MatchedFilter::LagEstimate> lag_estimates(1);
   MatchedFilterLagAggregator aggregator(&data_dumper, 100);
 
-  rtc::Optional<size_t> aggregated_lag;
+  rtc::Optional<DelayEstimate> aggregated_lag;
   for (size_t k = 0; k < kNumLagsBeforeDetection; ++k) {
     lag_estimates[0] = MatchedFilter::LagEstimate(1.f, true, 10, true);
     aggregated_lag = aggregator.Aggregate(lag_estimates);
@@ -97,9 +98,10 @@
   MatchedFilterLagAggregator aggregator(&data_dumper, kLag);
   for (size_t k = 0; k < kNumLagsBeforeDetection * 10; ++k) {
     lag_estimates[0] = MatchedFilter::LagEstimate(1.f, true, kLag, false);
-    rtc::Optional<size_t> aggregated_lag = aggregator.Aggregate(lag_estimates);
+    rtc::Optional<DelayEstimate> aggregated_lag =
+        aggregator.Aggregate(lag_estimates);
     EXPECT_FALSE(aggregated_lag);
-    EXPECT_EQ(kLag, *aggregated_lag);
+    EXPECT_EQ(kLag, aggregated_lag->delay);
   }
 }
 
@@ -112,19 +114,19 @@
   ApmDataDumper data_dumper(0);
   std::vector<MatchedFilter::LagEstimate> lag_estimates(1);
   MatchedFilterLagAggregator aggregator(&data_dumper, std::max(kLag1, kLag2));
-  rtc::Optional<size_t> aggregated_lag;
+  rtc::Optional<DelayEstimate> aggregated_lag;
   for (size_t k = 0; k < kNumLagsBeforeDetection; ++k) {
     lag_estimates[0] = MatchedFilter::LagEstimate(1.f, true, kLag1, true);
     aggregated_lag = aggregator.Aggregate(lag_estimates);
   }
   EXPECT_TRUE(aggregated_lag);
-  EXPECT_EQ(kLag1, *aggregated_lag);
+  EXPECT_EQ(kLag1, aggregated_lag->delay);
 
   for (size_t k = 0; k < kNumLagsBeforeDetection * 40; ++k) {
     lag_estimates[0] = MatchedFilter::LagEstimate(1.f, false, kLag2, true);
     aggregated_lag = aggregator.Aggregate(lag_estimates);
     EXPECT_TRUE(aggregated_lag);
-    EXPECT_EQ(kLag1, *aggregated_lag);
+    EXPECT_EQ(kLag1, aggregated_lag->delay);
   }
 }
 
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 2d3d9dc..1ed2b40 100644
--- a/modules/audio_processing/aec3/mock/mock_render_delay_buffer.h
+++ b/modules/audio_processing/aec3/mock/mock_render_delay_buffer.h
@@ -52,7 +52,7 @@
   MOCK_METHOD0(GetRenderBuffer, RenderBuffer*());
   MOCK_CONST_METHOD0(GetDownsampledRenderBuffer,
                      const DownsampledRenderBuffer&());
-  MOCK_CONST_METHOD0(CausalDelay, bool());
+  MOCK_CONST_METHOD1(CausalDelay, bool(size_t delay));
 
  private:
   RenderBuffer* FakeGetRenderBuffer() { return &render_buffer_; }
diff --git a/modules/audio_processing/aec3/mock/mock_render_delay_controller.h b/modules/audio_processing/aec3/mock/mock_render_delay_controller.h
index 13dd8c2..4a82ae3 100644
--- a/modules/audio_processing/aec3/mock/mock_render_delay_controller.h
+++ b/modules/audio_processing/aec3/mock/mock_render_delay_controller.h
@@ -25,11 +25,10 @@
   virtual ~MockRenderDelayController() = default;
 
   MOCK_METHOD0(Reset, void());
-  MOCK_METHOD1(SetDelay, void(size_t render_delay));
   MOCK_METHOD2(
       GetDelay,
-      rtc::Optional<size_t>(const DownsampledRenderBuffer& render_buffer,
-                            rtc::ArrayView<const float> capture));
+      rtc::Optional<DelayEstimate>(const DownsampledRenderBuffer& render_buffer,
+                                   rtc::ArrayView<const float> capture));
 };
 
 }  // namespace test
diff --git a/modules/audio_processing/aec3/render_delay_buffer.cc b/modules/audio_processing/aec3/render_delay_buffer.cc
index 3181bae..1373729 100644
--- a/modules/audio_processing/aec3/render_delay_buffer.cc
+++ b/modules/audio_processing/aec3/render_delay_buffer.cc
@@ -47,7 +47,7 @@
     return low_rate_;
   }
 
-  bool CausalDelay() const override;
+  bool CausalDelay(size_t delay) const override;
 
  private:
   static int instance_count_;
@@ -304,10 +304,14 @@
 }
 
 // Returns whether the specified delay is causal.
-bool RenderDelayBufferImpl::CausalDelay() const {
-  return !internal_delay_ ||
-         *internal_delay_ >=
-             static_cast<int>(config_.delay.min_echo_path_delay_blocks);
+bool RenderDelayBufferImpl::CausalDelay(size_t delay) const {
+  // Compute the internal delay and limit the delay to the allowed range.
+  int internal_delay = MaxExternalDelayToInternalDelay(delay);
+  internal_delay =
+      std::min(MaxDelay(), static_cast<size_t>(std::max(internal_delay, 0)));
+
+  return internal_delay >=
+         static_cast<int>(config_.delay.min_echo_path_delay_blocks);
 }
 
 // Maps the externally computed delay to the delay used internally.
diff --git a/modules/audio_processing/aec3/render_delay_buffer.h b/modules/audio_processing/aec3/render_delay_buffer.h
index b7718aa..28ae32b 100644
--- a/modules/audio_processing/aec3/render_delay_buffer.h
+++ b/modules/audio_processing/aec3/render_delay_buffer.h
@@ -68,7 +68,7 @@
   virtual const DownsampledRenderBuffer& GetDownsampledRenderBuffer() const = 0;
 
   // Returns whether the current delay is noncausal.
-  virtual bool CausalDelay() const = 0;
+  virtual bool CausalDelay(size_t delay) const = 0;
 
   // Returns the maximum non calusal offset that can occur in the delay buffer.
   static int DelayEstimatorOffset(const EchoCanceller3Config& config);
diff --git a/modules/audio_processing/aec3/render_delay_controller.cc b/modules/audio_processing/aec3/render_delay_controller.cc
index 799ea6b..3bc7d62 100644
--- a/modules/audio_processing/aec3/render_delay_controller.cc
+++ b/modules/audio_processing/aec3/render_delay_controller.cc
@@ -32,9 +32,9 @@
                             int sample_rate_hz);
   ~RenderDelayControllerImpl() override;
   void Reset() override;
-  void SetDelay(size_t render_delay) override;
-  rtc::Optional<size_t> GetDelay(const DownsampledRenderBuffer& render_buffer,
-                                 rtc::ArrayView<const float> capture) override;
+  rtc::Optional<DelayEstimate> GetDelay(
+      const DownsampledRenderBuffer& render_buffer,
+      rtc::ArrayView<const float> capture) override;
 
  private:
   static int instance_count_;
@@ -42,7 +42,7 @@
   const int delay_headroom_blocks_;
   const int hysteresis_limit_1_blocks_;
   const int hysteresis_limit_2_blocks_;
-  rtc::Optional<size_t> delay_;
+  rtc::Optional<DelayEstimate> delay_;
   EchoPathDelayEstimator delay_estimator_;
   std::vector<float> delay_buf_;
   int delay_buf_index_ = 0;
@@ -50,34 +50,40 @@
   RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RenderDelayControllerImpl);
 };
 
-size_t ComputeNewBufferDelay(const rtc::Optional<size_t>& current_delay,
-                             int delay_headroom_blocks,
-                             int hysteresis_limit_1_blocks,
-                             int hysteresis_limit_2_blocks,
-                             size_t delay_samples) {
+DelayEstimate ComputeNewBufferDelay(
+    const rtc::Optional<DelayEstimate>& current_delay,
+    int delay_headroom_blocks,
+    int hysteresis_limit_1_blocks,
+    int hysteresis_limit_2_blocks,
+    DelayEstimate estimated_delay) {
   // The below division is not exact and the truncation is intended.
-  const int echo_path_delay_blocks = delay_samples >> kBlockSizeLog2;
+  const int echo_path_delay_blocks = estimated_delay.delay >> kBlockSizeLog2;
 
   // Compute the buffer delay increase required to achieve the desired latency.
-  size_t new_delay =
+  size_t new_delay_blocks =
       std::max(echo_path_delay_blocks - delay_headroom_blocks, 0);
 
+  DelayEstimate new_delay(estimated_delay.quality, new_delay_blocks);
+
   // Add hysteresis.
   if (current_delay) {
-    if (new_delay > *current_delay) {
-      if (new_delay <= *current_delay + hysteresis_limit_1_blocks) {
-        new_delay = *current_delay;
+    size_t current_delay_blocks = current_delay->delay;
+    if (new_delay_blocks > current_delay_blocks) {
+      if (new_delay_blocks <=
+          current_delay_blocks + hysteresis_limit_1_blocks) {
+        new_delay_blocks = current_delay_blocks;
       }
-    } else if (new_delay < *current_delay) {
+    } else if (new_delay_blocks < current_delay_blocks) {
       size_t hysteresis_limit = std::max(
-          static_cast<int>(*current_delay) - hysteresis_limit_2_blocks, 0);
-      if (new_delay >= hysteresis_limit) {
-        new_delay = *current_delay;
+          static_cast<int>(current_delay_blocks) - hysteresis_limit_2_blocks,
+          0);
+      if (new_delay_blocks >= hysteresis_limit) {
+        new_delay_blocks = current_delay_blocks;
       }
     }
   }
 
-  return new_delay;
+  return DelayEstimate(estimated_delay.quality, new_delay_blocks);
 }
 
 int RenderDelayControllerImpl::instance_count_ = 0;
@@ -109,16 +115,7 @@
   delay_estimator_.Reset();
 }
 
-void RenderDelayControllerImpl::SetDelay(size_t render_delay) {
-  if (delay_ != render_delay) {
-    // If a the delay set does not match the actual delay, reset the delay
-    // controller.
-    Reset();
-    delay_ = render_delay;
-  }
-}
-
-rtc::Optional<size_t> RenderDelayControllerImpl::GetDelay(
+rtc::Optional<DelayEstimate> RenderDelayControllerImpl::GetDelay(
     const DownsampledRenderBuffer& render_buffer,
     rtc::ArrayView<const float> capture) {
   RTC_DCHECK_EQ(kBlockSize, capture.size());
@@ -136,19 +133,21 @@
 
   if (delay_samples) {
     // Compute and set new render delay buffer delay.
-      delay_ = ComputeNewBufferDelay(
-          delay_, delay_headroom_blocks_, hysteresis_limit_1_blocks_,
-          hysteresis_limit_2_blocks_, static_cast<int>(*delay_samples));
 
-    metrics_.Update(static_cast<int>(*delay_samples), delay_ ? *delay_ : 0);
+    delay_ = ComputeNewBufferDelay(delay_, delay_headroom_blocks_,
+                                   hysteresis_limit_1_blocks_,
+                                   hysteresis_limit_2_blocks_, *delay_samples);
+
+    metrics_.Update(static_cast<int>(delay_samples->delay),
+                    delay_ ? delay_->delay : 0);
   } else {
-    metrics_.Update(rtc::nullopt, delay_ ? *delay_ : 0);
+    metrics_.Update(rtc::nullopt, delay_ ? delay_->delay : 0);
   }
 
   data_dumper_->DumpRaw("aec3_render_delay_controller_delay",
-                        delay_samples ? *delay_samples : 0);
+                        delay_samples ? delay_samples->delay : 0);
   data_dumper_->DumpRaw("aec3_render_delay_controller_buffer_delay",
-                        delay_ ? *delay_ : 0);
+                        delay_ ? delay_->delay : 0);
 
   return delay_;
 }
diff --git a/modules/audio_processing/aec3/render_delay_controller.h b/modules/audio_processing/aec3/render_delay_controller.h
index a90ccb0..5b1fc35 100644
--- a/modules/audio_processing/aec3/render_delay_controller.h
+++ b/modules/audio_processing/aec3/render_delay_controller.h
@@ -13,6 +13,7 @@
 
 #include "api/array_view.h"
 #include "api/optional.h"
+#include "modules/audio_processing/aec3/delay_estimate.h"
 #include "modules/audio_processing/aec3/downsampled_render_buffer.h"
 #include "modules/audio_processing/aec3/render_delay_buffer.h"
 #include "modules/audio_processing/include/audio_processing.h"
@@ -31,11 +32,8 @@
   // Resets the delay controller.
   virtual void Reset() = 0;
 
-  // Receives the externally used delay.
-  virtual void SetDelay(size_t render_delay) = 0;
-
   // Aligns the render buffer content with the capture signal.
-  virtual rtc::Optional<size_t> GetDelay(
+  virtual rtc::Optional<DelayEstimate> GetDelay(
       const DownsampledRenderBuffer& render_buffer,
       rtc::ArrayView<const float> capture) = 0;
 };
diff --git a/modules/audio_processing/aec3/render_delay_controller_unittest.cc b/modules/audio_processing/aec3/render_delay_controller_unittest.cc
index e91f7bd..656c5e8 100644
--- a/modules/audio_processing/aec3/render_delay_controller_unittest.cc
+++ b/modules/audio_processing/aec3/render_delay_controller_unittest.cc
@@ -61,9 +61,9 @@
             RenderDelayController::Create(
                 config, RenderDelayBuffer::DelayEstimatorOffset(config), rate));
         for (size_t k = 0; k < 100; ++k) {
-          EXPECT_EQ(config.delay.min_echo_path_delay_blocks,
-                    delay_controller->GetDelay(
-                        delay_buffer->GetDownsampledRenderBuffer(), block));
+          auto delay = delay_controller->GetDelay(
+              delay_buffer->GetDownsampledRenderBuffer(), block);
+          EXPECT_EQ(config.delay.min_echo_path_delay_blocks, delay->delay);
         }
       }
     }
@@ -73,7 +73,7 @@
 // Verifies the basic API call sequence.
 TEST(RenderDelayController, BasicApiCalls) {
   std::vector<float> capture_block(kBlockSize, 0.f);
-  rtc::Optional<size_t> delay_blocks = 0;
+  rtc::Optional<DelayEstimate> delay_blocks;
   for (size_t num_matched_filters = 4; num_matched_filters == 10;
        num_matched_filters++) {
     for (auto down_sampling_factor : kDownSamplingFactors) {
@@ -97,7 +97,7 @@
               render_delay_buffer->GetDownsampledRenderBuffer(), capture_block);
         }
         EXPECT_TRUE(delay_blocks);
-        EXPECT_EQ(config.delay.min_echo_path_delay_blocks, delay_blocks);
+        EXPECT_EQ(config.delay.min_echo_path_delay_blocks, delay_blocks->delay);
       }
     }
   }
@@ -120,7 +120,7 @@
             NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
 
         for (size_t delay_samples : {15, 50, 150, 200, 800, 4000}) {
-          rtc::Optional<size_t> delay_blocks;
+          rtc::Optional<DelayEstimate> delay_blocks;
           SCOPED_TRACE(ProduceDebugText(rate, delay_samples));
           std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
               RenderDelayBuffer::Create(config, NumBandsForRate(rate)));
@@ -145,7 +145,7 @@
               std::max(0, static_cast<int>(delay_samples / kBlockSize) -
                               kDelayHeadroomBlocks);
 
-          EXPECT_EQ(expected_delay_blocks, delay_blocks);
+          EXPECT_EQ(expected_delay_blocks, delay_blocks->delay);
         }
       }
     }
@@ -169,7 +169,7 @@
             NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
 
         for (int delay_samples : {-15, -50, -150, -200}) {
-          rtc::Optional<size_t> delay_blocks;
+          rtc::Optional<DelayEstimate> delay_blocks;
           SCOPED_TRACE(ProduceDebugText(rate, -delay_samples));
           std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
               RenderDelayBuffer::Create(config, NumBandsForRate(rate)));
@@ -211,7 +211,7 @@
         std::vector<std::vector<float>> render_block(
             NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
         for (size_t delay_samples : {15, 50, 300, 800}) {
-          rtc::Optional<size_t> delay_blocks;
+          rtc::Optional<DelayEstimate> delay_blocks;
           SCOPED_TRACE(ProduceDebugText(rate, delay_samples));
           std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
               RenderDelayBuffer::Create(config, NumBandsForRate(rate)));
@@ -250,7 +250,7 @@
           }
 
           ASSERT_TRUE(delay_blocks);
-          EXPECT_EQ(expected_delay_blocks, *delay_blocks);
+          EXPECT_EQ(expected_delay_blocks, delay_blocks->delay);
         }
       }
     }