diff --git a/modules/audio_processing/aec3/block_processor.cc b/modules/audio_processing/aec3/block_processor.cc index 9116c81a9f..f2f3261489 100644 --- a/modules/audio_processing/aec3/block_processor.cc +++ b/modules/audio_processing/aec3/block_processor.cc @@ -128,6 +128,7 @@ void BlockProcessorImpl::ProcessCapture( } } else { // If no render data has yet arrived, do not process the capture signal. + render_buffer_->HandleSkippedCaptureProcessing(); return; } diff --git a/modules/audio_processing/aec3/block_processor_unittest.cc b/modules/audio_processing/aec3/block_processor_unittest.cc index 911dad4c81..d87e27a927 100644 --- a/modules/audio_processing/aec3/block_processor_unittest.cc +++ b/modules/audio_processing/aec3/block_processor_unittest.cc @@ -30,6 +30,7 @@ namespace { using ::testing::_; using ::testing::AtLeast; +using ::testing::NiceMock; using ::testing::Return; using ::testing::StrictMock; @@ -129,6 +130,14 @@ std::string ProduceDebugText(int sample_rate_hz) { return ss.Release(); } +void FillSampleVector(int call_counter, + int delay, + rtc::ArrayView samples) { + for (size_t i = 0; i < samples.size(); ++i) { + samples[i] = (call_counter - delay) * 10000.0f + i; + } +} + } // namespace // Verifies that the delay controller functionality is properly integrated with @@ -301,4 +310,77 @@ TEST(BlockProcessor, DISABLED_WrongSampleRate) { #endif +// Verifies that external delay estimator delays are applied correctly when a +// call begins with a sequence of capture blocks. +TEST(BlockProcessor, ExternalDelayAppliedCorrectlyWithInitialCaptureCalls) { + constexpr int kNumRenderChannels = 1; + constexpr int kNumCaptureChannels = 1; + constexpr int kSampleRateHz = 16000; + + EchoCanceller3Config config; + config.delay.use_external_delay_estimator = true; + + std::unique_ptr delay_buffer( + RenderDelayBuffer::Create(config, kSampleRateHz, kNumRenderChannels)); + + std::unique_ptr> + echo_remover_mock(new NiceMock()); + webrtc::test::MockEchoRemover* echo_remover_mock_pointer = + echo_remover_mock.get(); + + std::unique_ptr block_processor(BlockProcessor::Create( + config, kSampleRateHz, kNumRenderChannels, kNumCaptureChannels, + std::move(delay_buffer), /*delay_controller=*/nullptr, + std::move(echo_remover_mock))); + + std::vector>> render_block( + NumBandsForRate(kSampleRateHz), + std::vector>(kNumRenderChannels, + std::vector(kBlockSize, 0.f))); + std::vector>> capture_block( + NumBandsForRate(kSampleRateHz), + std::vector>(kNumCaptureChannels, + std::vector(kBlockSize, 0.f))); + + // Process... + // - 10 capture calls, where no render data is available, + // - 10 render calls, populating the buffer, + // - 2 capture calls, verifying that the delay was applied correctly. + constexpr int kDelayInBlocks = 5; + constexpr int kDelayInMs = 20; + block_processor->SetAudioBufferDelay(kDelayInMs); + + int capture_call_counter = 0; + int render_call_counter = 0; + for (size_t k = 0; k < 10; ++k) { + FillSampleVector(++capture_call_counter, kDelayInBlocks, + capture_block[0][0]); + block_processor->ProcessCapture(false, false, nullptr, &capture_block); + } + for (size_t k = 0; k < 10; ++k) { + FillSampleVector(++render_call_counter, 0, render_block[0][0]); + block_processor->BufferRender(render_block); + } + + EXPECT_CALL(*echo_remover_mock_pointer, ProcessCapture) + .WillRepeatedly( + [](EchoPathVariability /*echo_path_variability*/, + bool /*capture_signal_saturation*/, + const absl::optional& /*external_delay*/, + RenderBuffer* render_buffer, + std::vector>>* /*linear_output*/, + std::vector>>* capture) { + const auto& render = render_buffer->Block(0); + for (size_t i = 0; i < kBlockSize; ++i) { + EXPECT_FLOAT_EQ(render[0][0][i], (*capture)[0][0][i]); + } + }); + + FillSampleVector(++capture_call_counter, kDelayInBlocks, capture_block[0][0]); + block_processor->ProcessCapture(false, false, nullptr, &capture_block); + + FillSampleVector(++capture_call_counter, kDelayInBlocks, capture_block[0][0]); + block_processor->ProcessCapture(false, false, nullptr, &capture_block); +} + } // namespace webrtc 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 26f58cfe1e..9d7b8f4e86 100644 --- a/modules/audio_processing/aec3/mock/mock_render_delay_buffer.h +++ b/modules/audio_processing/aec3/mock/mock_render_delay_buffer.h @@ -32,6 +32,7 @@ class MockRenderDelayBuffer : public RenderDelayBuffer { Insert, (const std::vector>>& block), (override)); + MOCK_METHOD(void, HandleSkippedCaptureProcessing, (), (override)); MOCK_METHOD(RenderDelayBuffer::BufferingEvent, PrepareCaptureProcessing, (), diff --git a/modules/audio_processing/aec3/render_delay_buffer.cc b/modules/audio_processing/aec3/render_delay_buffer.cc index 10e81d8ec9..f5030e17b8 100644 --- a/modules/audio_processing/aec3/render_delay_buffer.cc +++ b/modules/audio_processing/aec3/render_delay_buffer.cc @@ -35,10 +35,16 @@ #include "rtc_base/atomic_ops.h" #include "rtc_base/checks.h" #include "rtc_base/logging.h" +#include "system_wrappers/include/field_trial.h" namespace webrtc { namespace { +bool UpdateCaptureCallCounterOnSkippedBlocks() { + return !field_trial::IsEnabled( + "WebRTC-Aec3RenderBufferCallCounterUpdateKillSwitch"); +} + class RenderDelayBufferImpl final : public RenderDelayBuffer { public: RenderDelayBufferImpl(const EchoCanceller3Config& config, @@ -51,6 +57,7 @@ class RenderDelayBufferImpl final : public RenderDelayBuffer { BufferingEvent Insert( const std::vector>>& block) override; BufferingEvent PrepareCaptureProcessing() override; + void HandleSkippedCaptureProcessing() override; bool AlignFromDelay(size_t delay) override; void AlignFromExternalDelay() override; size_t Delay() const override { return ComputeDelay(); } @@ -72,6 +79,7 @@ class RenderDelayBufferImpl final : public RenderDelayBuffer { std::unique_ptr data_dumper_; const Aec3Optimization optimization_; const EchoCanceller3Config config_; + const bool update_capture_call_counter_on_skipped_blocks_; const float render_linear_amplitude_gain_; const rtc::LoggingSeverity delay_log_level_; size_t down_sampling_factor_; @@ -122,6 +130,8 @@ RenderDelayBufferImpl::RenderDelayBufferImpl(const EchoCanceller3Config& config, new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))), optimization_(DetectOptimization()), config_(config), + update_capture_call_counter_on_skipped_blocks_( + UpdateCaptureCallCounterOnSkippedBlocks()), render_linear_amplitude_gain_( std::pow(10.0f, config_.render_levels.render_power_gain_db / 20.f)), delay_log_level_(config_.delay.log_warning_on_delay_changes @@ -243,6 +253,12 @@ RenderDelayBuffer::BufferingEvent RenderDelayBufferImpl::Insert( return event; } +void RenderDelayBufferImpl::HandleSkippedCaptureProcessing() { + if (update_capture_call_counter_on_skipped_blocks_) { + ++capture_call_counter_; + } +} + // Prepares the render buffers for processing another capture block. RenderDelayBuffer::BufferingEvent RenderDelayBufferImpl::PrepareCaptureProcessing() { diff --git a/modules/audio_processing/aec3/render_delay_buffer.h b/modules/audio_processing/aec3/render_delay_buffer.h index 0758e9dad9..79ffc4d8c9 100644 --- a/modules/audio_processing/aec3/render_delay_buffer.h +++ b/modules/audio_processing/aec3/render_delay_buffer.h @@ -48,6 +48,9 @@ class RenderDelayBuffer { // an enum indicating whether there was a special event that occurred. virtual BufferingEvent PrepareCaptureProcessing() = 0; + // Called on capture blocks where PrepareCaptureProcessing is not called. + virtual void HandleSkippedCaptureProcessing() = 0; + // Sets the buffer delay and returns a bool indicating whether the delay // changed. virtual bool AlignFromDelay(size_t delay) = 0;