From 930021d465cc1bcf7e14fd56fdbbe463a38f7678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Per=20=C3=85hgren?= Date: Fri, 15 Sep 2017 07:32:53 +0200 Subject: [PATCH] Eliminating the risk of sustained echo during capture data loss in AEC3. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This CL adds an offset to the delay estimation used in AEC3 for determining the alignment between the render and capture signals. This ensures that there is no possibility for the capture loss to cause the delay estimation to miss aligning the signals. BUG=webrtc:8247, chromium:765242 Change-Id: I526dc7971b13425a28e99d69168fd3722a4cfdae Reviewed-on: https://webrtc-review.googlesource.com/1232 Reviewed-by: Alex Loiko Commit-Queue: Per Ã…hgren Cr-Commit-Position: refs/heads/master@{#19871} --- .../aec3/render_delay_controller.cc | 49 ++++++++++++++----- .../aec3/render_delay_controller_unittest.cc | 39 +++++++++++++++ 2 files changed, 77 insertions(+), 11 deletions(-) diff --git a/modules/audio_processing/aec3/render_delay_controller.cc b/modules/audio_processing/aec3/render_delay_controller.cc index e651da9f76..9b400ff111 100644 --- a/modules/audio_processing/aec3/render_delay_controller.cc +++ b/modules/audio_processing/aec3/render_delay_controller.cc @@ -48,6 +48,8 @@ class RenderDelayControllerImpl final : public RenderDelayController { int echo_path_delay_samples_ = kMinEchoPathDelayBlocks * kBlockSize; size_t align_call_counter_ = 0; rtc::Optional headroom_samples_; + std::vector capture_delay_buffer_; + int capture_delay_buffer_index_ = 0; RenderDelayControllerMetrics metrics_; RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RenderDelayControllerImpl); }; @@ -76,7 +78,8 @@ RenderDelayControllerImpl::RenderDelayControllerImpl( int sample_rate_hz) : data_dumper_( new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))), - delay_estimator_(data_dumper_.get(), config) { + delay_estimator_(data_dumper_.get(), config), + capture_delay_buffer_(kBlockSize * (kMaxApiCallsJitterBlocks + 2), 0.f) { RTC_DCHECK(ValidFullBandRate(sample_rate_hz)); } @@ -88,7 +91,7 @@ void RenderDelayControllerImpl::Reset() { echo_path_delay_samples_ = delay_ * kBlockSize; align_call_counter_ = 0; headroom_samples_ = rtc::Optional(); - + std::fill(capture_delay_buffer_.begin(), capture_delay_buffer_.end(), 0.f); delay_estimator_.Reset(); } @@ -107,11 +110,29 @@ size_t RenderDelayControllerImpl::GetDelay( RTC_DCHECK_EQ(kBlockSize, capture.size()); ++align_call_counter_; - rtc::Optional echo_path_delay_samples = - delay_estimator_.EstimateDelay(render_buffer, capture); - if (echo_path_delay_samples) { + + // 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 echo_path_delay_samples_shifted = + delay_estimator_.EstimateDelay( + render_buffer, + rtc::ArrayView( + &capture_delay_buffer_[capture_delay_buffer_index_], kBlockSize)); + 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(); + + if (echo_path_delay_samples_shifted) { blocks_since_last_delay_estimate_ = 0; - echo_path_delay_samples_ = *echo_path_delay_samples; + + // Correct for the capture signal delay. + const int echo_path_delay_samples_corrected = + static_cast(*echo_path_delay_samples_shifted) - + static_cast(capture_delay_buffer_.size()); + echo_path_delay_samples_ = std::max(0, echo_path_delay_samples_corrected); // Compute and set new render delay buffer delay. const size_t new_delay = @@ -120,13 +141,19 @@ size_t RenderDelayControllerImpl::GetDelay( delay_ = new_delay; // Update render delay buffer headroom. - const int headroom = echo_path_delay_samples_ - delay_ * kBlockSize; - RTC_DCHECK_LE(0, headroom); - headroom_samples_ = rtc::Optional(headroom); + if (echo_path_delay_samples_corrected >= 0) { + const int headroom = echo_path_delay_samples_ - delay_ * kBlockSize; + RTC_DCHECK_LE(0, headroom); + headroom_samples_ = rtc::Optional(headroom); + } else { + headroom_samples_ = rtc::Optional(); + } } - } - metrics_.Update(echo_path_delay_samples, delay_); + metrics_.Update(rtc::Optional(echo_path_delay_samples_), delay_); + } else { + metrics_.Update(rtc::Optional(), delay_); + } data_dumper_->DumpRaw("aec3_render_delay_controller_delay", 1, &echo_path_delay_samples_); diff --git a/modules/audio_processing/aec3/render_delay_controller_unittest.cc b/modules/audio_processing/aec3/render_delay_controller_unittest.cc index 3f1d00e40c..7776b09100 100644 --- a/modules/audio_processing/aec3/render_delay_controller_unittest.cc +++ b/modules/audio_processing/aec3/render_delay_controller_unittest.cc @@ -126,6 +126,45 @@ TEST(RenderDelayController, Alignment) { } } +// Verifies that the RenderDelayController is able to properly handle noncausal +// delays. +TEST(RenderDelayController, NonCausalAlignment) { + Random random_generator(42U); + size_t delay_blocks = 0; + for (auto rate : {8000, 16000, 32000, 48000}) { + std::vector> render_block( + NumBandsForRate(rate), std::vector(kBlockSize, 0.f)); + std::vector> capture_block( + NumBandsForRate(rate), std::vector(kBlockSize, 0.f)); + + for (int delay_samples : {-15, -50, -150, -200}) { + SCOPED_TRACE(ProduceDebugText(rate, -delay_samples)); + std::unique_ptr render_delay_buffer( + RenderDelayBuffer::Create(NumBandsForRate(rate))); + std::unique_ptr delay_controller( + RenderDelayController::Create( + AudioProcessing::Config::EchoCanceller3(), rate)); + DelayBuffer signal_delay_buffer(-delay_samples); + for (int k = 0; k < (400 - delay_samples / static_cast(kBlockSize)); + ++k) { + 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(); + delay_blocks = delay_controller->GetDelay( + render_delay_buffer->GetDownsampledRenderBuffer(), + capture_block[0]); + } + + EXPECT_EQ(0u, delay_blocks); + + const rtc::Optional headroom_samples = + delay_controller->AlignmentHeadroomSamples(); + ASSERT_FALSE(headroom_samples); + } + } +} + // Verifies that the RenderDelayController is able to align the signals for // simple timeshifts between the signals when there is jitter in the API calls. TEST(RenderDelayController, AlignmentWithJitter) {