diff --git a/modules/audio_processing/BUILD.gn b/modules/audio_processing/BUILD.gn index 82e6e80503..302f24350a 100644 --- a/modules/audio_processing/BUILD.gn +++ b/modules/audio_processing/BUILD.gn @@ -44,8 +44,8 @@ rtc_static_library("audio_processing") { "aec3/cascaded_biquad_filter.h", "aec3/comfort_noise_generator.cc", "aec3/comfort_noise_generator.h", - "aec3/decimator_by_4.cc", - "aec3/decimator_by_4.h", + "aec3/decimator.cc", + "aec3/decimator.h", "aec3/downsampled_render_buffer.cc", "aec3/downsampled_render_buffer.h", "aec3/echo_canceller3.cc", @@ -610,7 +610,7 @@ if (rtc_include_tests) { "aec3/block_processor_unittest.cc", "aec3/cascaded_biquad_filter_unittest.cc", "aec3/comfort_noise_generator_unittest.cc", - "aec3/decimator_by_4_unittest.cc", + "aec3/decimator_unittest.cc", "aec3/echo_canceller3_unittest.cc", "aec3/echo_path_delay_estimator_unittest.cc", "aec3/echo_path_variability_unittest.cc", diff --git a/modules/audio_processing/aec3/aec3_common.h b/modules/audio_processing/aec3/aec3_common.h index 031e9b1476..fd0059674b 100644 --- a/modules/audio_processing/aec3/aec3_common.h +++ b/modules/audio_processing/aec3/aec3_common.h @@ -48,20 +48,9 @@ constexpr size_t kSubFrameLength = 80; constexpr size_t kBlockSize = kFftLengthBy2; constexpr size_t kExtendedBlockSize = 2 * kFftLengthBy2; -constexpr size_t kSubBlockSize = 16; - -constexpr size_t kNumMatchedFilters = 4; constexpr size_t kMatchedFilterWindowSizeSubBlocks = 32; constexpr size_t kMatchedFilterAlignmentShiftSizeSubBlocks = kMatchedFilterWindowSizeSubBlocks * 3 / 4; -constexpr size_t kDownsampledRenderBufferSize = - kSubBlockSize * - (kMatchedFilterAlignmentShiftSizeSubBlocks * kNumMatchedFilters + - kMatchedFilterWindowSizeSubBlocks + - 1); - -constexpr size_t kRenderDelayBufferSize = - (3 * kDownsampledRenderBufferSize) / (4 * kSubBlockSize); constexpr size_t kMinEchoPathDelayBlocks = 5; constexpr size_t kMaxApiCallsJitterBlocks = 26; @@ -85,6 +74,20 @@ constexpr bool ValidFullBandRate(int sample_rate_hz) { sample_rate_hz == 32000 || sample_rate_hz == 48000; } +constexpr size_t GetDownSampledBufferSize(size_t down_sampling_factor, + size_t num_matched_filters) { + return kBlockSize / down_sampling_factor * + (kMatchedFilterAlignmentShiftSizeSubBlocks * num_matched_filters + + kMatchedFilterWindowSizeSubBlocks + 1); +} + +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); +} + // Detects what kind of optimizations to use for the code. Aec3Optimization DetectOptimization(); diff --git a/modules/audio_processing/aec3/block_processor.cc b/modules/audio_processing/aec3/block_processor.cc index 953b5f4c50..7de6dc2194 100644 --- a/modules/audio_processing/aec3/block_processor.cc +++ b/modules/audio_processing/aec3/block_processor.cc @@ -183,8 +183,12 @@ void BlockProcessorImpl::UpdateEchoLeakageStatus(bool leakage_detected) { BlockProcessor* BlockProcessor::Create(const EchoCanceller3Config& config, int sample_rate_hz) { - std::unique_ptr render_buffer( - RenderDelayBuffer::Create(NumBandsForRate(sample_rate_hz))); + std::unique_ptr 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 delay_controller( RenderDelayController::Create(config, sample_rate_hz)); std::unique_ptr echo_remover( diff --git a/modules/audio_processing/aec3/decimator.cc b/modules/audio_processing/aec3/decimator.cc new file mode 100644 index 0000000000..135a771a7c --- /dev/null +++ b/modules/audio_processing/aec3/decimator.cc @@ -0,0 +1,70 @@ +/* + * 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/decimator.h" + +#include "rtc_base/checks.h" + +namespace webrtc { +namespace { + +// b, a = signal.butter(2, 3400/8000.0, 'lowpass', analog=False) which are the +// same as b, a = signal.butter(2, 1700/4000.0, 'lowpass', analog=False). +const CascadedBiQuadFilter::BiQuadCoefficients kLowPassFilterCoefficients2 = { + {0.22711796f, 0.45423593f, 0.22711796f}, + {-0.27666461f, 0.18513647f}}; +constexpr int kNumFilters2 = 3; + +// b, a = signal.butter(2, 1500/8000.0, 'lowpass', analog=False) which are the +// same as b, a = signal.butter(2, 75/4000.0, 'lowpass', analog=False). +const CascadedBiQuadFilter::BiQuadCoefficients kLowPassFilterCoefficients4 = { + {0.0179f, 0.0357f, 0.0179f}, + {-1.5879f, 0.6594f}}; +constexpr int kNumFilters4 = 3; + +// b, a = signal.butter(2, 800/8000.0, 'lowpass', analog=False) which are the +// same as b, a = signal.butter(2, 400/4000.0, 'lowpass', analog=False). +const CascadedBiQuadFilter::BiQuadCoefficients kLowPassFilterCoefficients8 = { + {0.02008337f, 0.04016673f, 0.02008337f}, + {-1.56101808f, 0.64135154f}}; +constexpr int kNumFilters8 = 4; + +} // namespace + +Decimator::Decimator(size_t down_sampling_factor) + : down_sampling_factor_(down_sampling_factor), + low_pass_filter_( + down_sampling_factor_ == 4 + ? kLowPassFilterCoefficients4 + : (down_sampling_factor_ == 8 ? kLowPassFilterCoefficients8 + : kLowPassFilterCoefficients2), + down_sampling_factor_ == 4 + ? kNumFilters4 + : (down_sampling_factor_ == 8 ? kNumFilters8 : kNumFilters2)) { + RTC_DCHECK(down_sampling_factor_ == 2 || down_sampling_factor_ == 4 || + down_sampling_factor_ == 8); +} + +void Decimator::Decimate(rtc::ArrayView in, + rtc::ArrayView out) { + RTC_DCHECK_EQ(kBlockSize, in.size()); + RTC_DCHECK_EQ(kBlockSize / down_sampling_factor_, out.size()); + std::array x; + + // Limit the frequency content of the signal to avoid aliasing. + low_pass_filter_.Process(in, x); + + // Downsample the signal. + for (size_t j = 0, k = 0; j < out.size(); ++j, k += down_sampling_factor_) { + RTC_DCHECK_GT(kBlockSize, k); + out[j] = x[k]; + } +} + +} // namespace webrtc diff --git a/modules/audio_processing/aec3/decimator_by_4.h b/modules/audio_processing/aec3/decimator.h similarity index 70% rename from modules/audio_processing/aec3/decimator_by_4.h rename to modules/audio_processing/aec3/decimator.h index 023d6d22e2..7418a26fb0 100644 --- a/modules/audio_processing/aec3/decimator_by_4.h +++ b/modules/audio_processing/aec3/decimator.h @@ -8,8 +8,8 @@ * be found in the AUTHORS file in the root of the source tree. */ -#ifndef MODULES_AUDIO_PROCESSING_AEC3_DECIMATOR_BY_4_H_ -#define MODULES_AUDIO_PROCESSING_AEC3_DECIMATOR_BY_4_H_ +#ifndef MODULES_AUDIO_PROCESSING_AEC3_DECIMATOR_H_ +#define MODULES_AUDIO_PROCESSING_AEC3_DECIMATOR_H_ #include @@ -20,19 +20,20 @@ namespace webrtc { -// Provides functionality for decimating a signal by 4. -class DecimatorBy4 { +// Provides functionality for decimating a signal. +class Decimator { public: - DecimatorBy4(); + explicit Decimator(size_t down_sampling_factor); // Downsamples the signal. void Decimate(rtc::ArrayView in, rtc::ArrayView out); private: + const size_t down_sampling_factor_; CascadedBiQuadFilter low_pass_filter_; - RTC_DISALLOW_COPY_AND_ASSIGN(DecimatorBy4); + RTC_DISALLOW_COPY_AND_ASSIGN(Decimator); }; } // namespace webrtc -#endif // MODULES_AUDIO_PROCESSING_AEC3_DECIMATOR_BY_4_H_ +#endif // MODULES_AUDIO_PROCESSING_AEC3_DECIMATOR_H_ diff --git a/modules/audio_processing/aec3/decimator_by_4.cc b/modules/audio_processing/aec3/decimator_by_4.cc deleted file mode 100644 index 0db3b8f842..0000000000 --- a/modules/audio_processing/aec3/decimator_by_4.cc +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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/decimator_by_4.h" - -#include "rtc_base/checks.h" - -namespace webrtc { -namespace { - -// [B,A] = butter(2,1500/16000) which are the same as [B,A] = -// butter(2,750/8000). -const CascadedBiQuadFilter::BiQuadCoefficients kLowPassFilterCoefficients = { - {0.0179f, 0.0357f, 0.0179f}, - {-1.5879f, 0.6594f}}; - -} // namespace - -DecimatorBy4::DecimatorBy4() - : low_pass_filter_(kLowPassFilterCoefficients, 3) {} - -void DecimatorBy4::Decimate(rtc::ArrayView in, - rtc::ArrayView out) { - RTC_DCHECK_EQ(kBlockSize, in.size()); - RTC_DCHECK_EQ(kSubBlockSize, out.size()); - std::array x; - - // Limit the frequency content of the signal to avoid aliasing. - low_pass_filter_.Process(in, x); - - // Downsample the signal. - for (size_t j = 0, k = 0; j < out.size(); ++j, k += 4) { - RTC_DCHECK_GT(kBlockSize, k); - out[j] = x[k]; - } -} - -} // namespace webrtc diff --git a/modules/audio_processing/aec3/decimator_by_4_unittest.cc b/modules/audio_processing/aec3/decimator_unittest.cc similarity index 62% rename from modules/audio_processing/aec3/decimator_by_4_unittest.cc rename to modules/audio_processing/aec3/decimator_unittest.cc index 1ae2c5b95c..e77a990f9c 100644 --- a/modules/audio_processing/aec3/decimator_by_4_unittest.cc +++ b/modules/audio_processing/aec3/decimator_unittest.cc @@ -8,7 +8,7 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "modules/audio_processing/aec3/decimator_by_4.h" +#include "modules/audio_processing/aec3/decimator.h" #include #include @@ -31,15 +31,18 @@ std::string ProduceDebugText(int sample_rate_hz) { return ss.str(); } +constexpr size_t kDownSamplingFactors[] = {2, 4, 8}; constexpr float kPi = 3.141592f; constexpr size_t kNumStartupBlocks = 50; constexpr size_t kNumBlocks = 1000; void ProduceDecimatedSinusoidalOutputPower(int sample_rate_hz, + size_t down_sampling_factor, float sinusoidal_frequency_hz, float* input_power, float* output_power) { float input[kBlockSize * kNumBlocks]; + const size_t sub_block_size = kBlockSize / down_sampling_factor; // Produce a sinusoid of the specified frequency. for (size_t k = 0; k < kBlockSize * kNumBlocks; ++k) { @@ -47,18 +50,18 @@ void ProduceDecimatedSinusoidalOutputPower(int sample_rate_hz, 32767.f * sin(2.f * kPi * sinusoidal_frequency_hz * k / sample_rate_hz); } - DecimatorBy4 decimator; - std::array output; + Decimator decimator(down_sampling_factor); + std::vector output(sub_block_size * kNumBlocks); for (size_t k = 0; k < kNumBlocks; ++k) { - std::array sub_block; + std::vector sub_block(sub_block_size); decimator.Decimate( rtc::ArrayView(&input[k * kBlockSize], kBlockSize), sub_block); std::copy(sub_block.begin(), sub_block.end(), - output.begin() + k * kSubBlockSize); + output.begin() + k * sub_block_size); } ASSERT_GT(kNumBlocks, kNumStartupBlocks); @@ -66,8 +69,8 @@ void ProduceDecimatedSinusoidalOutputPower(int sample_rate_hz, &input[kNumStartupBlocks * kBlockSize], (kNumBlocks - kNumStartupBlocks) * kBlockSize); rtc::ArrayView output_to_evaluate( - &output[kNumStartupBlocks * kSubBlockSize], - (kNumBlocks - kNumStartupBlocks) * kSubBlockSize); + &output[kNumStartupBlocks * sub_block_size], + (kNumBlocks - kNumStartupBlocks) * sub_block_size); *input_power = std::inner_product(input_to_evaluate.begin(), input_to_evaluate.end(), input_to_evaluate.begin(), 0.f) / @@ -82,46 +85,64 @@ void ProduceDecimatedSinusoidalOutputPower(int sample_rate_hz, // Verifies that there is little aliasing from upper frequencies in the // downsampling. -TEST(DecimatorBy4, NoLeakageFromUpperFrequencies) { +TEST(Decimator, NoLeakageFromUpperFrequencies) { float input_power; float output_power; for (auto rate : {8000, 16000, 32000, 48000}) { - ProduceDebugText(rate); - ProduceDecimatedSinusoidalOutputPower(rate, 3.f / 8.f * rate, &input_power, - &output_power); - EXPECT_GT(0.0001f * input_power, output_power); + for (auto down_sampling_factor : kDownSamplingFactors) { + ProduceDebugText(rate); + ProduceDecimatedSinusoidalOutputPower(rate, down_sampling_factor, + 3.f / 8.f * rate, &input_power, + &output_power); + EXPECT_GT(0.0001f * input_power, output_power); + } } } // Verifies that the impact of low-frequency content is small during the // downsampling. -TEST(DecimatorBy4, NoImpactOnLowerFrequencies) { +TEST(Decimator, NoImpactOnLowerFrequencies) { float input_power; float output_power; for (auto rate : {8000, 16000, 32000, 48000}) { - ProduceDebugText(rate); - ProduceDecimatedSinusoidalOutputPower(rate, 200.f, &input_power, - &output_power); - EXPECT_LT(0.7f * input_power, output_power); + for (auto down_sampling_factor : kDownSamplingFactors) { + ProduceDebugText(rate); + ProduceDecimatedSinusoidalOutputPower(rate, down_sampling_factor, 200.f, + &input_power, &output_power); + EXPECT_LT(0.7f * input_power, output_power); + } } } #if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) // Verifies the check for the input size. -TEST(DecimatorBy4, WrongInputSize) { - DecimatorBy4 decimator; +TEST(Decimator, WrongInputSize) { + Decimator decimator(4); std::vector x(std::vector(kBlockSize - 1, 0.f)); - std::array x_downsampled; + std::array x_downsampled; EXPECT_DEATH(decimator.Decimate(x, x_downsampled), ""); } // Verifies the check for non-null output parameter. -TEST(DecimatorBy4, NullOutput) { - DecimatorBy4 decimator; +TEST(Decimator, NullOutput) { + Decimator decimator(4); std::vector x(std::vector(kBlockSize, 0.f)); EXPECT_DEATH(decimator.Decimate(x, nullptr), ""); } +// Verifies the check for the output size. +TEST(Decimator, WrongOutputSize) { + Decimator decimator(4); + std::vector x(std::vector(kBlockSize, 0.f)); + std::array x_downsampled; + EXPECT_DEATH(decimator.Decimate(x, x_downsampled), ""); +} + +// Verifies the check for the correct downsampling factor. +TEST(Decimator, CorrectDownSamplingFactor) { + EXPECT_DEATH(Decimator(3), ""); +} + #endif } // namespace webrtc diff --git a/modules/audio_processing/aec3/downsampled_render_buffer.cc b/modules/audio_processing/aec3/downsampled_render_buffer.cc index cc65b3f354..efc733b182 100644 --- a/modules/audio_processing/aec3/downsampled_render_buffer.cc +++ b/modules/audio_processing/aec3/downsampled_render_buffer.cc @@ -12,7 +12,8 @@ namespace webrtc { -DownsampledRenderBuffer::DownsampledRenderBuffer() = default; +DownsampledRenderBuffer::DownsampledRenderBuffer(size_t downsampled_buffer_size) + : buffer(downsampled_buffer_size, 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 499eac7f99..531852a0c9 100644 --- a/modules/audio_processing/aec3/downsampled_render_buffer.h +++ b/modules/audio_processing/aec3/downsampled_render_buffer.h @@ -11,7 +11,7 @@ #ifndef MODULES_AUDIO_PROCESSING_AEC3_DOWNSAMPLED_RENDER_BUFFER_H_ #define MODULES_AUDIO_PROCESSING_AEC3_DOWNSAMPLED_RENDER_BUFFER_H_ -#include +#include #include "modules/audio_processing/aec3/aec3_common.h" @@ -19,9 +19,9 @@ namespace webrtc { // Holds the circular buffer of the downsampled render data. struct DownsampledRenderBuffer { - DownsampledRenderBuffer(); + explicit DownsampledRenderBuffer(size_t downsampled_buffer_size); ~DownsampledRenderBuffer(); - std::array buffer = {}; + std::vector buffer; int position = 0; }; diff --git a/modules/audio_processing/aec3/echo_path_delay_estimator.cc b/modules/audio_processing/aec3/echo_path_delay_estimator.cc index d1bb7eeaba..a57d5416cf 100644 --- a/modules/audio_processing/aec3/echo_path_delay_estimator.cc +++ b/modules/audio_processing/aec3/echo_path_delay_estimator.cc @@ -19,23 +19,26 @@ namespace webrtc { -namespace { - -constexpr int kDownSamplingFactor = 4; -} // namespace - EchoPathDelayEstimator::EchoPathDelayEstimator( ApmDataDumper* data_dumper, const EchoCanceller3Config& config) : data_dumper_(data_dumper), + down_sampling_factor_(config.delay.down_sampling_factor), + sub_block_size_(down_sampling_factor_ != 0 + ? kBlockSize / down_sampling_factor_ + : kBlockSize), + capture_decimator_(down_sampling_factor_), matched_filter_(data_dumper_, DetectOptimization(), + sub_block_size_, kMatchedFilterWindowSizeSubBlocks, - kNumMatchedFilters, + config.delay.num_filters, kMatchedFilterAlignmentShiftSizeSubBlocks, config.render_levels.poor_excitation_render_limit), - matched_filter_lag_aggregator_(data_dumper_) { + matched_filter_lag_aggregator_(data_dumper_, + matched_filter_.GetMaxFilterLag()) { RTC_DCHECK(data_dumper); + RTC_DCHECK(down_sampling_factor_ > 0); } EchoPathDelayEstimator::~EchoPathDelayEstimator() = default; @@ -50,8 +53,15 @@ rtc::Optional EchoPathDelayEstimator::EstimateDelay( rtc::ArrayView capture) { RTC_DCHECK_EQ(kBlockSize, capture.size()); - std::array downsampled_capture; + std::array downsampled_capture_data; + rtc::ArrayView downsampled_capture(downsampled_capture_data.data(), + sub_block_size_); + data_dumper_->DumpWav("aec3_capture_decimator_input", capture.size(), + capture.data(), 16000, 1); capture_decimator_.Decimate(capture, downsampled_capture); + data_dumper_->DumpWav("aec3_capture_decimator_output", + downsampled_capture.size(), downsampled_capture.data(), + 16000 / down_sampling_factor_, 1); matched_filter_.Update(render_buffer, downsampled_capture); rtc::Optional aggregated_matched_filter_lag = @@ -63,14 +73,14 @@ rtc::Optional EchoPathDelayEstimator::EstimateDelay( data_dumper_->DumpRaw("aec3_echo_path_delay_estimator_delay", aggregated_matched_filter_lag ? static_cast(*aggregated_matched_filter_lag * - kDownSamplingFactor) + 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(*aggregated_matched_filter_lag * - kDownSamplingFactor) + down_sampling_factor_) : rtc::Optional(); } diff --git a/modules/audio_processing/aec3/echo_path_delay_estimator.h b/modules/audio_processing/aec3/echo_path_delay_estimator.h index 9819007a62..04943ca4ba 100644 --- a/modules/audio_processing/aec3/echo_path_delay_estimator.h +++ b/modules/audio_processing/aec3/echo_path_delay_estimator.h @@ -14,7 +14,7 @@ #include #include "api/optional.h" -#include "modules/audio_processing/aec3/decimator_by_4.h" +#include "modules/audio_processing/aec3/decimator.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" @@ -40,9 +40,17 @@ class EchoPathDelayEstimator { const DownsampledRenderBuffer& render_buffer, rtc::ArrayView capture); + // Log delay estimator properties. + void LogDelayEstimationProperties(int sample_rate_hz, size_t shift) const { + matched_filter_.LogFilterProperties(sample_rate_hz, shift, + down_sampling_factor_); + } + private: ApmDataDumper* const data_dumper_; - DecimatorBy4 capture_decimator_; + const size_t down_sampling_factor_; + const size_t sub_block_size_; + Decimator capture_decimator_; MatchedFilter matched_filter_; MatchedFilterLagAggregator matched_filter_lag_aggregator_; 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 9ff45cda4c..2dbdb1ccf4 100644 --- a/modules/audio_processing/aec3/echo_path_delay_estimator_unittest.cc +++ b/modules/audio_processing/aec3/echo_path_delay_estimator_unittest.cc @@ -35,9 +35,15 @@ std::string ProduceDebugText(size_t delay) { // Verifies that the basic API calls work. TEST(EchoPathDelayEstimator, BasicApiCalls) { ApmDataDumper data_dumper(0); + EchoCanceller3Config config; std::unique_ptr render_delay_buffer( - RenderDelayBuffer::Create(3)); - EchoPathDelayEstimator estimator(&data_dumper, EchoCanceller3Config()); + 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))); + EchoPathDelayEstimator estimator(&data_dumper, config); std::vector> render(3, std::vector(kBlockSize)); std::vector capture(kBlockSize); for (size_t k = 0; k < 100; ++k) { @@ -54,29 +60,40 @@ TEST(EchoPathDelayEstimator, DelayEstimation) { std::vector> render(3, std::vector(kBlockSize)); std::vector capture(kBlockSize); ApmDataDumper data_dumper(0); - for (size_t delay_samples : {15, 64, 150, 200, 800, 4000}) { - SCOPED_TRACE(ProduceDebugText(delay_samples)); - std::unique_ptr render_delay_buffer( - RenderDelayBuffer::Create(3)); - DelayBuffer signal_delay_buffer(delay_samples); - EchoPathDelayEstimator estimator(&data_dumper, EchoCanceller3Config()); + constexpr size_t kDownSamplingFactors[] = {2, 4, 8}; + for (auto down_sampling_factor : kDownSamplingFactors) { + EchoCanceller3Config config; + 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)); + std::unique_ptr 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))); + DelayBuffer signal_delay_buffer(delay_samples); + EchoPathDelayEstimator estimator(&data_dumper, config); - rtc::Optional estimated_delay_samples; - for (size_t k = 0; k < (150 + delay_samples / kBlockSize); ++k) { - RandomizeSampleVector(&random_generator, render[0]); - signal_delay_buffer.Delay(render[0], capture); - render_delay_buffer->Insert(render); - render_delay_buffer->UpdateBuffers(); - estimated_delay_samples = estimator.EstimateDelay( - render_delay_buffer->GetDownsampledRenderBuffer(), capture); - } - if (estimated_delay_samples) { - // Due to the internal down-sampling by 4 done inside the delay estimator - // the estimated delay cannot be expected to be closer than 4 samples to - // the true delay. - EXPECT_NEAR(delay_samples, *estimated_delay_samples, 4); - } else { - ADD_FAILURE(); + rtc::Optional estimated_delay_samples; + for (size_t k = 0; k < (300 + delay_samples / kBlockSize); ++k) { + RandomizeSampleVector(&random_generator, render[0]); + signal_delay_buffer.Delay(render[0], capture); + render_delay_buffer->Insert(render); + render_delay_buffer->UpdateBuffers(); + 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); + } else { + ADD_FAILURE(); + } } } } @@ -85,13 +102,19 @@ TEST(EchoPathDelayEstimator, DelayEstimation) { // quickly. TEST(EchoPathDelayEstimator, NoInitialDelayestimates) { Random random_generator(42U); + EchoCanceller3Config config; std::vector> render(3, std::vector(kBlockSize)); std::vector capture(kBlockSize); ApmDataDumper data_dumper(0); std::unique_ptr render_delay_buffer( - RenderDelayBuffer::Create(3)); + 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))); - EchoPathDelayEstimator estimator(&data_dumper, EchoCanceller3Config()); + 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()); @@ -106,12 +129,18 @@ TEST(EchoPathDelayEstimator, NoInitialDelayestimates) { // signals of low level. TEST(EchoPathDelayEstimator, NoDelayEstimatesForLowLevelRenderSignals) { Random random_generator(42U); + EchoCanceller3Config config; std::vector> render(3, std::vector(kBlockSize)); std::vector capture(kBlockSize); ApmDataDumper data_dumper(0); - EchoPathDelayEstimator estimator(&data_dumper, EchoCanceller3Config()); + EchoPathDelayEstimator estimator(&data_dumper, config); std::unique_ptr render_delay_buffer( - RenderDelayBuffer::Create(3)); + 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))); for (size_t k = 0; k < 100; ++k) { RandomizeSampleVector(&random_generator, render[0]); for (auto& render_k : render[0]) { @@ -129,12 +158,18 @@ TEST(EchoPathDelayEstimator, NoDelayEstimatesForLowLevelRenderSignals) { // uncorrelated signals. TEST(EchoPathDelayEstimator, NoDelayEstimatesForUncorrelatedSignals) { Random random_generator(42U); + EchoCanceller3Config config; std::vector> render(3, std::vector(kBlockSize)); std::vector capture(kBlockSize); ApmDataDumper data_dumper(0); - EchoPathDelayEstimator estimator(&data_dumper, EchoCanceller3Config()); + EchoPathDelayEstimator estimator(&data_dumper, config); std::unique_ptr render_delay_buffer( - RenderDelayBuffer::Create(3)); + 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))); for (size_t k = 0; k < 100; ++k) { RandomizeSampleVector(&random_generator, render[0]); RandomizeSampleVector(&random_generator, capture); @@ -152,9 +187,15 @@ TEST(EchoPathDelayEstimator, NoDelayEstimatesForUncorrelatedSignals) { // tests on test bots has been fixed. TEST(EchoPathDelayEstimator, DISABLED_WrongRenderBlockSize) { ApmDataDumper data_dumper(0); - EchoPathDelayEstimator estimator(&data_dumper, EchoCanceller3Config()); + EchoCanceller3Config config; + EchoPathDelayEstimator estimator(&data_dumper, config); std::unique_ptr render_delay_buffer( - RenderDelayBuffer::Create(3)); + 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))); std::vector capture(kBlockSize); EXPECT_DEATH(estimator.EstimateDelay( render_delay_buffer->GetDownsampledRenderBuffer(), capture), @@ -166,9 +207,15 @@ TEST(EchoPathDelayEstimator, DISABLED_WrongRenderBlockSize) { // tests on test bots has been fixed. TEST(EchoPathDelayEstimator, WrongCaptureBlockSize) { ApmDataDumper data_dumper(0); - EchoPathDelayEstimator estimator(&data_dumper, EchoCanceller3Config()); + EchoCanceller3Config config; + EchoPathDelayEstimator estimator(&data_dumper, config); std::unique_ptr render_delay_buffer( - RenderDelayBuffer::Create(3)); + 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))); std::vector capture(std::vector(kBlockSize - 1)); EXPECT_DEATH(estimator.EstimateDelay( render_delay_buffer->GetDownsampledRenderBuffer(), capture), diff --git a/modules/audio_processing/aec3/echo_remover_unittest.cc b/modules/audio_processing/aec3/echo_remover_unittest.cc index b253efce81..84a54b45aa 100644 --- a/modules/audio_processing/aec3/echo_remover_unittest.cc +++ b/modules/audio_processing/aec3/echo_remover_unittest.cc @@ -39,6 +39,9 @@ std::string ProduceDebugText(int sample_rate_hz, int delay) { return ss.str(); } +constexpr size_t kDownSamplingFactor = 4; +constexpr size_t kNumMatchedFilters = 4; + } // namespace // Verifies the basic API call sequence @@ -47,8 +50,10 @@ TEST(EchoRemover, BasicApiCalls) { SCOPED_TRACE(ProduceDebugText(rate)); std::unique_ptr remover( EchoRemover::Create(EchoCanceller3Config(), rate)); - std::unique_ptr render_buffer( - RenderDelayBuffer::Create(NumBandsForRate(rate))); + std::unique_ptr render_buffer(RenderDelayBuffer::Create( + NumBandsForRate(rate), kDownSamplingFactor, + GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters), + GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters))); std::vector> render(NumBandsForRate(rate), std::vector(kBlockSize, 0.f)); @@ -86,8 +91,10 @@ TEST(EchoRemover, WrongCaptureBlockSize) { SCOPED_TRACE(ProduceDebugText(rate)); std::unique_ptr remover( EchoRemover::Create(EchoCanceller3Config(), rate)); - std::unique_ptr render_buffer( - RenderDelayBuffer::Create(NumBandsForRate(rate))); + std::unique_ptr render_buffer(RenderDelayBuffer::Create( + NumBandsForRate(rate), kDownSamplingFactor, + GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters), + GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters))); std::vector> capture( NumBandsForRate(rate), std::vector(kBlockSize - 1, 0.f)); EchoPathVariability echo_path_variability(false, false); @@ -107,8 +114,10 @@ TEST(EchoRemover, DISABLED_WrongCaptureNumBands) { SCOPED_TRACE(ProduceDebugText(rate)); std::unique_ptr remover( EchoRemover::Create(EchoCanceller3Config(), rate)); - std::unique_ptr render_buffer( - RenderDelayBuffer::Create(NumBandsForRate(rate))); + std::unique_ptr render_buffer(RenderDelayBuffer::Create( + NumBandsForRate(rate), kDownSamplingFactor, + GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters), + GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters))); std::vector> capture( NumBandsForRate(rate == 48000 ? 16000 : rate + 16000), std::vector(kBlockSize, 0.f)); @@ -125,8 +134,10 @@ TEST(EchoRemover, DISABLED_WrongCaptureNumBands) { TEST(EchoRemover, NullCapture) { std::unique_ptr remover( EchoRemover::Create(EchoCanceller3Config(), 8000)); - std::unique_ptr render_buffer( - RenderDelayBuffer::Create(3)); + std::unique_ptr render_buffer(RenderDelayBuffer::Create( + 3, kDownSamplingFactor, + GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters), + GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters))); EchoPathVariability echo_path_variability(false, false); rtc::Optional echo_path_delay_samples; EXPECT_DEATH( @@ -153,7 +164,11 @@ TEST(EchoRemover, BasicEchoRemoval) { std::unique_ptr remover( EchoRemover::Create(EchoCanceller3Config(), rate)); std::unique_ptr render_buffer( - RenderDelayBuffer::Create(NumBandsForRate(rate))); + RenderDelayBuffer::Create( + NumBandsForRate(rate), kDownSamplingFactor, + GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters), + GetRenderDelayBufferSize(kDownSamplingFactor, + kNumMatchedFilters))); std::vector>> delay_buffers(x.size()); for (size_t j = 0; j < x.size(); ++j) { delay_buffers[j].reset(new DelayBuffer(delay_samples)); diff --git a/modules/audio_processing/aec3/matched_filter.cc b/modules/audio_processing/aec3/matched_filter.cc index 901281fc8e..d5e6a28bf7 100644 --- a/modules/audio_processing/aec3/matched_filter.cc +++ b/modules/audio_processing/aec3/matched_filter.cc @@ -21,6 +21,7 @@ #include "modules/audio_processing/include/audio_processing.h" #include "modules/audio_processing/logging/apm_data_dumper.h" +#include "rtc_base/logging.h" namespace webrtc { namespace aec3 { @@ -39,7 +40,7 @@ void MatchedFilterCore_NEON(size_t x_start_index, RTC_DCHECK_EQ(0, h_size % 4); // Process for all samples in the sub-block. - for (size_t i = 0; i < kSubBlockSize; ++i) { + for (size_t i = 0; i < y.size(); ++i) { // Apply the matched filter as filter * x, and compute x * x. RTC_DCHECK_GT(x_size, x_start_index); @@ -147,7 +148,7 @@ void MatchedFilterCore_SSE2(size_t x_start_index, RTC_DCHECK_EQ(0, h_size % 4); // Process for all samples in the sub-block. - for (size_t i = 0; i < kSubBlockSize; ++i) { + for (size_t i = 0; i < y.size(); ++i) { // Apply the matched filter as filter * x, and compute x * x. RTC_DCHECK_GT(x_size, x_start_index); @@ -252,7 +253,7 @@ void MatchedFilterCore(size_t x_start_index, bool* filters_updated, float* error_sum) { // Process for all samples in the sub-block. - for (size_t i = 0; i < kSubBlockSize; ++i) { + for (size_t i = 0; i < y.size(); ++i) { // Apply the matched filter as filter * x, and compute x * x. float x2_sum = 0.f; float s = 0; @@ -289,19 +290,25 @@ void MatchedFilterCore(size_t x_start_index, MatchedFilter::MatchedFilter(ApmDataDumper* data_dumper, Aec3Optimization optimization, + size_t sub_block_size, size_t window_size_sub_blocks, int num_matched_filters, size_t alignment_shift_sub_blocks, float excitation_limit) : data_dumper_(data_dumper), optimization_(optimization), - filter_intra_lag_shift_(alignment_shift_sub_blocks * kSubBlockSize), - filters_(num_matched_filters, - std::vector(window_size_sub_blocks * kSubBlockSize, 0.f)), + sub_block_size_(sub_block_size), + filter_intra_lag_shift_(alignment_shift_sub_blocks * sub_block_size_), + filters_( + num_matched_filters, + std::vector(window_size_sub_blocks * sub_block_size_, 0.f)), lag_estimates_(num_matched_filters), + filters_offsets_(num_matched_filters, 0), excitation_limit_(excitation_limit) { RTC_DCHECK(data_dumper); RTC_DCHECK_LT(0, window_size_sub_blocks); + RTC_DCHECK((kBlockSize % sub_block_size) == 0); + RTC_DCHECK((sub_block_size % 4) == 0); } MatchedFilter::~MatchedFilter() = default; @@ -317,8 +324,9 @@ void MatchedFilter::Reset() { } void MatchedFilter::Update(const DownsampledRenderBuffer& render_buffer, - const std::array& capture) { - const std::array& y = capture; + rtc::ArrayView capture) { + RTC_DCHECK_EQ(sub_block_size_, capture.size()); + auto& y = capture; const float x2_sum_threshold = filters_[0].size() * excitation_limit_ * excitation_limit_; @@ -330,7 +338,7 @@ void MatchedFilter::Update(const DownsampledRenderBuffer& render_buffer, bool filters_updated = false; size_t x_start_index = - (render_buffer.position + alignment_shift + kSubBlockSize - 1) % + (render_buffer.position + alignment_shift + sub_block_size_ - 1) % render_buffer.buffer.size(); switch (optimization_) { @@ -375,8 +383,7 @@ void MatchedFilter::Update(const DownsampledRenderBuffer& render_buffer, error_sum < kMatchingFilterThreshold * error_sum_anchor), lag_estimate + alignment_shift, filters_updated); - // TODO(peah): Remove once development of EchoCanceller3 is fully done. - RTC_DCHECK_EQ(4, filters_.size()); + RTC_DCHECK_GE(10, filters_.size()); switch (n) { case 0: data_dumper_->DumpRaw("aec3_correlator_0_h", filters_[0]); @@ -390,12 +397,47 @@ void MatchedFilter::Update(const DownsampledRenderBuffer& render_buffer, case 3: data_dumper_->DumpRaw("aec3_correlator_3_h", filters_[3]); break; + case 4: + data_dumper_->DumpRaw("aec3_correlator_4_h", filters_[4]); + break; + case 5: + data_dumper_->DumpRaw("aec3_correlator_5_h", filters_[5]); + break; + case 6: + data_dumper_->DumpRaw("aec3_correlator_6_h", filters_[6]); + break; + case 7: + data_dumper_->DumpRaw("aec3_correlator_7_h", filters_[7]); + break; + case 8: + data_dumper_->DumpRaw("aec3_correlator_8_h", filters_[8]); + break; + case 9: + data_dumper_->DumpRaw("aec3_correlator_9_h", filters_[9]); + break; default: - RTC_DCHECK(false); + RTC_NOTREACHED(); } alignment_shift += filter_intra_lag_shift_; } } +void MatchedFilter::LogFilterProperties(int sample_rate_hz, + size_t shift, + size_t downsampling_factor) const { + size_t alignment_shift = 0; + const int fs_by_1000 = LowestBandRate(sample_rate_hz) / 1000; + for (size_t k = 0; k < filters_.size(); ++k) { + int start = static_cast(alignment_shift * downsampling_factor); + int end = static_cast((alignment_shift + filters_[k].size()) * + downsampling_factor); + RTC_LOG(LS_INFO) << "Filter " << k << ": start: " + << (start - static_cast(shift)) / fs_by_1000 + << " ms, end: " + << (end - static_cast(shift)) / fs_by_1000 << " ms."; + alignment_shift += filter_intra_lag_shift_; + } +} + } // namespace webrtc diff --git a/modules/audio_processing/aec3/matched_filter.h b/modules/audio_processing/aec3/matched_filter.h index 37e219fa68..c9bdc462e8 100644 --- a/modules/audio_processing/aec3/matched_filter.h +++ b/modules/audio_processing/aec3/matched_filter.h @@ -81,6 +81,7 @@ class MatchedFilter { MatchedFilter(ApmDataDumper* data_dumper, Aec3Optimization optimization, + size_t sub_block_size, size_t window_size_sub_blocks, int num_matched_filters, size_t alignment_shift_sub_blocks, @@ -90,7 +91,7 @@ class MatchedFilter { // Updates the correlation with the values in the capture buffer. void Update(const DownsampledRenderBuffer& render_buffer, - const std::array& capture); + rtc::ArrayView capture); // Resets the matched filter. void Reset(); @@ -100,15 +101,24 @@ class MatchedFilter { return lag_estimates_; } - // Returns the number of lag estimates produced using the shifted signals. - size_t NumLagEstimates() const { return filters_.size(); } + // Returns the maximum filter lag. + size_t GetMaxFilterLag() const { + return filters_.size() * filter_intra_lag_shift_ + filters_[0].size(); + } + + // Log matched filter properties. + void LogFilterProperties(int sample_rate_hz, + size_t shift, + size_t downsampling_factor) const; private: ApmDataDumper* const data_dumper_; const Aec3Optimization optimization_; + const size_t sub_block_size_; const size_t filter_intra_lag_shift_; std::vector> filters_; std::vector lag_estimates_; + std::vector filters_offsets_; const float excitation_limit_; RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(MatchedFilter); diff --git a/modules/audio_processing/aec3/matched_filter_lag_aggregator.cc b/modules/audio_processing/aec3/matched_filter_lag_aggregator.cc index 1d16dd6d1b..5115fcbf43 100644 --- a/modules/audio_processing/aec3/matched_filter_lag_aggregator.cc +++ b/modules/audio_processing/aec3/matched_filter_lag_aggregator.cc @@ -14,17 +14,17 @@ namespace webrtc { MatchedFilterLagAggregator::MatchedFilterLagAggregator( - ApmDataDumper* data_dumper) - : data_dumper_(data_dumper) { + ApmDataDumper* data_dumper, + size_t max_filter_lag) + : data_dumper_(data_dumper), histogram_(max_filter_lag + 1, 0) { RTC_DCHECK(data_dumper); - histogram_.fill(0); histogram_data_.fill(0); } MatchedFilterLagAggregator::~MatchedFilterLagAggregator() = default; void MatchedFilterLagAggregator::Reset() { - histogram_.fill(0); + std::fill(histogram_.begin(), histogram_.end(), 0); histogram_data_.fill(0); histogram_data_index_ = 0; } diff --git a/modules/audio_processing/aec3/matched_filter_lag_aggregator.h b/modules/audio_processing/aec3/matched_filter_lag_aggregator.h index 76d711ce9e..c5dd24700e 100644 --- a/modules/audio_processing/aec3/matched_filter_lag_aggregator.h +++ b/modules/audio_processing/aec3/matched_filter_lag_aggregator.h @@ -25,7 +25,7 @@ class ApmDataDumper; // reliable combined lag estimate. class MatchedFilterLagAggregator { public: - explicit MatchedFilterLagAggregator(ApmDataDumper* data_dumper); + MatchedFilterLagAggregator(ApmDataDumper* data_dumper, size_t max_filter_lag); ~MatchedFilterLagAggregator(); // Resets the aggregator. @@ -37,7 +37,7 @@ class MatchedFilterLagAggregator { private: ApmDataDumper* const data_dumper_; - std::array histogram_; + std::vector histogram_; std::array histogram_data_; int histogram_data_index_ = 0; 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 9b0c2e1171..985ed43427 100644 --- a/modules/audio_processing/aec3/matched_filter_lag_aggregator_unittest.cc +++ b/modules/audio_processing/aec3/matched_filter_lag_aggregator_unittest.cc @@ -32,7 +32,7 @@ TEST(MatchedFilterLagAggregator, MostAccurateLagChosen) { constexpr size_t kLag2 = 10; ApmDataDumper data_dumper(0); std::vector lag_estimates(2); - MatchedFilterLagAggregator aggregator(&data_dumper); + MatchedFilterLagAggregator aggregator(&data_dumper, std::max(kLag1, kLag2)); lag_estimates[0] = MatchedFilter::LagEstimate(1.f, true, kLag1, true); lag_estimates[1] = MatchedFilter::LagEstimate(0.5f, true, kLag2, true); @@ -65,7 +65,7 @@ TEST(MatchedFilterLagAggregator, LagEstimateInvarianceRequiredForAggregatedLag) { ApmDataDumper data_dumper(0); std::vector lag_estimates(1); - MatchedFilterLagAggregator aggregator(&data_dumper); + MatchedFilterLagAggregator aggregator(&data_dumper, 100); for (size_t k = 0; k < kNumLagsBeforeDetection * 100; ++k) { lag_estimates[0] = MatchedFilter::LagEstimate(1.f, true, k % 100, true); rtc::Optional aggregated_lag = aggregator.Aggregate(lag_estimates); @@ -80,7 +80,7 @@ TEST(MatchedFilterLagAggregator, constexpr size_t kLag = 5; ApmDataDumper data_dumper(0); std::vector lag_estimates(1); - MatchedFilterLagAggregator aggregator(&data_dumper); + 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 aggregated_lag = aggregator.Aggregate(lag_estimates); @@ -97,7 +97,7 @@ TEST(MatchedFilterLagAggregator, DISABLED_PersistentAggregatedLag) { constexpr size_t kLag2 = 10; ApmDataDumper data_dumper(0); std::vector lag_estimates(1); - MatchedFilterLagAggregator aggregator(&data_dumper); + MatchedFilterLagAggregator aggregator(&data_dumper, std::max(kLag1, kLag2)); rtc::Optional aggregated_lag; for (size_t k = 0; k < kNumLagsBeforeDetection; ++k) { lag_estimates[0] = MatchedFilter::LagEstimate(1.f, true, kLag1, true); @@ -118,7 +118,7 @@ TEST(MatchedFilterLagAggregator, DISABLED_PersistentAggregatedLag) { // Verifies the check for non-null data dumper. TEST(MatchedFilterLagAggregator, NullDataDumper) { - EXPECT_DEATH(MatchedFilterLagAggregator(nullptr), ""); + EXPECT_DEATH(MatchedFilterLagAggregator(nullptr, 10), ""); } #endif diff --git a/modules/audio_processing/aec3/matched_filter_unittest.cc b/modules/audio_processing/aec3/matched_filter_unittest.cc index deaad113ea..e03ea8f362 100644 --- a/modules/audio_processing/aec3/matched_filter_unittest.cc +++ b/modules/audio_processing/aec3/matched_filter_unittest.cc @@ -19,7 +19,7 @@ #include #include "modules/audio_processing/aec3/aec3_common.h" -#include "modules/audio_processing/aec3/decimator_by_4.h" +#include "modules/audio_processing/aec3/decimator.h" #include "modules/audio_processing/aec3/render_delay_buffer.h" #include "modules/audio_processing/logging/apm_data_dumper.h" #include "modules/audio_processing/test/echo_canceller_test_tools.h" @@ -31,15 +31,17 @@ namespace webrtc { namespace aec3 { 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(); } +constexpr size_t kNumMatchedFilters = 10; +constexpr size_t kDownSamplingFactors[] = {2, 4, 8}; constexpr size_t kWindowSizeSubBlocks = 32; constexpr size_t kAlignmentShiftSubBlocks = kWindowSizeSubBlocks * 3 / 4; -constexpr size_t kNumMatchedFilters = 4; } // namespace @@ -48,34 +50,38 @@ constexpr size_t kNumMatchedFilters = 4; // counterparts. TEST(MatchedFilter, TestNeonOptimizations) { Random random_generator(42U); - std::vector x(2000); - RandomizeSampleVector(&random_generator, x); - std::vector y(kSubBlockSize); - std::vector h_NEON(512); - std::vector h(512); - int x_index = 0; - for (int k = 0; k < 1000; ++k) { - RandomizeSampleVector(&random_generator, y); + for (auto down_sampling_factor : kDownSamplingFactors) { + const size_t sub_block_size = kBlockSize / down_sampling_factor; - bool filters_updated = false; - float error_sum = 0.f; - bool filters_updated_NEON = false; - float error_sum_NEON = 0.f; + std::vector x(2000); + RandomizeSampleVector(&random_generator, x); + std::vector y(sub_block_size); + std::vector h_NEON(512); + std::vector h(512); + int x_index = 0; + for (int k = 0; k < 1000; ++k) { + RandomizeSampleVector(&random_generator, y); - MatchedFilterCore_NEON(x_index, h.size() * 150.f * 150.f, x, y, h_NEON, - &filters_updated_NEON, &error_sum_NEON); + bool filters_updated = false; + float error_sum = 0.f; + bool filters_updated_NEON = false; + float error_sum_NEON = 0.f; - MatchedFilterCore(x_index, h.size() * 150.f * 150.f, x, y, h, - &filters_updated, &error_sum); + MatchedFilterCore_NEON(x_index, h.size() * 150.f * 150.f, x, y, h_NEON, + &filters_updated_NEON, &error_sum_NEON); - EXPECT_EQ(filters_updated, filters_updated_NEON); - EXPECT_NEAR(error_sum, error_sum_NEON, error_sum / 100000.f); + MatchedFilterCore(x_index, h.size() * 150.f * 150.f, x, y, h, + &filters_updated, &error_sum); - for (size_t j = 0; j < h.size(); ++j) { - EXPECT_NEAR(h[j], h_NEON[j], 0.00001f); + EXPECT_EQ(filters_updated, filters_updated_NEON); + EXPECT_NEAR(error_sum, error_sum_NEON, error_sum / 100000.f); + + for (size_t j = 0; j < h.size(); ++j) { + EXPECT_NEAR(h[j], h_NEON[j], 0.00001f); + } + + x_index = (x_index + sub_block_size) % x.size(); } - - x_index = (x_index + kSubBlockSize) % x.size(); } } #endif @@ -87,34 +93,37 @@ TEST(MatchedFilter, TestSse2Optimizations) { bool use_sse2 = (WebRtc_GetCPUInfo(kSSE2) != 0); if (use_sse2) { Random random_generator(42U); - std::vector x(2000); - RandomizeSampleVector(&random_generator, x); - std::vector y(kSubBlockSize); - std::vector h_SSE2(512); - std::vector h(512); - int x_index = 0; - for (int k = 0; k < 1000; ++k) { - RandomizeSampleVector(&random_generator, y); + for (auto down_sampling_factor : kDownSamplingFactors) { + const size_t sub_block_size = kBlockSize / down_sampling_factor; + std::vector x(2000); + RandomizeSampleVector(&random_generator, x); + std::vector y(sub_block_size); + std::vector h_SSE2(512); + std::vector h(512); + int x_index = 0; + for (int k = 0; k < 1000; ++k) { + RandomizeSampleVector(&random_generator, y); - bool filters_updated = false; - float error_sum = 0.f; - bool filters_updated_SSE2 = false; - float error_sum_SSE2 = 0.f; + bool filters_updated = false; + float error_sum = 0.f; + bool filters_updated_SSE2 = false; + float error_sum_SSE2 = 0.f; - MatchedFilterCore_SSE2(x_index, h.size() * 150.f * 150.f, x, y, h_SSE2, - &filters_updated_SSE2, &error_sum_SSE2); + MatchedFilterCore_SSE2(x_index, h.size() * 150.f * 150.f, x, y, h_SSE2, + &filters_updated_SSE2, &error_sum_SSE2); - MatchedFilterCore(x_index, h.size() * 150.f * 150.f, x, y, h, - &filters_updated, &error_sum); + MatchedFilterCore(x_index, h.size() * 150.f * 150.f, x, y, h, + &filters_updated, &error_sum); - EXPECT_EQ(filters_updated, filters_updated_SSE2); - EXPECT_NEAR(error_sum, error_sum_SSE2, error_sum / 100000.f); + EXPECT_EQ(filters_updated, filters_updated_SSE2); + EXPECT_NEAR(error_sum, error_sum_SSE2, error_sum / 100000.f); - for (size_t j = 0; j < h.size(); ++j) { - EXPECT_NEAR(h[j], h_SSE2[j], 0.00001f); + for (size_t j = 0; j < h.size(); ++j) { + EXPECT_NEAR(h[j], h_SSE2[j], 0.00001f); + } + + x_index = (x_index + sub_block_size) % x.size(); } - - x_index = (x_index + kSubBlockSize) % x.size(); } } } @@ -126,71 +135,100 @@ TEST(MatchedFilter, TestSse2Optimizations) { // delayed signals. TEST(MatchedFilter, LagEstimation) { Random random_generator(42U); - std::vector> render(3, - std::vector(kBlockSize, 0.f)); - std::array capture; - capture.fill(0.f); - ApmDataDumper data_dumper(0); - for (size_t delay_samples : {5, 64, 150, 200, 800, 1000}) { - SCOPED_TRACE(ProduceDebugText(delay_samples)); - DecimatorBy4 capture_decimator; - DelayBuffer signal_delay_buffer(4 * delay_samples); - MatchedFilter filter(&data_dumper, DetectOptimization(), - kWindowSizeSubBlocks, kNumMatchedFilters, - kAlignmentShiftSubBlocks, 150); - std::unique_ptr render_delay_buffer( - RenderDelayBuffer::Create(3)); + for (auto down_sampling_factor : kDownSamplingFactors) { + const size_t sub_block_size = kBlockSize / down_sampling_factor; - // Analyze the correlation between render and capture. - for (size_t k = 0; k < (150 + delay_samples / kSubBlockSize); ++k) { - RandomizeSampleVector(&random_generator, render[0]); - signal_delay_buffer.Delay(render[0], capture); - render_delay_buffer->Insert(render); - render_delay_buffer->UpdateBuffers(); - std::array downsampled_capture; - capture_decimator.Decimate(capture, downsampled_capture); - filter.Update(render_delay_buffer->GetDownsampledRenderBuffer(), - downsampled_capture); - } + std::vector> render(3, + std::vector(kBlockSize, 0.f)); + std::array capture; + capture.fill(0.f); + ApmDataDumper data_dumper(0); + for (size_t delay_samples : {5, 64, 150, 200, 800, 1000}) { + SCOPED_TRACE(ProduceDebugText(delay_samples, down_sampling_factor)); + Decimator capture_decimator(down_sampling_factor); + DelayBuffer signal_delay_buffer(down_sampling_factor * + delay_samples); + MatchedFilter filter(&data_dumper, DetectOptimization(), sub_block_size, + kWindowSizeSubBlocks, kNumMatchedFilters, + kAlignmentShiftSubBlocks, 150); + std::unique_ptr render_delay_buffer( + RenderDelayBuffer::Create( + 3, down_sampling_factor, + GetDownSampledBufferSize(down_sampling_factor, + kNumMatchedFilters), + GetRenderDelayBufferSize(down_sampling_factor, + kNumMatchedFilters))); - // Obtain the lag estimates. - auto lag_estimates = filter.GetLagEstimates(); - - // Find which lag estimate should be the most accurate. - rtc::Optional expected_most_accurate_lag_estimate; - size_t alignment_shift_sub_blocks = 0; - for (size_t k = 0; k < kNumMatchedFilters; ++k) { - if ((alignment_shift_sub_blocks + kWindowSizeSubBlocks / 2) * - kSubBlockSize > - delay_samples) { - expected_most_accurate_lag_estimate = rtc::Optional(k); - break; + // Analyze the correlation between render and capture. + for (size_t k = 0; k < (300 + 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(); + std::array downsampled_capture_data; + rtc::ArrayView downsampled_capture( + downsampled_capture_data.data(), sub_block_size); + capture_decimator.Decimate(capture, downsampled_capture); + filter.Update(render_delay_buffer->GetDownsampledRenderBuffer(), + downsampled_capture); } - alignment_shift_sub_blocks += kAlignmentShiftSubBlocks; - } - ASSERT_TRUE(expected_most_accurate_lag_estimate); - // Verify that the expected most accurate lag estimate is the most accurate - // estimate. - for (size_t k = 0; k < kNumMatchedFilters; ++k) { - if (k != *expected_most_accurate_lag_estimate) { - EXPECT_GT(lag_estimates[*expected_most_accurate_lag_estimate].accuracy, - lag_estimates[k].accuracy); + // Obtain the lag estimates. + auto lag_estimates = filter.GetLagEstimates(); + + // Find which lag estimate should be the most accurate. + rtc::Optional expected_most_accurate_lag_estimate; + size_t alignment_shift_sub_blocks = 0; + for (size_t k = 0; k < kNumMatchedFilters; ++k) { + if ((alignment_shift_sub_blocks + 3 * kWindowSizeSubBlocks / 4) * + sub_block_size > + delay_samples) { + expected_most_accurate_lag_estimate = + rtc::Optional(k > 0 ? k - 1 : 0); + break; + } + alignment_shift_sub_blocks += kAlignmentShiftSubBlocks; + } + ASSERT_TRUE(expected_most_accurate_lag_estimate); + + // Verify that the expected most accurate lag estimate is the most + // accurate estimate. + for (size_t k = 0; k < kNumMatchedFilters; ++k) { + if (k != *expected_most_accurate_lag_estimate && + k != (*expected_most_accurate_lag_estimate + 1)) { + EXPECT_TRUE( + lag_estimates[*expected_most_accurate_lag_estimate].accuracy > + lag_estimates[k].accuracy || + !lag_estimates[k].reliable || + !lag_estimates[*expected_most_accurate_lag_estimate].reliable); + } + } + + // Verify that all lag estimates are updated as expected for signals + // containing strong noise. + for (auto& le : lag_estimates) { + EXPECT_TRUE(le.updated); + } + + // Verify that the expected most accurate lag estimate is reliable. + EXPECT_TRUE( + lag_estimates[*expected_most_accurate_lag_estimate].reliable || + lag_estimates[std::min(*expected_most_accurate_lag_estimate + 1, + lag_estimates.size() - 1)] + .reliable); + + // Verify that the expected most accurate lag estimate is correct. + if (lag_estimates[*expected_most_accurate_lag_estimate].reliable) { + EXPECT_TRUE(delay_samples == + lag_estimates[*expected_most_accurate_lag_estimate].lag); + } else { + EXPECT_TRUE( + delay_samples == + lag_estimates[std::min(*expected_most_accurate_lag_estimate + 1, + lag_estimates.size() - 1)] + .lag); } } - - // Verify that all lag estimates are updated as expected for signals - // containing strong noise. - for (auto& le : lag_estimates) { - EXPECT_TRUE(le.updated); - } - - // Verify that the expected most accurate lag estimate is reliable. - EXPECT_TRUE(lag_estimates[*expected_most_accurate_lag_estimate].reliable); - - // Verify that the expected most accurate lag estimate is correct. - EXPECT_EQ(delay_samples, - lag_estimates[*expected_most_accurate_lag_estimate].lag); } } @@ -198,31 +236,41 @@ TEST(MatchedFilter, LagEstimation) { // estimates for uncorrelated render and capture signals. TEST(MatchedFilter, LagNotReliableForUncorrelatedRenderAndCapture) { Random random_generator(42U); - std::vector> render(3, - std::vector(kBlockSize, 0.f)); - std::array capture; - capture.fill(0.f); - ApmDataDumper data_dumper(0); - std::unique_ptr render_delay_buffer( - RenderDelayBuffer::Create(3)); - MatchedFilter filter(&data_dumper, DetectOptimization(), kWindowSizeSubBlocks, - kNumMatchedFilters, kAlignmentShiftSubBlocks, 150); + for (auto down_sampling_factor : kDownSamplingFactors) { + const size_t sub_block_size = kBlockSize / down_sampling_factor; - // Analyze the correlation between render and capture. - for (size_t k = 0; k < 100; ++k) { - RandomizeSampleVector(&random_generator, render[0]); - RandomizeSampleVector(&random_generator, capture); - render_delay_buffer->Insert(render); - filter.Update(render_delay_buffer->GetDownsampledRenderBuffer(), capture); - } + std::vector> render(3, + std::vector(kBlockSize, 0.f)); + std::array capture_data; + rtc::ArrayView capture(capture_data.data(), sub_block_size); + std::fill(capture.begin(), capture.end(), 0.f); + ApmDataDumper data_dumper(0); + std::unique_ptr render_delay_buffer( + RenderDelayBuffer::Create( + 3, down_sampling_factor, + GetDownSampledBufferSize(down_sampling_factor, kNumMatchedFilters), + GetRenderDelayBufferSize(down_sampling_factor, + kNumMatchedFilters))); + MatchedFilter filter(&data_dumper, DetectOptimization(), sub_block_size, + kWindowSizeSubBlocks, kNumMatchedFilters, + kAlignmentShiftSubBlocks, 150); - // Obtain the lag estimates. - auto lag_estimates = filter.GetLagEstimates(); - EXPECT_EQ(kNumMatchedFilters, lag_estimates.size()); + // Analyze the correlation between render and capture. + for (size_t k = 0; k < 100; ++k) { + RandomizeSampleVector(&random_generator, render[0]); + RandomizeSampleVector(&random_generator, capture); + render_delay_buffer->Insert(render); + filter.Update(render_delay_buffer->GetDownsampledRenderBuffer(), capture); + } - // Verify that no lag estimates are reliable. - for (auto& le : lag_estimates) { - EXPECT_FALSE(le.reliable); + // Obtain the lag estimates. + auto lag_estimates = filter.GetLagEstimates(); + EXPECT_EQ(kNumMatchedFilters, lag_estimates.size()); + + // Verify that no lag estimates are reliable. + for (auto& le : lag_estimates) { + EXPECT_FALSE(le.reliable); + } } } @@ -230,39 +278,50 @@ TEST(MatchedFilter, LagNotReliableForUncorrelatedRenderAndCapture) { // render signals of low level. TEST(MatchedFilter, LagNotUpdatedForLowLevelRender) { Random random_generator(42U); - std::vector> render(3, - std::vector(kBlockSize, 0.f)); - std::array capture; - capture.fill(0.f); - ApmDataDumper data_dumper(0); - MatchedFilter filter(&data_dumper, DetectOptimization(), kWindowSizeSubBlocks, - kNumMatchedFilters, kAlignmentShiftSubBlocks, 150); - std::unique_ptr render_delay_buffer( - RenderDelayBuffer::Create(3)); - DecimatorBy4 capture_decimator; + for (auto down_sampling_factor : kDownSamplingFactors) { + const size_t sub_block_size = kBlockSize / down_sampling_factor; - // Analyze the correlation between render and capture. - for (size_t k = 0; k < 100; ++k) { - RandomizeSampleVector(&random_generator, render[0]); - for (auto& render_k : render[0]) { - render_k *= 149.f / 32767.f; + std::vector> render(3, + std::vector(kBlockSize, 0.f)); + std::array capture; + capture.fill(0.f); + ApmDataDumper data_dumper(0); + MatchedFilter filter(&data_dumper, DetectOptimization(), sub_block_size, + kWindowSizeSubBlocks, kNumMatchedFilters, + kAlignmentShiftSubBlocks, 150); + std::unique_ptr render_delay_buffer( + RenderDelayBuffer::Create( + 3, down_sampling_factor, + GetDownSampledBufferSize(down_sampling_factor, kNumMatchedFilters), + GetRenderDelayBufferSize(down_sampling_factor, + kNumMatchedFilters))); + Decimator capture_decimator(down_sampling_factor); + + // Analyze the correlation between render and capture. + for (size_t k = 0; k < 100; ++k) { + RandomizeSampleVector(&random_generator, render[0]); + for (auto& render_k : render[0]) { + render_k *= 149.f / 32767.f; + } + std::copy(render[0].begin(), render[0].end(), capture.begin()); + std::array downsampled_capture_data; + rtc::ArrayView downsampled_capture(downsampled_capture_data.data(), + sub_block_size); + capture_decimator.Decimate(capture, downsampled_capture); + filter.Update(render_delay_buffer->GetDownsampledRenderBuffer(), + downsampled_capture); } - std::copy(render[0].begin(), render[0].end(), capture.begin()); - std::array downsampled_capture; - capture_decimator.Decimate(capture, downsampled_capture); - filter.Update(render_delay_buffer->GetDownsampledRenderBuffer(), - downsampled_capture); - } - // Obtain the lag estimates. - auto lag_estimates = filter.GetLagEstimates(); - EXPECT_EQ(kNumMatchedFilters, lag_estimates.size()); + // Obtain the lag estimates. + auto lag_estimates = filter.GetLagEstimates(); + EXPECT_EQ(kNumMatchedFilters, lag_estimates.size()); - // Verify that no lag estimates are updated and that no lag estimates are - // reliable. - for (auto& le : lag_estimates) { - EXPECT_FALSE(le.updated); - EXPECT_FALSE(le.reliable); + // Verify that no lag estimates are updated and that no lag estimates are + // reliable. + for (auto& le : lag_estimates) { + EXPECT_FALSE(le.updated); + EXPECT_FALSE(le.reliable); + } } } @@ -270,11 +329,14 @@ TEST(MatchedFilter, LagNotUpdatedForLowLevelRender) { // number of alignment shifts. TEST(MatchedFilter, NumberOfLagEstimates) { ApmDataDumper data_dumper(0); - for (size_t num_matched_filters = 0; num_matched_filters < 10; - ++num_matched_filters) { - MatchedFilter filter(&data_dumper, DetectOptimization(), 32, - num_matched_filters, 1, 150); - EXPECT_EQ(num_matched_filters, filter.GetLagEstimates().size()); + for (auto down_sampling_factor : kDownSamplingFactors) { + const size_t sub_block_size = kBlockSize / down_sampling_factor; + for (size_t num_matched_filters = 0; num_matched_filters < 10; + ++num_matched_filters) { + MatchedFilter filter(&data_dumper, DetectOptimization(), sub_block_size, + 32, num_matched_filters, 1, 150); + EXPECT_EQ(num_matched_filters, filter.GetLagEstimates().size()); + } } } @@ -283,13 +345,31 @@ TEST(MatchedFilter, NumberOfLagEstimates) { // Verifies the check for non-zero windows size. TEST(MatchedFilter, ZeroWindowSize) { ApmDataDumper data_dumper(0); - EXPECT_DEATH(MatchedFilter(&data_dumper, DetectOptimization(), 0, 1, 1, 150), - ""); + EXPECT_DEATH( + MatchedFilter(&data_dumper, DetectOptimization(), 16, 0, 1, 1, 150), ""); } // Verifies the check for non-null data dumper. TEST(MatchedFilter, NullDataDumper) { - EXPECT_DEATH(MatchedFilter(nullptr, DetectOptimization(), 1, 1, 1, 150), ""); + EXPECT_DEATH(MatchedFilter(nullptr, DetectOptimization(), 16, 1, 1, 1, 150), + ""); +} + +// Verifies the check for that the sub block size is a multiple of 4. +// TODO(peah): Activate the unittest once the required code has been landed. +TEST(MatchedFilter, DISABLED_BlockSizeMultipleOf4) { + ApmDataDumper data_dumper(0); + EXPECT_DEATH( + MatchedFilter(&data_dumper, DetectOptimization(), 15, 1, 1, 1, 150), ""); +} + +// Verifies the check for that there is an integer number of sub blocks that add +// up to a block size. +// TODO(peah): Activate the unittest once the required code has been landed. +TEST(MatchedFilter, DISABLED_SubBlockSizeAddsUpToBlockSize) { + ApmDataDumper data_dumper(0); + EXPECT_DEATH( + MatchedFilter(&data_dumper, DetectOptimization(), 12, 1, 1, 1, 150), ""); } #endif 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 7e6de8bc3a..6b5870901d 100644 --- a/modules/audio_processing/aec3/mock/mock_render_delay_buffer.h +++ b/modules/audio_processing/aec3/mock/mock_render_delay_buffer.h @@ -27,8 +27,9 @@ class MockRenderDelayBuffer : public RenderDelayBuffer { explicit MockRenderDelayBuffer(int sample_rate_hz) : render_buffer_(Aec3Optimization::kNone, NumBandsForRate(sample_rate_hz), - kRenderDelayBufferSize, - std::vector(1, kAdaptiveFilterLength)) { + GetRenderDelayBufferSize(4, 4), + std::vector(1, kAdaptiveFilterLength)), + downsampled_render_buffer_(GetDownSampledBufferSize(4, 4)) { ON_CALL(*this, GetRenderBuffer()) .WillByDefault( testing::Invoke(this, &MockRenderDelayBuffer::FakeGetRenderBuffer)); diff --git a/modules/audio_processing/aec3/render_delay_buffer.cc b/modules/audio_processing/aec3/render_delay_buffer.cc index e173aa14d9..d2ead63b02 100644 --- a/modules/audio_processing/aec3/render_delay_buffer.cc +++ b/modules/audio_processing/aec3/render_delay_buffer.cc @@ -15,8 +15,9 @@ #include "modules/audio_processing/aec3/aec3_common.h" #include "modules/audio_processing/aec3/block_processor.h" -#include "modules/audio_processing/aec3/decimator_by_4.h" +#include "modules/audio_processing/aec3/decimator.h" #include "modules/audio_processing/aec3/fft_data.h" +#include "rtc_base/atomicops.h" #include "rtc_base/checks.h" #include "rtc_base/constructormagic.h" #include "rtc_base/logging.h" @@ -73,7 +74,10 @@ class ApiCallJitterBuffer { class RenderDelayBufferImpl final : public RenderDelayBuffer { public: - explicit RenderDelayBufferImpl(size_t num_bands); + RenderDelayBufferImpl(size_t num_bands, + size_t down_sampling_factor, + size_t downsampled_render_buffer_size, + size_t render_delay_buffer_size); ~RenderDelayBufferImpl() override; void Reset() override; @@ -89,30 +93,49 @@ class RenderDelayBufferImpl final : public RenderDelayBuffer { } private: + static int instance_count_; + std::unique_ptr data_dumper_; const Aec3Optimization optimization_; - std::array>, kRenderDelayBufferSize> buffer_; + const size_t down_sampling_factor_; + const size_t sub_block_size_; + std::vector>> buffer_; size_t delay_ = 0; size_t last_insert_index_ = 0; RenderBuffer fft_buffer_; DownsampledRenderBuffer downsampled_render_buffer_; - DecimatorBy4 render_decimator_; + Decimator render_decimator_; ApiCallJitterBuffer api_call_jitter_buffer_; const std::vector> zero_block_; RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RenderDelayBufferImpl); }; -RenderDelayBufferImpl::RenderDelayBufferImpl(size_t num_bands) - : optimization_(DetectOptimization()), +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) + : 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>(num_bands, + std::vector(kBlockSize, 0.f))), fft_buffer_( optimization_, num_bands, std::max(kUnknownDelayRenderWindowSize, kAdaptiveFilterLength), std::vector(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(kBlockSize, 0.f)) { - buffer_.fill(std::vector>( - num_bands, std::vector(kBlockSize, 0.f))); - RTC_DCHECK_LT(buffer_.size(), downsampled_render_buffer_.buffer.size()); } @@ -123,7 +146,8 @@ void RenderDelayBufferImpl::Reset() { delay_ = 0; last_insert_index_ = 0; downsampled_render_buffer_.position = 0; - downsampled_render_buffer_.buffer.fill(0.f); + 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_) { @@ -158,20 +182,26 @@ bool RenderDelayBufferImpl::UpdateBuffers() { } downsampled_render_buffer_.position = - (downsampled_render_buffer_.position - kSubBlockSize + + (downsampled_render_buffer_.position - sub_block_size_ + downsampled_render_buffer_.buffer.size()) % downsampled_render_buffer_.buffer.size(); - std::array render_downsampled; - if (underrun) { - render_decimator_.Decimate(zero_block_[0], render_downsampled); - } else { - render_decimator_.Decimate(buffer_[last_insert_index_][0], - render_downsampled); + rtc::ArrayView input( + underrun ? zero_block_[0].data() : buffer_[last_insert_index_][0].data(), + kBlockSize); + rtc::ArrayView 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; } - std::copy(render_downsampled.rbegin(), render_downsampled.rend(), - downsampled_render_buffer_.buffer.begin() + - downsampled_render_buffer_.position); if (underrun) { fft_buffer_.Insert(zero_block_); @@ -196,7 +226,7 @@ void RenderDelayBufferImpl::SetDelay(size_t delay) { // size. downsampled_render_buffer_.position = (downsampled_render_buffer_.position + - kSubBlockSize * (delay - (buffer_.size() - 1))) % + sub_block_size_ * (delay - (buffer_.size() - 1))) % downsampled_render_buffer_.buffer.size(); last_insert_index_ = @@ -210,8 +240,14 @@ void RenderDelayBufferImpl::SetDelay(size_t delay) { } // namespace -RenderDelayBuffer* RenderDelayBuffer::Create(size_t num_bands) { - return new RenderDelayBufferImpl(num_bands); +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); } } // namespace webrtc diff --git a/modules/audio_processing/aec3/render_delay_buffer.h b/modules/audio_processing/aec3/render_delay_buffer.h index 1678fc79c0..8f5de40752 100644 --- a/modules/audio_processing/aec3/render_delay_buffer.h +++ b/modules/audio_processing/aec3/render_delay_buffer.h @@ -27,7 +27,10 @@ namespace webrtc { // extracted with a specified delay. class RenderDelayBuffer { public: - static RenderDelayBuffer* Create(size_t num_bands); + static RenderDelayBuffer* Create(size_t num_bands, + size_t down_sampling_factor, + size_t downsampled_render_buffer_size, + size_t render_delay_buffer_size); virtual ~RenderDelayBuffer() = default; // Resets the buffer data. diff --git a/modules/audio_processing/aec3/render_delay_buffer_unittest.cc b/modules/audio_processing/aec3/render_delay_buffer_unittest.cc index 5a8f4b2401..3e0abea753 100644 --- a/modules/audio_processing/aec3/render_delay_buffer_unittest.cc +++ b/modules/audio_processing/aec3/render_delay_buffer_unittest.cc @@ -30,14 +30,19 @@ std::string ProduceDebugText(int sample_rate_hz) { 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) { for (auto rate : {8000, 16000, 32000, 48000}) { SCOPED_TRACE(ProduceDebugText(rate)); - std::unique_ptr delay_buffer( - RenderDelayBuffer::Create(NumBandsForRate(rate))); + std::unique_ptr delay_buffer(RenderDelayBuffer::Create( + NumBandsForRate(rate), kDownSamplingFactor, + GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters), + GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters))); std::vector> block_to_insert( NumBandsForRate(rate), std::vector(kBlockSize, 0.f)); for (size_t k = 0; k < kMaxApiCallsJitterBlocks; ++k) { @@ -50,8 +55,10 @@ TEST(RenderDelayBuffer, BufferOverflow) { // Verifies that the check for available block works. TEST(RenderDelayBuffer, AvailableBlock) { constexpr size_t kNumBands = 1; - std::unique_ptr delay_buffer( - RenderDelayBuffer::Create(kNumBands)); + std::unique_ptr delay_buffer(RenderDelayBuffer::Create( + kNumBands, kDownSamplingFactor, + GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters), + GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters))); std::vector> input_block( kNumBands, std::vector(kBlockSize, 1.f)); EXPECT_TRUE(delay_buffer->Insert(input_block)); @@ -60,7 +67,10 @@ TEST(RenderDelayBuffer, AvailableBlock) { // Verifies the SetDelay method. TEST(RenderDelayBuffer, SetDelay) { - std::unique_ptr delay_buffer(RenderDelayBuffer::Create(1)); + std::unique_ptr 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) { delay_buffer->SetDelay(delay); @@ -74,7 +84,10 @@ TEST(RenderDelayBuffer, SetDelay) { // 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 delay_buffer(RenderDelayBuffer::Create(3)); + std::unique_ptr delay_buffer(RenderDelayBuffer::Create( + 3, kDownSamplingFactor, + GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters), + GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters))); EXPECT_DEATH(delay_buffer->SetDelay(21), ""); } @@ -82,8 +95,10 @@ TEST(RenderDelayBuffer, DISABLED_WrongDelay) { TEST(RenderDelayBuffer, WrongNumberOfBands) { for (auto rate : {16000, 32000, 48000}) { SCOPED_TRACE(ProduceDebugText(rate)); - std::unique_ptr delay_buffer( - RenderDelayBuffer::Create(NumBandsForRate(rate))); + std::unique_ptr delay_buffer(RenderDelayBuffer::Create( + NumBandsForRate(rate), kDownSamplingFactor, + GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters), + GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters))); std::vector> block_to_insert( NumBandsForRate(rate < 48000 ? rate + 16000 : 16000), std::vector(kBlockSize, 0.f)); @@ -95,8 +110,10 @@ TEST(RenderDelayBuffer, WrongNumberOfBands) { TEST(RenderDelayBuffer, WrongBlockLength) { for (auto rate : {8000, 16000, 32000, 48000}) { SCOPED_TRACE(ProduceDebugText(rate)); - std::unique_ptr delay_buffer( - RenderDelayBuffer::Create(3)); + std::unique_ptr delay_buffer(RenderDelayBuffer::Create( + 3, kDownSamplingFactor, + GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters), + GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters))); std::vector> block_to_insert( NumBandsForRate(rate), std::vector(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 c29954eba1..7de97cd095 100644 --- a/modules/audio_processing/aec3/render_delay_controller.cc +++ b/modules/audio_processing/aec3/render_delay_controller.cc @@ -43,7 +43,6 @@ class RenderDelayControllerImpl final : public RenderDelayController { std::unique_ptr data_dumper_; 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; @@ -51,6 +50,7 @@ class RenderDelayControllerImpl final : public RenderDelayController { std::vector capture_delay_buffer_; int capture_delay_buffer_index_ = 0; RenderDelayControllerMetrics metrics_; + EchoPathDelayEstimator delay_estimator_; RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RenderDelayControllerImpl); }; @@ -81,10 +81,12 @@ RenderDelayControllerImpl::RenderDelayControllerImpl( default_delay_( std::max(config.delay.default_delay, kMinEchoPathDelayBlocks)), delay_(default_delay_), - delay_estimator_(data_dumper_.get(), config), echo_path_delay_samples_(default_delay_ * kBlockSize), - capture_delay_buffer_(kBlockSize * (kMaxApiCallsJitterBlocks + 2), 0.f) { + capture_delay_buffer_(kBlockSize * (kMaxApiCallsJitterBlocks + 2), 0.f), + delay_estimator_(data_dumper_.get(), config) { RTC_DCHECK(ValidFullBandRate(sample_rate_hz)); + delay_estimator_.LogDelayEstimationProperties(sample_rate_hz, + capture_delay_buffer_.size()); } RenderDelayControllerImpl::~RenderDelayControllerImpl() = default; diff --git a/modules/audio_processing/aec3/render_delay_controller_unittest.cc b/modules/audio_processing/aec3/render_delay_controller_unittest.cc index 7219b0b3ea..2e36d22484 100644 --- a/modules/audio_processing/aec3/render_delay_controller_unittest.cc +++ b/modules/audio_processing/aec3/render_delay_controller_unittest.cc @@ -18,7 +18,7 @@ #include "modules/audio_processing/aec3/aec3_common.h" #include "modules/audio_processing/aec3/block_processor.h" -#include "modules/audio_processing/aec3/decimator_by_4.h" +#include "modules/audio_processing/aec3/decimator.h" #include "modules/audio_processing/aec3/render_delay_buffer.h" #include "modules/audio_processing/logging/apm_data_dumper.h" #include "modules/audio_processing/test/echo_canceller_test_tools.h" @@ -40,21 +40,33 @@ std::string ProduceDebugText(int sample_rate_hz, size_t delay) { return ss.str(); } +constexpr size_t kDownSamplingFactors[] = {2, 4, 8}; + } // namespace // Verifies the output of GetDelay when there are no AnalyzeRender calls. TEST(RenderDelayController, NoRenderSignal) { std::vector block(kBlockSize, 0.f); - for (auto rate : {8000, 16000, 32000, 48000}) { - SCOPED_TRACE(ProduceDebugText(rate)); - std::unique_ptr delay_buffer( - RenderDelayBuffer::Create(NumBandsForRate(rate))); - std::unique_ptr delay_controller( - RenderDelayController::Create(EchoCanceller3Config(), rate)); - for (size_t k = 0; k < 100; ++k) { - EXPECT_EQ(kMinEchoPathDelayBlocks, - delay_controller->GetDelay( - delay_buffer->GetDownsampledRenderBuffer(), block)); + for (size_t num_matched_filters = 4; num_matched_filters == 10; + num_matched_filters++) { + for (auto down_sampling_factor : kDownSamplingFactors) { + for (auto rate : {8000, 16000, 32000, 48000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + std::unique_ptr delay_buffer( + RenderDelayBuffer::Create( + NumBandsForRate(rate), down_sampling_factor, + GetDownSampledBufferSize(down_sampling_factor, + num_matched_filters), + GetRenderDelayBufferSize(down_sampling_factor, + num_matched_filters))); + std::unique_ptr delay_controller( + RenderDelayController::Create(EchoCanceller3Config(), rate)); + for (size_t k = 0; k < 100; ++k) { + EXPECT_EQ(kMinEchoPathDelayBlocks, + delay_controller->GetDelay( + delay_buffer->GetDownsampledRenderBuffer(), block)); + } + } } } } @@ -63,21 +75,31 @@ TEST(RenderDelayController, NoRenderSignal) { TEST(RenderDelayController, BasicApiCalls) { std::vector capture_block(kBlockSize, 0.f); size_t delay_blocks = 0; - for (auto rate : {8000, 16000, 32000, 48000}) { - std::vector> render_block( - NumBandsForRate(rate), std::vector(kBlockSize, 0.f)); - std::unique_ptr render_delay_buffer( - RenderDelayBuffer::Create(NumBandsForRate(rate))); - std::unique_ptr delay_controller( - RenderDelayController::Create(EchoCanceller3Config(), rate)); - for (size_t k = 0; k < 10; ++k) { - render_delay_buffer->Insert(render_block); - render_delay_buffer->UpdateBuffers(); - delay_blocks = delay_controller->GetDelay( - render_delay_buffer->GetDownsampledRenderBuffer(), capture_block); + for (size_t num_matched_filters = 4; num_matched_filters == 10; + num_matched_filters++) { + for (auto down_sampling_factor : kDownSamplingFactors) { + for (auto rate : {8000, 16000, 32000, 48000}) { + std::vector> render_block( + NumBandsForRate(rate), std::vector(kBlockSize, 0.f)); + std::unique_ptr render_delay_buffer( + RenderDelayBuffer::Create( + NumBandsForRate(rate), down_sampling_factor, + GetDownSampledBufferSize(down_sampling_factor, + num_matched_filters), + GetRenderDelayBufferSize(down_sampling_factor, + num_matched_filters))); + std::unique_ptr delay_controller( + RenderDelayController::Create(EchoCanceller3Config(), rate)); + for (size_t k = 0; k < 10; ++k) { + render_delay_buffer->Insert(render_block); + render_delay_buffer->UpdateBuffers(); + delay_blocks = delay_controller->GetDelay( + render_delay_buffer->GetDownsampledRenderBuffer(), capture_block); + } + EXPECT_FALSE(delay_controller->AlignmentHeadroomSamples()); + EXPECT_EQ(kMinEchoPathDelayBlocks, delay_blocks); + } } - EXPECT_FALSE(delay_controller->AlignmentHeadroomSamples()); - EXPECT_EQ(kMinEchoPathDelayBlocks, delay_blocks); } } @@ -87,38 +109,49 @@ TEST(RenderDelayController, Alignment) { Random random_generator(42U); std::vector capture_block(kBlockSize, 0.f); size_t delay_blocks = 0; - for (auto rate : {8000, 16000, 32000, 48000}) { - std::vector> render_block( - NumBandsForRate(rate), std::vector(kBlockSize, 0.f)); + for (size_t num_matched_filters = 4; num_matched_filters == 10; + num_matched_filters++) { + for (auto down_sampling_factor : kDownSamplingFactors) { + for (auto rate : {8000, 16000, 32000, 48000}) { + std::vector> render_block( + NumBandsForRate(rate), std::vector(kBlockSize, 0.f)); - for (size_t delay_samples : {15, 50, 150, 200, 800, 4000}) { - SCOPED_TRACE(ProduceDebugText(rate, delay_samples)); - std::unique_ptr render_delay_buffer( - RenderDelayBuffer::Create(NumBandsForRate(rate))); - std::unique_ptr delay_controller( - RenderDelayController::Create(EchoCanceller3Config(), rate)); - DelayBuffer 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(); - delay_blocks = delay_controller->GetDelay( - render_delay_buffer->GetDownsampledRenderBuffer(), capture_block); + for (size_t delay_samples : {15, 50, 150, 200, 800, 4000}) { + SCOPED_TRACE(ProduceDebugText(rate, delay_samples)); + std::unique_ptr render_delay_buffer( + RenderDelayBuffer::Create( + NumBandsForRate(rate), down_sampling_factor, + GetDownSampledBufferSize(down_sampling_factor, + num_matched_filters), + GetRenderDelayBufferSize(down_sampling_factor, + num_matched_filters))); + std::unique_ptr delay_controller( + RenderDelayController::Create(EchoCanceller3Config(), rate)); + DelayBuffer 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(); + delay_blocks = delay_controller->GetDelay( + render_delay_buffer->GetDownsampledRenderBuffer(), + capture_block); + } + + constexpr int kDelayHeadroomBlocks = 1; + size_t expected_delay_blocks = + std::max(0, static_cast(delay_samples / kBlockSize) - + kDelayHeadroomBlocks); + + EXPECT_EQ(expected_delay_blocks, delay_blocks); + + const rtc::Optional headroom_samples = + delay_controller->AlignmentHeadroomSamples(); + ASSERT_TRUE(headroom_samples); + EXPECT_NEAR(delay_samples - delay_blocks * kBlockSize, + *headroom_samples, 4); + } } - - constexpr int kDelayHeadroomBlocks = 1; - size_t expected_delay_blocks = - std::max(0, static_cast(delay_samples / kBlockSize) - - kDelayHeadroomBlocks); - - EXPECT_EQ(expected_delay_blocks, delay_blocks); - - const rtc::Optional headroom_samples = - delay_controller->AlignmentHeadroomSamples(); - ASSERT_TRUE(headroom_samples); - EXPECT_NEAR(delay_samples - delay_blocks * kBlockSize, *headroom_samples, - 4); } } } @@ -128,35 +161,45 @@ TEST(RenderDelayController, Alignment) { 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 (size_t num_matched_filters = 4; num_matched_filters == 10; + num_matched_filters++) { + for (auto down_sampling_factor : kDownSamplingFactors) { + 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(EchoCanceller3Config(), 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]); + for (int delay_samples : {-15, -50, -150, -200}) { + SCOPED_TRACE(ProduceDebugText(rate, -delay_samples)); + std::unique_ptr render_delay_buffer( + RenderDelayBuffer::Create( + NumBandsForRate(rate), down_sampling_factor, + GetDownSampledBufferSize(down_sampling_factor, + num_matched_filters), + GetRenderDelayBufferSize(down_sampling_factor, + num_matched_filters))); + std::unique_ptr delay_controller( + RenderDelayController::Create(EchoCanceller3Config(), 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); + } } - - EXPECT_EQ(0u, delay_blocks); - - const rtc::Optional headroom_samples = - delay_controller->AlignmentHeadroomSamples(); - ASSERT_FALSE(headroom_samples); } } } @@ -166,66 +209,86 @@ TEST(RenderDelayController, NonCausalAlignment) { TEST(RenderDelayController, AlignmentWithJitter) { Random random_generator(42U); std::vector capture_block(kBlockSize, 0.f); - for (auto rate : {8000, 16000, 32000, 48000}) { - std::vector> render_block( - NumBandsForRate(rate), std::vector(kBlockSize, 0.f)); - for (size_t delay_samples : {15, 50, 300, 800}) { - size_t delay_blocks = 0; - SCOPED_TRACE(ProduceDebugText(rate, delay_samples)); - std::unique_ptr render_delay_buffer( - RenderDelayBuffer::Create(NumBandsForRate(rate))); - std::unique_ptr delay_controller( - RenderDelayController::Create(EchoCanceller3Config(), rate)); - DelayBuffer signal_delay_buffer(delay_samples); - for (size_t j = 0; - j < - (1000 + delay_samples / kBlockSize) / kMaxApiCallsJitterBlocks + 1; - ++j) { - std::vector> capture_block_buffer; - for (size_t k = 0; k < (kMaxApiCallsJitterBlocks - 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 num_matched_filters = 4; num_matched_filters == 10; + num_matched_filters++) { + for (auto down_sampling_factor : kDownSamplingFactors) { + for (auto rate : {8000, 16000, 32000, 48000}) { + std::vector> render_block( + NumBandsForRate(rate), std::vector(kBlockSize, 0.f)); + for (size_t delay_samples : {15, 50, 300, 800}) { + size_t delay_blocks = 0; + SCOPED_TRACE(ProduceDebugText(rate, delay_samples)); + std::unique_ptr render_delay_buffer( + RenderDelayBuffer::Create( + NumBandsForRate(rate), down_sampling_factor, + GetDownSampledBufferSize(down_sampling_factor, + num_matched_filters), + GetRenderDelayBufferSize(down_sampling_factor, + num_matched_filters))); + std::unique_ptr delay_controller( + RenderDelayController::Create(EchoCanceller3Config(), rate)); + DelayBuffer signal_delay_buffer(delay_samples); + for (size_t j = 0; j < (1000 + delay_samples / kBlockSize) / + kMaxApiCallsJitterBlocks + + 1; + ++j) { + std::vector> capture_block_buffer; + for (size_t k = 0; k < (kMaxApiCallsJitterBlocks - 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(); + delay_blocks = delay_controller->GetDelay( + render_delay_buffer->GetDownsampledRenderBuffer(), + capture_block_buffer[k]); + } + } + + constexpr int kDelayHeadroomBlocks = 1; + size_t expected_delay_blocks = + std::max(0, static_cast(delay_samples / kBlockSize) - + kDelayHeadroomBlocks); + if (expected_delay_blocks < 2) { + expected_delay_blocks = 0; + } + + EXPECT_EQ(expected_delay_blocks, delay_blocks); + + const rtc::Optional headroom_samples = + delay_controller->AlignmentHeadroomSamples(); + ASSERT_TRUE(headroom_samples); + EXPECT_NEAR(delay_samples - delay_blocks * kBlockSize, + *headroom_samples, 4); } - for (size_t k = 0; k < (kMaxApiCallsJitterBlocks - 1); ++k) { - render_delay_buffer->UpdateBuffers(); - delay_blocks = delay_controller->GetDelay( - render_delay_buffer->GetDownsampledRenderBuffer(), - capture_block_buffer[k]); - } - } - - constexpr int kDelayHeadroomBlocks = 1; - size_t expected_delay_blocks = - std::max(0, static_cast(delay_samples / kBlockSize) - - kDelayHeadroomBlocks); - if (expected_delay_blocks < 2) { - expected_delay_blocks = 0; - } - - EXPECT_EQ(expected_delay_blocks, delay_blocks); - - const rtc::Optional headroom_samples = - delay_controller->AlignmentHeadroomSamples(); - ASSERT_TRUE(headroom_samples); - EXPECT_NEAR(delay_samples - delay_blocks * kBlockSize, *headroom_samples, - 4); } } } +} // Verifies the initial value for the AlignmentHeadroomSamples. TEST(RenderDelayController, InitialHeadroom) { std::vector render_block(kBlockSize, 0.f); std::vector capture_block(kBlockSize, 0.f); - for (auto rate : {8000, 16000, 32000, 48000}) { - SCOPED_TRACE(ProduceDebugText(rate)); - std::unique_ptr render_delay_buffer( - RenderDelayBuffer::Create(NumBandsForRate(rate))); - std::unique_ptr delay_controller( - RenderDelayController::Create(EchoCanceller3Config(), rate)); - EXPECT_FALSE(delay_controller->AlignmentHeadroomSamples()); + for (size_t num_matched_filters = 4; num_matched_filters == 10; + num_matched_filters++) { + for (auto down_sampling_factor : kDownSamplingFactors) { + for (auto rate : {8000, 16000, 32000, 48000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + std::unique_ptr render_delay_buffer( + RenderDelayBuffer::Create( + NumBandsForRate(rate), down_sampling_factor, + GetDownSampledBufferSize(down_sampling_factor, + num_matched_filters), + GetRenderDelayBufferSize(down_sampling_factor, + num_matched_filters))); + std::unique_ptr delay_controller( + RenderDelayController::Create(EchoCanceller3Config(), rate)); + EXPECT_FALSE(delay_controller->AlignmentHeadroomSamples()); + } + } } } @@ -237,7 +300,9 @@ TEST(RenderDelayController, WrongCaptureSize) { for (auto rate : {8000, 16000, 32000, 48000}) { SCOPED_TRACE(ProduceDebugText(rate)); std::unique_ptr render_delay_buffer( - RenderDelayBuffer::Create(NumBandsForRate(rate))); + RenderDelayBuffer::Create(NumBandsForRate(rate), 4, + GetDownSampledBufferSize(4, 4), + GetRenderDelayBufferSize(4, 4))); EXPECT_DEATH( std::unique_ptr( RenderDelayController::Create(EchoCanceller3Config(), rate)) @@ -254,7 +319,9 @@ TEST(RenderDelayController, DISABLED_WrongSampleRate) { for (auto rate : {-1, 0, 8001, 16001}) { SCOPED_TRACE(ProduceDebugText(rate)); std::unique_ptr render_delay_buffer( - RenderDelayBuffer::Create(NumBandsForRate(rate))); + RenderDelayBuffer::Create(NumBandsForRate(rate), 4, + GetDownSampledBufferSize(4, 4), + GetRenderDelayBufferSize(4, 4))); EXPECT_DEATH( std::unique_ptr( RenderDelayController::Create(EchoCanceller3Config(), rate)), diff --git a/modules/audio_processing/aec3/suppression_gain.cc b/modules/audio_processing/aec3/suppression_gain.cc index df2f46a7d0..e420558685 100644 --- a/modules/audio_processing/aec3/suppression_gain.cc +++ b/modules/audio_processing/aec3/suppression_gain.cc @@ -93,7 +93,7 @@ float UpperBandsGain( // or if the power in upper frequencies is low, do not bound the gain in the // upper bands. float anti_howling_gain; - constexpr float kThreshold = kSubBlockSize * 10.f * 10.f; + constexpr float kThreshold = kBlockSize * 10.f * 10.f / 4.f; if (high_band_energy < std::max(low_band_energy, kThreshold)) { anti_howling_gain = 1.f; } else { diff --git a/modules/audio_processing/include/audio_processing.h b/modules/audio_processing/include/audio_processing.h index f3dc75b437..cdc1cb3d95 100644 --- a/modules/audio_processing/include/audio_processing.h +++ b/modules/audio_processing/include/audio_processing.h @@ -1137,6 +1137,8 @@ class VoiceDetection { struct EchoCanceller3Config { struct Delay { size_t default_delay = 5; + size_t down_sampling_factor = 4; + size_t num_filters = 4; } delay; struct Erle {