Corrections of the render buffering scheme in AEC3 to ensure causality
This CL modifies the refactored render buffering scheme in AEC3
so that:
-A non-causal state can never occur which means that situations with
nonrecoverable echo should not occur.
-For a stable audio pipeline with a predefined API call jitter,
render overruns and underruns can never occur.
Bug: webrtc:8629,chromium:793305
Change-Id: I06ba1c368f92db95274090b08475dd02dbb85145
Reviewed-on: https://webrtc-review.googlesource.com/29861
Commit-Queue: Per Åhgren <peah@webrtc.org>
Reviewed-by: Gustaf Ullberg <gustaf@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#21215}
diff --git a/modules/audio_processing/aec3/render_delay_controller.cc b/modules/audio_processing/aec3/render_delay_controller.cc
index 05121f2..e37207e 100644
--- a/modules/audio_processing/aec3/render_delay_controller.cc
+++ b/modules/audio_processing/aec3/render_delay_controller.cc
@@ -28,12 +28,13 @@
class RenderDelayControllerImpl final : public RenderDelayController {
public:
RenderDelayControllerImpl(const EchoCanceller3Config& config,
+ int non_causal_offset,
int sample_rate_hz);
~RenderDelayControllerImpl() override;
void Reset() override;
void SetDelay(size_t render_delay) override;
- size_t GetDelay(const DownsampledRenderBuffer& render_buffer,
- rtc::ArrayView<const float> capture) override;
+ rtc::Optional<size_t> GetDelay(const DownsampledRenderBuffer& render_buffer,
+ rtc::ArrayView<const float> capture) override;
rtc::Optional<size_t> AlignmentHeadroomSamples() const override {
return headroom_samples_;
}
@@ -41,32 +42,30 @@
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_;
+ rtc::Optional<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;
rtc::Optional<size_t> headroom_samples_;
- std::vector<float> capture_delay_buffer_;
- int capture_delay_buffer_index_ = 0;
+ std::vector<float> delay_buf_;
+ int delay_buf_index_ = 0;
RenderDelayControllerMetrics metrics_;
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RenderDelayControllerImpl);
};
-size_t ComputeNewBufferDelay(size_t current_delay,
- size_t echo_path_delay_samples) {
+size_t ComputeNewBufferDelay(rtc::Optional<size_t> current_delay,
+ size_t delay_samples) {
// The below division is not exact and the truncation is intended.
- const int echo_path_delay_blocks = echo_path_delay_samples / kBlockSize;
+ const int echo_path_delay_blocks = delay_samples >> kBlockSizeLog2;
constexpr int kDelayHeadroomBlocks = 1;
// Compute the buffer delay increase required to achieve the desired latency.
size_t new_delay = std::max(echo_path_delay_blocks - kDelayHeadroomBlocks, 0);
// Add hysteresis.
- if (new_delay == current_delay + 1) {
- new_delay = current_delay;
+ if (current_delay) {
+ if (new_delay == *current_delay + 1) {
+ new_delay = *current_delay;
+ }
}
return new_delay;
@@ -76,32 +75,24 @@
RenderDelayControllerImpl::RenderDelayControllerImpl(
const EchoCanceller3Config& config,
+ int non_causal_offset,
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, min_echo_path_delay_)),
- delay_(default_delay_),
delay_estimator_(data_dumper_.get(), config),
- echo_path_delay_samples_(default_delay_ * kBlockSize),
- capture_delay_buffer_(
- kBlockSize * (config.delay.api_call_jitter_blocks + 2),
- 0.f) {
+ delay_buf_(kBlockSize * non_causal_offset, 0.f) {
RTC_DCHECK(ValidFullBandRate(sample_rate_hz));
delay_estimator_.LogDelayEstimationProperties(sample_rate_hz,
- capture_delay_buffer_.size());
+ delay_buf_.size());
}
RenderDelayControllerImpl::~RenderDelayControllerImpl() = default;
void RenderDelayControllerImpl::Reset() {
- delay_ = default_delay_;
- blocks_since_last_delay_estimate_ = 300000;
- echo_path_delay_samples_ = delay_ * kBlockSize;
+ delay_ = rtc::nullopt;
align_call_counter_ = 0;
headroom_samples_ = rtc::nullopt;
- std::fill(capture_delay_buffer_.begin(), capture_delay_buffer_.end(), 0.f);
+ std::fill(delay_buf_.begin(), delay_buf_.end(), 0.f);
delay_estimator_.Reset();
}
@@ -114,60 +105,43 @@
}
}
-size_t RenderDelayControllerImpl::GetDelay(
+rtc::Optional<size_t> RenderDelayControllerImpl::GetDelay(
const DownsampledRenderBuffer& render_buffer,
rtc::ArrayView<const float> capture) {
RTC_DCHECK_EQ(kBlockSize, capture.size());
-
++align_call_counter_;
- // Estimate the delay with a delayed capture signal in order to catch
- // noncausal delays.
- RTC_DCHECK_LT(capture_delay_buffer_index_ + kBlockSize - 1,
- capture_delay_buffer_.size());
- const rtc::Optional<size_t> echo_path_delay_samples_shifted =
- delay_estimator_.EstimateDelay(
- render_buffer,
- rtc::ArrayView<const float>(
- &capture_delay_buffer_[capture_delay_buffer_index_], kBlockSize));
+ // Estimate the delay with a delayed capture.
+ RTC_DCHECK_LT(delay_buf_index_ + kBlockSize - 1, delay_buf_.size());
+ rtc::ArrayView<const float> capture_delayed(&delay_buf_[delay_buf_index_],
+ kBlockSize);
+ auto delay_samples =
+ delay_estimator_.EstimateDelay(render_buffer, capture_delayed);
+
std::copy(capture.begin(), capture.end(),
- capture_delay_buffer_.begin() + capture_delay_buffer_index_);
- capture_delay_buffer_index_ =
- (capture_delay_buffer_index_ + kBlockSize) % capture_delay_buffer_.size();
+ delay_buf_.begin() + delay_buf_index_);
+ delay_buf_index_ = (delay_buf_index_ + kBlockSize) % delay_buf_.size();
- if (echo_path_delay_samples_shifted) {
- blocks_since_last_delay_estimate_ = 0;
-
- // Correct for the capture signal delay.
- const int echo_path_delay_samples_corrected =
- static_cast<int>(*echo_path_delay_samples_shifted) -
- static_cast<int>(capture_delay_buffer_.size());
- echo_path_delay_samples_ = std::max(0, echo_path_delay_samples_corrected);
-
+ if (delay_samples) {
// Compute and set new render delay buffer delay.
- const size_t new_delay =
- ComputeNewBufferDelay(delay_, echo_path_delay_samples_);
if (align_call_counter_ > kNumBlocksPerSecond) {
- delay_ = new_delay;
-
+ delay_ = ComputeNewBufferDelay(delay_, static_cast<int>(*delay_samples));
// Update render delay buffer headroom.
- if (echo_path_delay_samples_corrected >= 0) {
- const int headroom = echo_path_delay_samples_ - delay_ * kBlockSize;
- RTC_DCHECK_LE(0, headroom);
- headroom_samples_ = headroom;
- } else {
- headroom_samples_ = rtc::nullopt;
- }
+ const int headroom =
+ static_cast<int>(*delay_samples) - *delay_ * kBlockSize;
+ RTC_DCHECK_LE(0, headroom);
+ headroom_samples_ = headroom;
}
- metrics_.Update(echo_path_delay_samples_, delay_);
+ metrics_.Update(static_cast<int>(*delay_samples), delay_ ? *delay_ : 0);
} else {
- metrics_.Update(rtc::nullopt, delay_);
+ metrics_.Update(rtc::nullopt, delay_ ? *delay_ : 0);
}
- data_dumper_->DumpRaw("aec3_render_delay_controller_delay", 1,
- &echo_path_delay_samples_);
- data_dumper_->DumpRaw("aec3_render_delay_controller_buffer_delay", delay_);
+ data_dumper_->DumpRaw("aec3_render_delay_controller_delay",
+ delay_samples ? *delay_samples : 0);
+ data_dumper_->DumpRaw("aec3_render_delay_controller_buffer_delay",
+ delay_ ? *delay_ : 0);
return delay_;
}
@@ -176,8 +150,10 @@
RenderDelayController* RenderDelayController::Create(
const EchoCanceller3Config& config,
+ int non_causal_offset,
int sample_rate_hz) {
- return new RenderDelayControllerImpl(config, sample_rate_hz);
+ return new RenderDelayControllerImpl(config, non_causal_offset,
+ sample_rate_hz);
}
} // namespace webrtc