From b18c4eb0a9934e4a5ca54bb54b33777f6f449373 Mon Sep 17 00:00:00 2001 From: Sam Zackrisson Date: Fri, 24 Jan 2020 12:55:17 +0100 Subject: [PATCH] Add parameterization for three multi channel AEC3 unit tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: webrtc:11295 Change-Id: I478aa02908c494cf9609db00021438a59a132b66 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/167202 Commit-Queue: Sam Zackrisson Reviewed-by: Per Ã…hgren Cr-Commit-Position: refs/heads/master@{#30370} --- .../aec3/adaptive_fir_filter_unittest.cc | 612 +++++++++--------- .../aec3/aec_state_unittest.cc | 27 +- .../echo_path_delay_estimator_unittest.cc | 47 +- .../aec3/echo_remover_unittest.cc | 70 +- .../aec3/erl_estimator_unittest.cc | 124 ++-- .../aec3/erle_estimator_unittest.cc | 238 ++++--- .../aec3/residual_echo_estimator_unittest.cc | 146 +++-- .../shadow_filter_update_gain_unittest.cc | 166 ++--- ...ignal_dependent_erle_estimator_unittest.cc | 112 ++-- .../aec3/subtractor_unittest.cc | 102 +-- 10 files changed, 849 insertions(+), 795 deletions(-) diff --git a/modules/audio_processing/aec3/adaptive_fir_filter_unittest.cc b/modules/audio_processing/aec3/adaptive_fir_filter_unittest.cc index 9c48a43af8..9d9c79ec7a 100644 --- a/modules/audio_processing/aec3/adaptive_fir_filter_unittest.cc +++ b/modules/audio_processing/aec3/adaptive_fir_filter_unittest.cc @@ -51,15 +51,137 @@ std::string ProduceDebugText(size_t num_render_channels, size_t delay) { } // namespace +class AdaptiveFirFilterOneTwoFourEightRenderChannels + : public ::testing::Test, + public ::testing::WithParamInterface {}; + +INSTANTIATE_TEST_SUITE_P(MultiChannel, + AdaptiveFirFilterOneTwoFourEightRenderChannels, + ::testing::Values(1, 2, 4, 8)); + #if defined(WEBRTC_HAS_NEON) // Verifies that the optimized methods for filter adaptation are similar to // their reference counterparts. -TEST(AdaptiveFirFilter, FilterAdaptationNeonOptimizations) { +TEST_P(AdaptiveFirFilterOneTwoFourEightRenderChannels, + FilterAdaptationNeonOptimizations) { + const size_t num_render_channels = GetParam(); for (size_t num_partitions : {2, 5, 12, 30, 50}) { - for (size_t num_render_channels : {1, 2, 4, 8}) { - constexpr int kSampleRateHz = 48000; - constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz); + constexpr int kSampleRateHz = 48000; + constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz); + std::unique_ptr render_delay_buffer( + RenderDelayBuffer::Create(EchoCanceller3Config(), kSampleRateHz, + num_render_channels)); + Random random_generator(42U); + std::vector>> x( + kNumBands, + std::vector>(num_render_channels, + std::vector(kBlockSize, 0.f))); + FftData S_C; + FftData S_Neon; + FftData G; + Aec3Fft fft; + std::vector> H_C( + num_partitions, std::vector(num_render_channels)); + std::vector> H_Neon( + num_partitions, std::vector(num_render_channels)); + for (size_t p = 0; p < num_partitions; ++p) { + for (size_t ch = 0; ch < num_render_channels; ++ch) { + H_C[p][ch].Clear(); + H_Neon[p][ch].Clear(); + } + } + + for (size_t k = 0; k < 30; ++k) { + for (size_t band = 0; band < x.size(); ++band) { + for (size_t ch = 0; ch < x[band].size(); ++ch) { + RandomizeSampleVector(&random_generator, x[band][ch]); + } + } + render_delay_buffer->Insert(x); + if (k == 0) { + render_delay_buffer->Reset(); + } + render_delay_buffer->PrepareCaptureProcessing(); + } + auto* const render_buffer = render_delay_buffer->GetRenderBuffer(); + + for (size_t j = 0; j < G.re.size(); ++j) { + G.re[j] = j / 10001.f; + } + for (size_t j = 1; j < G.im.size() - 1; ++j) { + G.im[j] = j / 20001.f; + } + G.im[0] = 0.f; + G.im[G.im.size() - 1] = 0.f; + + AdaptPartitions_Neon(*render_buffer, G, num_partitions, &H_Neon); + AdaptPartitions(*render_buffer, G, num_partitions, &H_C); + AdaptPartitions_Neon(*render_buffer, G, num_partitions, &H_Neon); + AdaptPartitions(*render_buffer, G, num_partitions, &H_C); + + for (size_t p = 0; p < num_partitions; ++p) { + for (size_t ch = 0; ch < num_render_channels; ++ch) { + for (size_t j = 0; j < H_C[p][ch].re.size(); ++j) { + EXPECT_FLOAT_EQ(H_C[p][ch].re[j], H_Neon[p][ch].re[j]); + EXPECT_FLOAT_EQ(H_C[p][ch].im[j], H_Neon[p][ch].im[j]); + } + } + } + + ApplyFilter_Neon(*render_buffer, num_partitions, H_Neon, &S_Neon); + ApplyFilter(*render_buffer, num_partitions, H_C, &S_C); + for (size_t j = 0; j < S_C.re.size(); ++j) { + EXPECT_NEAR(S_C.re[j], S_Neon.re[j], fabs(S_C.re[j] * 0.00001f)); + EXPECT_NEAR(S_C.im[j], S_Neon.im[j], fabs(S_C.re[j] * 0.00001f)); + } + } +} + +// Verifies that the optimized method for frequency response computation is +// bitexact to the reference counterpart. +TEST_P(AdaptiveFirFilterOneTwoFourEightRenderChannels, + ComputeFrequencyResponseNeonOptimization) { + const size_t num_render_channels = GetParam(); + for (size_t num_partitions : {2, 5, 12, 30, 50}) { + std::vector> H( + num_partitions, std::vector(num_render_channels)); + std::vector> H2(num_partitions); + std::vector> H2_Neon(num_partitions); + + for (size_t p = 0; p < num_partitions; ++p) { + for (size_t ch = 0; ch < num_render_channels; ++ch) { + for (size_t k = 0; k < H[p][ch].re.size(); ++k) { + H[p][ch].re[k] = k + p / 3.f + ch; + H[p][ch].im[k] = p + k / 7.f - ch; + } + } + } + + ComputeFrequencyResponse(num_partitions, H, &H2); + ComputeFrequencyResponse_Neon(num_partitions, H, &H2_Neon); + + for (size_t p = 0; p < num_partitions; ++p) { + for (size_t k = 0; k < H2[p].size(); ++k) { + EXPECT_FLOAT_EQ(H2[p][k], H2_Neon[p][k]); + } + } + } +} +#endif + +#if defined(WEBRTC_ARCH_X86_FAMILY) +// Verifies that the optimized methods for filter adaptation are bitexact to +// their reference counterparts. +TEST_P(AdaptiveFirFilterOneTwoFourEightRenderChannels, + FilterAdaptationSse2Optimizations) { + const size_t num_render_channels = GetParam(); + constexpr int kSampleRateHz = 48000; + constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz); + + bool use_sse2 = (WebRtc_GetCPUInfo(kSSE2) != 0); + if (use_sse2) { + for (size_t num_partitions : {2, 5, 12, 30, 50}) { std::unique_ptr render_delay_buffer( RenderDelayBuffer::Create(EchoCanceller3Config(), kSampleRateHz, num_render_channels)); @@ -69,21 +191,21 @@ TEST(AdaptiveFirFilter, FilterAdaptationNeonOptimizations) { std::vector>(num_render_channels, std::vector(kBlockSize, 0.f))); FftData S_C; - FftData S_Neon; + FftData S_Sse2; FftData G; Aec3Fft fft; std::vector> H_C( num_partitions, std::vector(num_render_channels)); - std::vector> H_Neon( + std::vector> H_Sse2( num_partitions, std::vector(num_render_channels)); for (size_t p = 0; p < num_partitions; ++p) { for (size_t ch = 0; ch < num_render_channels; ++ch) { H_C[p][ch].Clear(); - H_Neon[p][ch].Clear(); + H_Sse2[p][ch].Clear(); } } - for (size_t k = 0; k < 30; ++k) { + for (size_t k = 0; k < 500; ++k) { for (size_t band = 0; band < x.size(); ++band) { for (size_t ch = 0; ch < x[band].size(); ++ch) { RandomizeSampleVector(&random_generator, x[band][ch]); @@ -94,51 +216,48 @@ TEST(AdaptiveFirFilter, FilterAdaptationNeonOptimizations) { render_delay_buffer->Reset(); } render_delay_buffer->PrepareCaptureProcessing(); - } - auto* const render_buffer = render_delay_buffer->GetRenderBuffer(); + auto* const render_buffer = render_delay_buffer->GetRenderBuffer(); - for (size_t j = 0; j < G.re.size(); ++j) { - G.re[j] = j / 10001.f; - } - for (size_t j = 1; j < G.im.size() - 1; ++j) { - G.im[j] = j / 20001.f; - } - G.im[0] = 0.f; - G.im[G.im.size() - 1] = 0.f; + ApplyFilter_Sse2(*render_buffer, num_partitions, H_Sse2, &S_Sse2); + ApplyFilter(*render_buffer, num_partitions, H_C, &S_C); + for (size_t j = 0; j < S_C.re.size(); ++j) { + EXPECT_FLOAT_EQ(S_C.re[j], S_Sse2.re[j]); + EXPECT_FLOAT_EQ(S_C.im[j], S_Sse2.im[j]); + } - AdaptPartitions_Neon(*render_buffer, G, num_partitions, &H_Neon); - AdaptPartitions(*render_buffer, G, num_partitions, &H_C); - AdaptPartitions_Neon(*render_buffer, G, num_partitions, &H_Neon); - AdaptPartitions(*render_buffer, G, num_partitions, &H_C); + std::for_each(G.re.begin(), G.re.end(), + [&](float& a) { a = random_generator.Rand(); }); + std::for_each(G.im.begin(), G.im.end(), + [&](float& a) { a = random_generator.Rand(); }); - for (size_t p = 0; p < num_partitions; ++p) { - for (size_t ch = 0; ch < num_render_channels; ++ch) { - for (size_t j = 0; j < H_C[p][ch].re.size(); ++j) { - EXPECT_FLOAT_EQ(H_C[p][ch].re[j], H_Neon[p][ch].re[j]); - EXPECT_FLOAT_EQ(H_C[p][ch].im[j], H_Neon[p][ch].im[j]); + AdaptPartitions_Sse2(*render_buffer, G, num_partitions, &H_Sse2); + AdaptPartitions(*render_buffer, G, num_partitions, &H_C); + + for (size_t p = 0; p < num_partitions; ++p) { + for (size_t ch = 0; ch < num_render_channels; ++ch) { + for (size_t j = 0; j < H_C[p][ch].re.size(); ++j) { + EXPECT_FLOAT_EQ(H_C[p][ch].re[j], H_Sse2[p][ch].re[j]); + EXPECT_FLOAT_EQ(H_C[p][ch].im[j], H_Sse2[p][ch].im[j]); + } } } } - - ApplyFilter_Neon(*render_buffer, num_partitions, H_Neon, &S_Neon); - ApplyFilter(*render_buffer, num_partitions, H_C, &S_C); - for (size_t j = 0; j < S_C.re.size(); ++j) { - EXPECT_NEAR(S_C.re[j], S_Neon.re[j], fabs(S_C.re[j] * 0.00001f)); - EXPECT_NEAR(S_C.im[j], S_Neon.im[j], fabs(S_C.re[j] * 0.00001f)); - } } } } // Verifies that the optimized method for frequency response computation is // bitexact to the reference counterpart. -TEST(AdaptiveFirFilter, ComputeFrequencyResponseNeonOptimization) { - for (size_t num_partitions : {2, 5, 12, 30, 50}) { - for (size_t num_render_channels : {1, 2, 4, 8}) { +TEST_P(AdaptiveFirFilterOneTwoFourEightRenderChannels, + ComputeFrequencyResponseSse2Optimization) { + const size_t num_render_channels = GetParam(); + bool use_sse2 = (WebRtc_GetCPUInfo(kSSE2) != 0); + if (use_sse2) { + for (size_t num_partitions : {2, 5, 12, 30, 50}) { std::vector> H( num_partitions, std::vector(num_render_channels)); std::vector> H2(num_partitions); - std::vector> H2_Neon( + std::vector> H2_Sse2( num_partitions); for (size_t p = 0; p < num_partitions; ++p) { @@ -151,123 +270,11 @@ TEST(AdaptiveFirFilter, ComputeFrequencyResponseNeonOptimization) { } ComputeFrequencyResponse(num_partitions, H, &H2); - ComputeFrequencyResponse_Neon(num_partitions, H, &H2_Neon); + ComputeFrequencyResponse_Sse2(num_partitions, H, &H2_Sse2); for (size_t p = 0; p < num_partitions; ++p) { for (size_t k = 0; k < H2[p].size(); ++k) { - EXPECT_FLOAT_EQ(H2[p][k], H2_Neon[p][k]); - } - } - } - } -} -#endif - -#if defined(WEBRTC_ARCH_X86_FAMILY) -// Verifies that the optimized methods for filter adaptation are bitexact to -// their reference counterparts. -TEST(AdaptiveFirFilter, FilterAdaptationSse2Optimizations) { - constexpr int kSampleRateHz = 48000; - constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz); - - bool use_sse2 = (WebRtc_GetCPUInfo(kSSE2) != 0); - if (use_sse2) { - for (size_t num_partitions : {2, 5, 12, 30, 50}) { - for (size_t num_render_channels : {1, 2, 4, 8}) { - std::unique_ptr render_delay_buffer( - RenderDelayBuffer::Create(EchoCanceller3Config(), kSampleRateHz, - num_render_channels)); - Random random_generator(42U); - std::vector>> x( - kNumBands, - std::vector>( - num_render_channels, std::vector(kBlockSize, 0.f))); - FftData S_C; - FftData S_Sse2; - FftData G; - Aec3Fft fft; - std::vector> H_C( - num_partitions, std::vector(num_render_channels)); - std::vector> H_Sse2( - num_partitions, std::vector(num_render_channels)); - for (size_t p = 0; p < num_partitions; ++p) { - for (size_t ch = 0; ch < num_render_channels; ++ch) { - H_C[p][ch].Clear(); - H_Sse2[p][ch].Clear(); - } - } - - for (size_t k = 0; k < 500; ++k) { - for (size_t band = 0; band < x.size(); ++band) { - for (size_t ch = 0; ch < x[band].size(); ++ch) { - RandomizeSampleVector(&random_generator, x[band][ch]); - } - } - render_delay_buffer->Insert(x); - if (k == 0) { - render_delay_buffer->Reset(); - } - render_delay_buffer->PrepareCaptureProcessing(); - auto* const render_buffer = render_delay_buffer->GetRenderBuffer(); - - ApplyFilter_Sse2(*render_buffer, num_partitions, H_Sse2, &S_Sse2); - ApplyFilter(*render_buffer, num_partitions, H_C, &S_C); - for (size_t j = 0; j < S_C.re.size(); ++j) { - EXPECT_FLOAT_EQ(S_C.re[j], S_Sse2.re[j]); - EXPECT_FLOAT_EQ(S_C.im[j], S_Sse2.im[j]); - } - - std::for_each(G.re.begin(), G.re.end(), - [&](float& a) { a = random_generator.Rand(); }); - std::for_each(G.im.begin(), G.im.end(), - [&](float& a) { a = random_generator.Rand(); }); - - AdaptPartitions_Sse2(*render_buffer, G, num_partitions, &H_Sse2); - AdaptPartitions(*render_buffer, G, num_partitions, &H_C); - - for (size_t p = 0; p < num_partitions; ++p) { - for (size_t ch = 0; ch < num_render_channels; ++ch) { - for (size_t j = 0; j < H_C[p][ch].re.size(); ++j) { - EXPECT_FLOAT_EQ(H_C[p][ch].re[j], H_Sse2[p][ch].re[j]); - EXPECT_FLOAT_EQ(H_C[p][ch].im[j], H_Sse2[p][ch].im[j]); - } - } - } - } - } - } - } -} - -// Verifies that the optimized method for frequency response computation is -// bitexact to the reference counterpart. -TEST(AdaptiveFirFilter, ComputeFrequencyResponseSse2Optimization) { - bool use_sse2 = (WebRtc_GetCPUInfo(kSSE2) != 0); - if (use_sse2) { - for (size_t num_partitions : {2, 5, 12, 30, 50}) { - for (size_t num_render_channels : {1, 2, 4, 8}) { - std::vector> H( - num_partitions, std::vector(num_render_channels)); - std::vector> H2(num_partitions); - std::vector> H2_Sse2( - num_partitions); - - for (size_t p = 0; p < num_partitions; ++p) { - for (size_t ch = 0; ch < num_render_channels; ++ch) { - for (size_t k = 0; k < H[p][ch].re.size(); ++k) { - H[p][ch].re[k] = k + p / 3.f + ch; - H[p][ch].im[k] = p + k / 7.f - ch; - } - } - } - - ComputeFrequencyResponse(num_partitions, H, &H2); - ComputeFrequencyResponse_Sse2(num_partitions, H, &H2_Sse2); - - for (size_t p = 0; p < num_partitions; ++p) { - for (size_t k = 0; k < H2[p].size(); ++k) { - EXPECT_FLOAT_EQ(H2[p][k], H2_Sse2[p][k]); - } + EXPECT_FLOAT_EQ(H2[p][k], H2_Sse2[p][k]); } } } @@ -278,13 +285,13 @@ TEST(AdaptiveFirFilter, ComputeFrequencyResponseSse2Optimization) { #if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) // Verifies that the check for non-null data dumper works. -TEST(AdaptiveFirFilter, NullDataDumper) { +TEST(AdaptiveFirFilterTest, NullDataDumper) { EXPECT_DEATH(AdaptiveFirFilter(9, 9, 250, 1, DetectOptimization(), nullptr), ""); } // Verifies that the check for non-null filter output works. -TEST(AdaptiveFirFilter, NullFilterOutput) { +TEST(AdaptiveFirFilterTest, NullFilterOutput) { ApmDataDumper data_dumper(42); AdaptiveFirFilter filter(9, 9, 250, 1, DetectOptimization(), &data_dumper); std::unique_ptr render_delay_buffer( @@ -297,7 +304,7 @@ TEST(AdaptiveFirFilter, NullFilterOutput) { // Verifies that the filter statistics can be accessed when filter statistics // are turned on. -TEST(AdaptiveFirFilter, FilterStatisticsAccess) { +TEST(AdaptiveFirFilterTest, FilterStatisticsAccess) { ApmDataDumper data_dumper(42); Aec3Optimization optimization = DetectOptimization(); AdaptiveFirFilter filter(9, 9, 250, 1, optimization, &data_dumper); @@ -314,7 +321,7 @@ TEST(AdaptiveFirFilter, FilterStatisticsAccess) { } // Verifies that the filter size if correctly repported. -TEST(AdaptiveFirFilter, FilterSize) { +TEST(AdaptiveFirFilterTest, FilterSize) { ApmDataDumper data_dumper(42); for (size_t filter_size = 1; filter_size < 5; ++filter_size) { AdaptiveFirFilter filter(filter_size, filter_size, 250, 1, @@ -323,163 +330,166 @@ TEST(AdaptiveFirFilter, FilterSize) { } } +class AdaptiveFirFilterMultiChannel + : public ::testing::Test, + public ::testing::WithParamInterface> {}; + +INSTANTIATE_TEST_SUITE_P(MultiChannel, + AdaptiveFirFilterMultiChannel, + ::testing::Combine(::testing::Values(1, 4), + ::testing::Values(1, 8))); + // Verifies that the filter is being able to properly filter a signal and to // adapt its coefficients. -TEST(AdaptiveFirFilter, FilterAndAdapt) { +TEST_P(AdaptiveFirFilterMultiChannel, FilterAndAdapt) { + const size_t num_render_channels = std::get<0>(GetParam()); + const size_t num_capture_channels = std::get<1>(GetParam()); + constexpr int kSampleRateHz = 48000; constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz); constexpr size_t kNumBlocksToProcessPerRenderChannel = 1000; - for (size_t num_capture_channels : {1, 4}) { - for (size_t num_render_channels : {1, 8}) { - ApmDataDumper data_dumper(42); - EchoCanceller3Config config; + ApmDataDumper data_dumper(42); + EchoCanceller3Config config; - if (num_render_channels == 33) { - config.filter.main = {13, 0.00005f, 0.0005f, 0.0001f, 2.f, 20075344.f}; - config.filter.shadow = {13, 0.1f, 20075344.f}; - config.filter.main_initial = {12, 0.005f, 0.5f, - 0.001f, 2.f, 20075344.f}; - config.filter.shadow_initial = {12, 0.7f, 20075344.f}; - } + if (num_render_channels == 33) { + config.filter.main = {13, 0.00005f, 0.0005f, 0.0001f, 2.f, 20075344.f}; + config.filter.shadow = {13, 0.1f, 20075344.f}; + config.filter.main_initial = {12, 0.005f, 0.5f, 0.001f, 2.f, 20075344.f}; + config.filter.shadow_initial = {12, 0.7f, 20075344.f}; + } - AdaptiveFirFilter filter( - config.filter.main.length_blocks, config.filter.main.length_blocks, - config.filter.config_change_duration_blocks, num_render_channels, - DetectOptimization(), &data_dumper); - std::vector>> H2( - num_capture_channels, - std::vector>( - filter.max_filter_size_partitions(), - std::array())); - std::vector> h( - num_capture_channels, - std::vector( - GetTimeDomainLength(filter.max_filter_size_partitions()), 0.f)); - Aec3Fft fft; - config.delay.default_delay = 1; - std::unique_ptr render_delay_buffer( - RenderDelayBuffer::Create(config, kSampleRateHz, - num_render_channels)); - ShadowFilterUpdateGain gain(config.filter.shadow, - config.filter.config_change_duration_blocks); - Random random_generator(42U); - std::vector>> x( - kNumBands, - std::vector>(num_render_channels, - std::vector(kBlockSize, 0.f))); - std::vector n(kBlockSize, 0.f); - std::vector y(kBlockSize, 0.f); - AecState aec_state(EchoCanceller3Config{}, num_capture_channels); - RenderSignalAnalyzer render_signal_analyzer(config); - absl::optional delay_estimate; - std::vector e(kBlockSize, 0.f); - std::array s_scratch; - std::vector output(num_capture_channels); - FftData S; - FftData G; - FftData E; - std::vector> Y2( - num_capture_channels); - std::vector> E2_main( - num_capture_channels); - std::array E2_shadow; - // [B,A] = butter(2,100/8000,'high') - constexpr CascadedBiQuadFilter::BiQuadCoefficients - kHighPassFilterCoefficients = {{0.97261f, -1.94523f, 0.97261f}, - {-1.94448f, 0.94598f}}; - for (auto& Y2_ch : Y2) { - Y2_ch.fill(0.f); - } - for (auto& E2_main_ch : E2_main) { - E2_main_ch.fill(0.f); - } - E2_shadow.fill(0.f); - for (auto& subtractor_output : output) { - subtractor_output.Reset(); - } + AdaptiveFirFilter filter( + config.filter.main.length_blocks, config.filter.main.length_blocks, + config.filter.config_change_duration_blocks, num_render_channels, + DetectOptimization(), &data_dumper); + std::vector>> H2( + num_capture_channels, std::vector>( + filter.max_filter_size_partitions(), + std::array())); + std::vector> h( + num_capture_channels, + std::vector( + GetTimeDomainLength(filter.max_filter_size_partitions()), 0.f)); + Aec3Fft fft; + config.delay.default_delay = 1; + std::unique_ptr render_delay_buffer( + RenderDelayBuffer::Create(config, kSampleRateHz, num_render_channels)); + ShadowFilterUpdateGain gain(config.filter.shadow, + config.filter.config_change_duration_blocks); + Random random_generator(42U); + std::vector>> x( + kNumBands, std::vector>( + num_render_channels, std::vector(kBlockSize, 0.f))); + std::vector n(kBlockSize, 0.f); + std::vector y(kBlockSize, 0.f); + AecState aec_state(EchoCanceller3Config{}, num_capture_channels); + RenderSignalAnalyzer render_signal_analyzer(config); + absl::optional delay_estimate; + std::vector e(kBlockSize, 0.f); + std::array s_scratch; + std::vector output(num_capture_channels); + FftData S; + FftData G; + FftData E; + std::vector> Y2(num_capture_channels); + std::vector> E2_main( + num_capture_channels); + std::array E2_shadow; + // [B,A] = butter(2,100/8000,'high') + constexpr CascadedBiQuadFilter::BiQuadCoefficients + kHighPassFilterCoefficients = {{0.97261f, -1.94523f, 0.97261f}, + {-1.94448f, 0.94598f}}; + for (auto& Y2_ch : Y2) { + Y2_ch.fill(0.f); + } + for (auto& E2_main_ch : E2_main) { + E2_main_ch.fill(0.f); + } + E2_shadow.fill(0.f); + for (auto& subtractor_output : output) { + subtractor_output.Reset(); + } - constexpr float kScale = 1.0f / kFftLengthBy2; + constexpr float kScale = 1.0f / kFftLengthBy2; - for (size_t delay_samples : {0, 64, 150, 200, 301}) { - std::vector> delay_buffer( - num_render_channels, DelayBuffer(delay_samples)); - std::vector> x_hp_filter( - num_render_channels); - for (size_t ch = 0; ch < num_render_channels; ++ch) { - x_hp_filter[ch] = std::make_unique( - kHighPassFilterCoefficients, 1); - } - CascadedBiQuadFilter y_hp_filter(kHighPassFilterCoefficients, 1); - - SCOPED_TRACE(ProduceDebugText(num_render_channels, delay_samples)); - const size_t num_blocks_to_process = - kNumBlocksToProcessPerRenderChannel * num_render_channels; - for (size_t j = 0; j < num_blocks_to_process; ++j) { - std::fill(y.begin(), y.end(), 0.f); - for (size_t ch = 0; ch < num_render_channels; ++ch) { - RandomizeSampleVector(&random_generator, x[0][ch]); - std::array y_channel; - delay_buffer[ch].Delay(x[0][ch], y_channel); - for (size_t k = 0; k < y.size(); ++k) { - y[k] += y_channel[k] / num_render_channels; - } - } - - RandomizeSampleVector(&random_generator, n); - const float noise_scaling = 1.f / 100.f / num_render_channels; - for (size_t k = 0; k < y.size(); ++k) { - y[k] += n[k] * noise_scaling; - } - - for (size_t ch = 0; ch < num_render_channels; ++ch) { - x_hp_filter[ch]->Process(x[0][ch]); - } - y_hp_filter.Process(y); - - render_delay_buffer->Insert(x); - if (j == 0) { - render_delay_buffer->Reset(); - } - render_delay_buffer->PrepareCaptureProcessing(); - auto* const render_buffer = render_delay_buffer->GetRenderBuffer(); - - render_signal_analyzer.Update(*render_buffer, - aec_state.MinDirectPathFilterDelay()); - - filter.Filter(*render_buffer, &S); - fft.Ifft(S, &s_scratch); - std::transform(y.begin(), y.end(), s_scratch.begin() + kFftLengthBy2, - e.begin(), - [&](float a, float b) { return a - b * kScale; }); - std::for_each(e.begin(), e.end(), [](float& a) { - a = rtc::SafeClamp(a, -32768.f, 32767.f); - }); - fft.ZeroPaddedFft(e, Aec3Fft::Window::kRectangular, &E); - for (auto& o : output) { - for (size_t k = 0; k < kBlockSize; ++k) { - o.s_main[k] = kScale * s_scratch[k + kFftLengthBy2]; - } - } - - std::array render_power; - render_buffer->SpectralSum(filter.SizePartitions(), &render_power); - gain.Compute(render_power, render_signal_analyzer, E, - filter.SizePartitions(), false, &G); - filter.Adapt(*render_buffer, G, &h[0]); - aec_state.HandleEchoPathChange(EchoPathVariability( - false, EchoPathVariability::DelayAdjustment::kNone, false)); - - filter.ComputeFrequencyResponse(&H2[0]); - aec_state.Update(delay_estimate, H2, h, *render_buffer, E2_main, Y2, - output); - } - // Verify that the filter is able to perform well. - EXPECT_LT(1000 * std::inner_product(e.begin(), e.end(), e.begin(), 0.f), - std::inner_product(y.begin(), y.end(), y.begin(), 0.f)); - } + for (size_t delay_samples : {0, 64, 150, 200, 301}) { + std::vector> delay_buffer( + num_render_channels, DelayBuffer(delay_samples)); + std::vector> x_hp_filter( + num_render_channels); + for (size_t ch = 0; ch < num_render_channels; ++ch) { + x_hp_filter[ch] = std::make_unique( + kHighPassFilterCoefficients, 1); } + CascadedBiQuadFilter y_hp_filter(kHighPassFilterCoefficients, 1); + + SCOPED_TRACE(ProduceDebugText(num_render_channels, delay_samples)); + const size_t num_blocks_to_process = + kNumBlocksToProcessPerRenderChannel * num_render_channels; + for (size_t j = 0; j < num_blocks_to_process; ++j) { + std::fill(y.begin(), y.end(), 0.f); + for (size_t ch = 0; ch < num_render_channels; ++ch) { + RandomizeSampleVector(&random_generator, x[0][ch]); + std::array y_channel; + delay_buffer[ch].Delay(x[0][ch], y_channel); + for (size_t k = 0; k < y.size(); ++k) { + y[k] += y_channel[k] / num_render_channels; + } + } + + RandomizeSampleVector(&random_generator, n); + const float noise_scaling = 1.f / 100.f / num_render_channels; + for (size_t k = 0; k < y.size(); ++k) { + y[k] += n[k] * noise_scaling; + } + + for (size_t ch = 0; ch < num_render_channels; ++ch) { + x_hp_filter[ch]->Process(x[0][ch]); + } + y_hp_filter.Process(y); + + render_delay_buffer->Insert(x); + if (j == 0) { + render_delay_buffer->Reset(); + } + render_delay_buffer->PrepareCaptureProcessing(); + auto* const render_buffer = render_delay_buffer->GetRenderBuffer(); + + render_signal_analyzer.Update(*render_buffer, + aec_state.MinDirectPathFilterDelay()); + + filter.Filter(*render_buffer, &S); + fft.Ifft(S, &s_scratch); + std::transform(y.begin(), y.end(), s_scratch.begin() + kFftLengthBy2, + e.begin(), + [&](float a, float b) { return a - b * kScale; }); + std::for_each(e.begin(), e.end(), + [](float& a) { a = rtc::SafeClamp(a, -32768.f, 32767.f); }); + fft.ZeroPaddedFft(e, Aec3Fft::Window::kRectangular, &E); + for (auto& o : output) { + for (size_t k = 0; k < kBlockSize; ++k) { + o.s_main[k] = kScale * s_scratch[k + kFftLengthBy2]; + } + } + + std::array render_power; + render_buffer->SpectralSum(filter.SizePartitions(), &render_power); + gain.Compute(render_power, render_signal_analyzer, E, + filter.SizePartitions(), false, &G); + filter.Adapt(*render_buffer, G, &h[0]); + aec_state.HandleEchoPathChange(EchoPathVariability( + false, EchoPathVariability::DelayAdjustment::kNone, false)); + + filter.ComputeFrequencyResponse(&H2[0]); + aec_state.Update(delay_estimate, H2, h, *render_buffer, E2_main, Y2, + output); + } + // Verify that the filter is able to perform well. + EXPECT_LT(1000 * std::inner_product(e.begin(), e.end(), e.begin(), 0.f), + std::inner_product(y.begin(), y.end(), y.begin(), 0.f)); } } + } // namespace aec3 } // namespace webrtc diff --git a/modules/audio_processing/aec3/aec_state_unittest.cc b/modules/audio_processing/aec3/aec_state_unittest.cc index c068b6e5f4..3ca8220471 100644 --- a/modules/audio_processing/aec3/aec_state_unittest.cc +++ b/modules/audio_processing/aec3/aec_state_unittest.cc @@ -18,13 +18,6 @@ namespace webrtc { namespace { -std::string ProduceDebugText(size_t num_render_channels, - size_t num_capture_channels) { - rtc::StringBuilder ss; - ss << "Render channels: " << num_render_channels; - ss << ", Capture channels: " << num_capture_channels; - return ss.Release(); -} void RunNormalUsageTest(size_t num_render_channels, size_t num_capture_channels) { @@ -232,14 +225,20 @@ void RunNormalUsageTest(size_t num_render_channels, } // namespace +class AecStateMultiChannel + : public ::testing::Test, + public ::testing::WithParamInterface> {}; + +INSTANTIATE_TEST_SUITE_P(MultiChannel, + AecStateMultiChannel, + ::testing::Combine(::testing::Values(1, 2, 8), + ::testing::Values(1, 2, 8))); + // Verify the general functionality of AecState -TEST(AecState, NormalUsage) { - for (size_t num_render_channels : {1, 2, 8}) { - for (size_t num_capture_channels : {1, 2, 8}) { - SCOPED_TRACE(ProduceDebugText(num_render_channels, num_capture_channels)); - RunNormalUsageTest(num_render_channels, num_capture_channels); - } - } +TEST_P(AecStateMultiChannel, NormalUsage) { + const size_t num_render_channels = std::get<0>(GetParam()); + const size_t num_capture_channels = std::get<1>(GetParam()); + RunNormalUsageTest(num_render_channels, num_capture_channels); } // Verifies the delay for a converged filter is correctly identified. 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 ec64533de8..8003a11bbc 100644 --- a/modules/audio_processing/aec3/echo_path_delay_estimator_unittest.cc +++ b/modules/audio_processing/aec3/echo_path_delay_estimator_unittest.cc @@ -34,30 +34,35 @@ std::string ProduceDebugText(size_t delay, size_t down_sampling_factor) { } // namespace +class EchoPathDelayEstimatorMultiChannel + : public ::testing::Test, + public ::testing::WithParamInterface> {}; + +INSTANTIATE_TEST_SUITE_P(MultiChannel, + EchoPathDelayEstimatorMultiChannel, + ::testing::Combine(::testing::Values(1, 2, 3, 6, 8), + ::testing::Values(1, 2, 4))); + // Verifies that the basic API calls work. -TEST(EchoPathDelayEstimator, BasicApiCalls) { +TEST_P(EchoPathDelayEstimatorMultiChannel, BasicApiCalls) { + const size_t num_render_channels = std::get<0>(GetParam()); + const size_t num_capture_channels = std::get<1>(GetParam()); constexpr int kSampleRateHz = 48000; constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz); - for (size_t num_capture_channels : {1, 2, 4}) { - for (size_t num_render_channels : {1, 2, 3, 6, 8}) { - ApmDataDumper data_dumper(0); - EchoCanceller3Config config; - std::unique_ptr render_delay_buffer( - RenderDelayBuffer::Create(config, kSampleRateHz, - num_render_channels)); - EchoPathDelayEstimator estimator(&data_dumper, config, - num_capture_channels); - std::vector>> render( - kNumBands, std::vector>( - num_render_channels, std::vector(kBlockSize))); - std::vector> capture(num_capture_channels, - std::vector(kBlockSize)); - for (size_t k = 0; k < 100; ++k) { - render_delay_buffer->Insert(render); - estimator.EstimateDelay( - render_delay_buffer->GetDownsampledRenderBuffer(), capture); - } - } + ApmDataDumper data_dumper(0); + EchoCanceller3Config config; + std::unique_ptr render_delay_buffer( + RenderDelayBuffer::Create(config, kSampleRateHz, num_render_channels)); + EchoPathDelayEstimator estimator(&data_dumper, config, num_capture_channels); + std::vector>> render( + kNumBands, std::vector>( + num_render_channels, std::vector(kBlockSize))); + std::vector> capture(num_capture_channels, + std::vector(kBlockSize)); + for (size_t k = 0; k < 100; ++k) { + render_delay_buffer->Insert(render); + 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 d79993ac69..e050027c63 100644 --- a/modules/audio_processing/aec3/echo_remover_unittest.cc +++ b/modules/audio_processing/aec3/echo_remover_unittest.cc @@ -26,7 +26,6 @@ namespace webrtc { namespace { - std::string ProduceDebugText(int sample_rate_hz) { rtc::StringBuilder ss; ss << "Sample rate: " << sample_rate_hz; @@ -41,43 +40,48 @@ std::string ProduceDebugText(int sample_rate_hz, int delay) { } // namespace +class EchoRemoverMultiChannel + : public ::testing::Test, + public ::testing::WithParamInterface> {}; + +INSTANTIATE_TEST_SUITE_P(MultiChannel, + EchoRemoverMultiChannel, + ::testing::Combine(::testing::Values(1, 2, 8), + ::testing::Values(1, 2, 8))); + // Verifies the basic API call sequence -TEST(EchoRemover, BasicApiCalls) { +TEST_P(EchoRemoverMultiChannel, BasicApiCalls) { + const size_t num_render_channels = std::get<0>(GetParam()); + const size_t num_capture_channels = std::get<1>(GetParam()); absl::optional delay_estimate; for (auto rate : {16000, 32000, 48000}) { - for (size_t num_render_channels : {1, 2, 8}) { - for (size_t num_capture_channels : {1, 2, 8}) { - SCOPED_TRACE(ProduceDebugText(rate)); - std::unique_ptr remover( - EchoRemover::Create(EchoCanceller3Config(), rate, - num_render_channels, num_capture_channels)); - std::unique_ptr render_buffer( - RenderDelayBuffer::Create(EchoCanceller3Config(), rate, - num_render_channels)); + SCOPED_TRACE(ProduceDebugText(rate)); + std::unique_ptr remover( + EchoRemover::Create(EchoCanceller3Config(), rate, num_render_channels, + num_capture_channels)); + std::unique_ptr render_buffer(RenderDelayBuffer::Create( + EchoCanceller3Config(), rate, num_render_channels)); - std::vector>> render( - NumBandsForRate(rate), - std::vector>( - num_render_channels, std::vector(kBlockSize, 0.f))); - std::vector>> capture( - NumBandsForRate(rate), - std::vector>( - num_capture_channels, std::vector(kBlockSize, 0.f))); - for (size_t k = 0; k < 100; ++k) { - EchoPathVariability echo_path_variability( - k % 3 == 0 ? true : false, - k % 5 == 0 - ? EchoPathVariability::DelayAdjustment::kNewDetectedDelay - : EchoPathVariability::DelayAdjustment::kNone, - false); - render_buffer->Insert(render); - render_buffer->PrepareCaptureProcessing(); + std::vector>> render( + NumBandsForRate(rate), + std::vector>(num_render_channels, + std::vector(kBlockSize, 0.f))); + std::vector>> capture( + NumBandsForRate(rate), + std::vector>(num_capture_channels, + std::vector(kBlockSize, 0.f))); + for (size_t k = 0; k < 100; ++k) { + EchoPathVariability echo_path_variability( + k % 3 == 0 ? true : false, + k % 5 == 0 ? EchoPathVariability::DelayAdjustment::kNewDetectedDelay + : EchoPathVariability::DelayAdjustment::kNone, + false); + render_buffer->Insert(render); + render_buffer->PrepareCaptureProcessing(); - remover->ProcessCapture( - echo_path_variability, k % 2 == 0 ? true : false, delay_estimate, - render_buffer->GetRenderBuffer(), nullptr, &capture); - } - } + remover->ProcessCapture(echo_path_variability, k % 2 == 0 ? true : false, + delay_estimate, render_buffer->GetRenderBuffer(), + nullptr, &capture); } } } diff --git a/modules/audio_processing/aec3/erl_estimator_unittest.cc b/modules/audio_processing/aec3/erl_estimator_unittest.cc index 344551dd1f..79e5465e3c 100644 --- a/modules/audio_processing/aec3/erl_estimator_unittest.cc +++ b/modules/audio_processing/aec3/erl_estimator_unittest.cc @@ -34,67 +34,71 @@ void VerifyErl(const std::array& erl, } // namespace +class ErlEstimatorMultiChannel + : public ::testing::Test, + public ::testing::WithParamInterface> {}; + +INSTANTIATE_TEST_SUITE_P(MultiChannel, + ErlEstimatorMultiChannel, + ::testing::Combine(::testing::Values(1, 2, 8), + ::testing::Values(1, 2, 8))); + // Verifies that the correct ERL estimates are achieved. -TEST(ErlEstimator, Estimates) { - for (size_t num_render_channels : {1, 2, 8}) { - for (size_t num_capture_channels : {1, 2, 8}) { - SCOPED_TRACE(ProduceDebugText(num_render_channels, num_capture_channels)); - std::vector> X2( - num_render_channels); - for (auto& X2_ch : X2) { - X2_ch.fill(0.f); - } - std::vector> Y2( - num_capture_channels); - for (auto& Y2_ch : Y2) { - Y2_ch.fill(0.f); - } - std::vector converged_filters(num_capture_channels, false); - const size_t converged_idx = num_capture_channels - 1; - converged_filters[converged_idx] = true; - - ErlEstimator estimator(0); - - // Verifies that the ERL estimate is properly reduced to lower values. - for (auto& X2_ch : X2) { - X2_ch.fill(500 * 1000.f * 1000.f); - } - Y2[converged_idx].fill(10 * X2[0][0]); - for (size_t k = 0; k < 200; ++k) { - estimator.Update(converged_filters, X2, Y2); - } - VerifyErl(estimator.Erl(), estimator.ErlTimeDomain(), 10.f); - - // Verifies that the ERL is not immediately increased when the ERL in the - // data increases. - Y2[converged_idx].fill(10000 * X2[0][0]); - for (size_t k = 0; k < 998; ++k) { - estimator.Update(converged_filters, X2, Y2); - } - VerifyErl(estimator.Erl(), estimator.ErlTimeDomain(), 10.f); - - // Verifies that the rate of increase is 3 dB. - estimator.Update(converged_filters, X2, Y2); - VerifyErl(estimator.Erl(), estimator.ErlTimeDomain(), 20.f); - - // Verifies that the maximum ERL is achieved when there are no low RLE - // estimates. - for (size_t k = 0; k < 1000; ++k) { - estimator.Update(converged_filters, X2, Y2); - } - VerifyErl(estimator.Erl(), estimator.ErlTimeDomain(), 1000.f); - - // Verifies that the ERL estimate is is not updated for low-level signals - for (auto& X2_ch : X2) { - X2_ch.fill(1000.f * 1000.f); - } - Y2[converged_idx].fill(10 * X2[0][0]); - for (size_t k = 0; k < 200; ++k) { - estimator.Update(converged_filters, X2, Y2); - } - VerifyErl(estimator.Erl(), estimator.ErlTimeDomain(), 1000.f); - } +TEST_P(ErlEstimatorMultiChannel, Estimates) { + const size_t num_render_channels = std::get<0>(GetParam()); + const size_t num_capture_channels = std::get<1>(GetParam()); + SCOPED_TRACE(ProduceDebugText(num_render_channels, num_capture_channels)); + std::vector> X2(num_render_channels); + for (auto& X2_ch : X2) { + X2_ch.fill(0.f); } -} + std::vector> Y2(num_capture_channels); + for (auto& Y2_ch : Y2) { + Y2_ch.fill(0.f); + } + std::vector converged_filters(num_capture_channels, false); + const size_t converged_idx = num_capture_channels - 1; + converged_filters[converged_idx] = true; + ErlEstimator estimator(0); + + // Verifies that the ERL estimate is properly reduced to lower values. + for (auto& X2_ch : X2) { + X2_ch.fill(500 * 1000.f * 1000.f); + } + Y2[converged_idx].fill(10 * X2[0][0]); + for (size_t k = 0; k < 200; ++k) { + estimator.Update(converged_filters, X2, Y2); + } + VerifyErl(estimator.Erl(), estimator.ErlTimeDomain(), 10.f); + + // Verifies that the ERL is not immediately increased when the ERL in the + // data increases. + Y2[converged_idx].fill(10000 * X2[0][0]); + for (size_t k = 0; k < 998; ++k) { + estimator.Update(converged_filters, X2, Y2); + } + VerifyErl(estimator.Erl(), estimator.ErlTimeDomain(), 10.f); + + // Verifies that the rate of increase is 3 dB. + estimator.Update(converged_filters, X2, Y2); + VerifyErl(estimator.Erl(), estimator.ErlTimeDomain(), 20.f); + + // Verifies that the maximum ERL is achieved when there are no low RLE + // estimates. + for (size_t k = 0; k < 1000; ++k) { + estimator.Update(converged_filters, X2, Y2); + } + VerifyErl(estimator.Erl(), estimator.ErlTimeDomain(), 1000.f); + + // Verifies that the ERL estimate is is not updated for low-level signals + for (auto& X2_ch : X2) { + X2_ch.fill(1000.f * 1000.f); + } + Y2[converged_idx].fill(10 * X2[0][0]); + for (size_t k = 0; k < 200; ++k) { + estimator.Update(converged_filters, X2, Y2); + } + VerifyErl(estimator.Erl(), estimator.ErlTimeDomain(), 1000.f); +} } // namespace webrtc diff --git a/modules/audio_processing/aec3/erle_estimator_unittest.cc b/modules/audio_processing/aec3/erle_estimator_unittest.cc index 48a6d6cecd..20df34d312 100644 --- a/modules/audio_processing/aec3/erle_estimator_unittest.cc +++ b/modules/audio_processing/aec3/erle_estimator_unittest.cc @@ -16,12 +16,12 @@ #include "modules/audio_processing/aec3/render_delay_buffer.h" #include "modules/audio_processing/aec3/spectrum_buffer.h" #include "rtc_base/random.h" +#include "rtc_base/strings/string_builder.h" #include "test/gtest.h" namespace webrtc { namespace { - constexpr int kLowFrequencyLimit = kFftLengthBy2 / 2; constexpr float kTrueErle = 10.f; constexpr float kTrueErleOnsets = 1.0f; @@ -129,150 +129,140 @@ void GetFilterFreq( } // namespace -TEST(ErleEstimator, VerifyErleIncreaseAndHold) { +class ErleEstimatorMultiChannel + : public ::testing::Test, + public ::testing::WithParamInterface> {}; + +INSTANTIATE_TEST_SUITE_P(MultiChannel, + ErleEstimatorMultiChannel, + ::testing::Combine(::testing::Values(1, 2, 4, 8), + ::testing::Values(1, 2, 8))); + +TEST_P(ErleEstimatorMultiChannel, VerifyErleIncreaseAndHold) { + const size_t num_render_channels = std::get<0>(GetParam()); + const size_t num_capture_channels = std::get<1>(GetParam()); constexpr int kSampleRateHz = 48000; constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz); - for (size_t num_render_channels : {1, 2, 4, 8}) { - for (size_t num_capture_channels : {1, 2, 4}) { - std::array X2; - std::vector> E2( - num_capture_channels); - std::vector> Y2( - num_capture_channels); - std::vector converged_filters(num_capture_channels, true); + std::array X2; + std::vector> E2(num_capture_channels); + std::vector> Y2(num_capture_channels); + std::vector converged_filters(num_capture_channels, true); - EchoCanceller3Config config; - config.erle.onset_detection = true; + EchoCanceller3Config config; + config.erle.onset_detection = true; - std::vector>> x( - kNumBands, - std::vector>(num_render_channels, - std::vector(kBlockSize, 0.f))); - std::vector>> - filter_frequency_response( - config.filter.main.length_blocks, - std::vector>( - num_capture_channels)); - std::unique_ptr render_delay_buffer( - RenderDelayBuffer::Create(config, kSampleRateHz, - num_render_channels)); + std::vector>> x( + kNumBands, std::vector>( + num_render_channels, std::vector(kBlockSize, 0.f))); + std::vector>> + filter_frequency_response( + config.filter.main.length_blocks, + std::vector>(num_capture_channels)); + std::unique_ptr render_delay_buffer( + RenderDelayBuffer::Create(config, kSampleRateHz, num_render_channels)); - GetFilterFreq(config.delay.delay_headroom_samples, - filter_frequency_response); + GetFilterFreq(config.delay.delay_headroom_samples, filter_frequency_response); - ErleEstimator estimator(0, config, num_capture_channels); + ErleEstimator estimator(0, config, num_capture_channels); - FormFarendTimeFrame(&x); - render_delay_buffer->Insert(x); - render_delay_buffer->PrepareCaptureProcessing(); - // Verifies that the ERLE estimate is properly increased to higher values. - FormFarendFrame(*render_delay_buffer->GetRenderBuffer(), kTrueErle, &X2, - E2, Y2); - for (size_t k = 0; k < 200; ++k) { - render_delay_buffer->Insert(x); - render_delay_buffer->PrepareCaptureProcessing(); - estimator.Update(*render_delay_buffer->GetRenderBuffer(), - filter_frequency_response, X2, Y2, E2, - converged_filters); - } - VerifyErle(estimator.Erle(), std::pow(2.f, estimator.FullbandErleLog2()), - config.erle.max_l, config.erle.max_h); - - FormNearendFrame(&x, &X2, E2, Y2); - // Verifies that the ERLE is not immediately decreased during nearend - // activity. - for (size_t k = 0; k < 50; ++k) { - render_delay_buffer->Insert(x); - render_delay_buffer->PrepareCaptureProcessing(); - estimator.Update(*render_delay_buffer->GetRenderBuffer(), - filter_frequency_response, X2, Y2, E2, - converged_filters); - } - VerifyErle(estimator.Erle(), std::pow(2.f, estimator.FullbandErleLog2()), - config.erle.max_l, config.erle.max_h); - } + FormFarendTimeFrame(&x); + render_delay_buffer->Insert(x); + render_delay_buffer->PrepareCaptureProcessing(); + // Verifies that the ERLE estimate is properly increased to higher values. + FormFarendFrame(*render_delay_buffer->GetRenderBuffer(), kTrueErle, &X2, E2, + Y2); + for (size_t k = 0; k < 200; ++k) { + render_delay_buffer->Insert(x); + render_delay_buffer->PrepareCaptureProcessing(); + estimator.Update(*render_delay_buffer->GetRenderBuffer(), + filter_frequency_response, X2, Y2, E2, converged_filters); } + VerifyErle(estimator.Erle(), std::pow(2.f, estimator.FullbandErleLog2()), + config.erle.max_l, config.erle.max_h); + + FormNearendFrame(&x, &X2, E2, Y2); + // Verifies that the ERLE is not immediately decreased during nearend + // activity. + for (size_t k = 0; k < 50; ++k) { + render_delay_buffer->Insert(x); + render_delay_buffer->PrepareCaptureProcessing(); + estimator.Update(*render_delay_buffer->GetRenderBuffer(), + filter_frequency_response, X2, Y2, E2, converged_filters); + } + VerifyErle(estimator.Erle(), std::pow(2.f, estimator.FullbandErleLog2()), + config.erle.max_l, config.erle.max_h); } -TEST(ErleEstimator, VerifyErleTrackingOnOnsets) { +TEST_P(ErleEstimatorMultiChannel, VerifyErleTrackingOnOnsets) { + const size_t num_render_channels = std::get<0>(GetParam()); + const size_t num_capture_channels = std::get<1>(GetParam()); constexpr int kSampleRateHz = 48000; constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz); - for (size_t num_render_channels : {1, 2, 4, 8}) { - for (size_t num_capture_channels : {1, 2, 4}) { - std::array X2; - std::vector> E2( - num_capture_channels); - std::vector> Y2( - num_capture_channels); - std::vector converged_filters(num_capture_channels, true); - EchoCanceller3Config config; - config.erle.onset_detection = true; - std::vector>> x( - kNumBands, - std::vector>(num_render_channels, - std::vector(kBlockSize, 0.f))); - std::vector>> - filter_frequency_response( - config.filter.main.length_blocks, - std::vector>( - num_capture_channels)); - std::unique_ptr render_delay_buffer( - RenderDelayBuffer::Create(config, kSampleRateHz, - num_render_channels)); + std::array X2; + std::vector> E2(num_capture_channels); + std::vector> Y2(num_capture_channels); + std::vector converged_filters(num_capture_channels, true); + EchoCanceller3Config config; + config.erle.onset_detection = true; + std::vector>> x( + kNumBands, std::vector>( + num_render_channels, std::vector(kBlockSize, 0.f))); + std::vector>> + filter_frequency_response( + config.filter.main.length_blocks, + std::vector>(num_capture_channels)); + std::unique_ptr render_delay_buffer( + RenderDelayBuffer::Create(config, kSampleRateHz, num_render_channels)); - GetFilterFreq(config.delay.delay_headroom_samples, - filter_frequency_response); + GetFilterFreq(config.delay.delay_headroom_samples, filter_frequency_response); - ErleEstimator estimator(/*startup_phase_length_blocks=*/0, config, - num_capture_channels); + ErleEstimator estimator(/*startup_phase_length_blocks=*/0, config, + num_capture_channels); - FormFarendTimeFrame(&x); + FormFarendTimeFrame(&x); + render_delay_buffer->Insert(x); + render_delay_buffer->PrepareCaptureProcessing(); + + for (size_t burst = 0; burst < 20; ++burst) { + FormFarendFrame(*render_delay_buffer->GetRenderBuffer(), kTrueErleOnsets, + &X2, E2, Y2); + for (size_t k = 0; k < 10; ++k) { render_delay_buffer->Insert(x); render_delay_buffer->PrepareCaptureProcessing(); - - for (size_t burst = 0; burst < 20; ++burst) { - FormFarendFrame(*render_delay_buffer->GetRenderBuffer(), - kTrueErleOnsets, &X2, E2, Y2); - for (size_t k = 0; k < 10; ++k) { - render_delay_buffer->Insert(x); - render_delay_buffer->PrepareCaptureProcessing(); - estimator.Update(*render_delay_buffer->GetRenderBuffer(), - filter_frequency_response, X2, Y2, E2, - converged_filters); - } - FormFarendFrame(*render_delay_buffer->GetRenderBuffer(), kTrueErle, &X2, - E2, Y2); - for (size_t k = 0; k < 200; ++k) { - render_delay_buffer->Insert(x); - render_delay_buffer->PrepareCaptureProcessing(); - estimator.Update(*render_delay_buffer->GetRenderBuffer(), - filter_frequency_response, X2, Y2, E2, - converged_filters); - } - FormNearendFrame(&x, &X2, E2, Y2); - for (size_t k = 0; k < 300; ++k) { - render_delay_buffer->Insert(x); - render_delay_buffer->PrepareCaptureProcessing(); - estimator.Update(*render_delay_buffer->GetRenderBuffer(), - filter_frequency_response, X2, Y2, E2, - converged_filters); - } - } - VerifyErleBands(estimator.ErleOnsets(), config.erle.min, config.erle.min); - FormNearendFrame(&x, &X2, E2, Y2); - for (size_t k = 0; k < 1000; k++) { - estimator.Update(*render_delay_buffer->GetRenderBuffer(), - filter_frequency_response, X2, Y2, E2, - converged_filters); - } - // Verifies that during ne activity, Erle converges to the Erle for - // onsets. - VerifyErle(estimator.Erle(), std::pow(2.f, estimator.FullbandErleLog2()), - config.erle.min, config.erle.min); + estimator.Update(*render_delay_buffer->GetRenderBuffer(), + filter_frequency_response, X2, Y2, E2, + converged_filters); + } + FormFarendFrame(*render_delay_buffer->GetRenderBuffer(), kTrueErle, &X2, E2, + Y2); + for (size_t k = 0; k < 200; ++k) { + render_delay_buffer->Insert(x); + render_delay_buffer->PrepareCaptureProcessing(); + estimator.Update(*render_delay_buffer->GetRenderBuffer(), + filter_frequency_response, X2, Y2, E2, + converged_filters); + } + FormNearendFrame(&x, &X2, E2, Y2); + for (size_t k = 0; k < 300; ++k) { + render_delay_buffer->Insert(x); + render_delay_buffer->PrepareCaptureProcessing(); + estimator.Update(*render_delay_buffer->GetRenderBuffer(), + filter_frequency_response, X2, Y2, E2, + converged_filters); } } + VerifyErleBands(estimator.ErleOnsets(), config.erle.min, config.erle.min); + FormNearendFrame(&x, &X2, E2, Y2); + for (size_t k = 0; k < 1000; k++) { + estimator.Update(*render_delay_buffer->GetRenderBuffer(), + filter_frequency_response, X2, Y2, E2, converged_filters); + } + // Verifies that during ne activity, Erle converges to the Erle for + // onsets. + VerifyErle(estimator.Erle(), std::pow(2.f, estimator.FullbandErleLog2()), + config.erle.min, config.erle.min); } } // namespace webrtc diff --git a/modules/audio_processing/aec3/residual_echo_estimator_unittest.cc b/modules/audio_processing/aec3/residual_echo_estimator_unittest.cc index c8a45a40e3..7c00bbdb2b 100644 --- a/modules/audio_processing/aec3/residual_echo_estimator_unittest.cc +++ b/modules/audio_processing/aec3/residual_echo_estimator_unittest.cc @@ -16,87 +16,91 @@ #include "modules/audio_processing/aec3/render_delay_buffer.h" #include "modules/audio_processing/test/echo_canceller_test_tools.h" #include "rtc_base/random.h" +#include "rtc_base/strings/string_builder.h" #include "test/gtest.h" namespace webrtc { -TEST(ResidualEchoEstimator, BasicTest) { - for (size_t num_render_channels : {1, 2, 4}) { - for (size_t num_capture_channels : {1, 2, 4}) { - constexpr int kSampleRateHz = 48000; - constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz); +class ResidualEchoEstimatorMultiChannel + : public ::testing::Test, + public ::testing::WithParamInterface> {}; - EchoCanceller3Config config; - ResidualEchoEstimator estimator(config, num_render_channels); - AecState aec_state(config, num_capture_channels); - std::unique_ptr render_delay_buffer( - RenderDelayBuffer::Create(config, kSampleRateHz, - num_render_channels)); +INSTANTIATE_TEST_SUITE_P(MultiChannel, + ResidualEchoEstimatorMultiChannel, + ::testing::Combine(::testing::Values(1, 2, 4), + ::testing::Values(1, 2, 4))); - std::vector> E2_main( - num_capture_channels); - std::vector> S2_linear( - num_capture_channels); - std::vector> Y2( - num_capture_channels); - std::vector> R2( - num_capture_channels); - std::vector>> x( - kNumBands, - std::vector>(num_render_channels, - std::vector(kBlockSize, 0.f))); - std::vector>> H2( - num_capture_channels, - std::vector>(10)); - Random random_generator(42U); - std::vector output(num_capture_channels); - std::array y; - absl::optional delay_estimate; +TEST_P(ResidualEchoEstimatorMultiChannel, BasicTest) { + const size_t num_render_channels = std::get<0>(GetParam()); + const size_t num_capture_channels = std::get<1>(GetParam()); + constexpr int kSampleRateHz = 48000; + constexpr size_t kNumBands = NumBandsForRate(kSampleRateHz); - for (auto& H2_ch : H2) { - for (auto& H2_k : H2_ch) { - H2_k.fill(0.01f); - } - H2_ch[2].fill(10.f); - H2_ch[2][0] = 0.1f; - } + EchoCanceller3Config config; + ResidualEchoEstimator estimator(config, num_render_channels); + AecState aec_state(config, num_capture_channels); + std::unique_ptr render_delay_buffer( + RenderDelayBuffer::Create(config, kSampleRateHz, num_render_channels)); - std::vector> h( - num_capture_channels, - std::vector( - GetTimeDomainLength(config.filter.main.length_blocks), 0.f)); + std::vector> E2_main( + num_capture_channels); + std::vector> S2_linear( + num_capture_channels); + std::vector> Y2(num_capture_channels); + std::vector> R2(num_capture_channels); + std::vector>> x( + kNumBands, std::vector>( + num_render_channels, std::vector(kBlockSize, 0.f))); + std::vector>> H2( + num_capture_channels, + std::vector>(10)); + Random random_generator(42U); + std::vector output(num_capture_channels); + std::array y; + absl::optional delay_estimate; - for (auto& subtractor_output : output) { - subtractor_output.Reset(); - subtractor_output.s_main.fill(100.f); - } - y.fill(0.f); - - constexpr float kLevel = 10.f; - for (auto& E2_main_ch : E2_main) { - E2_main_ch.fill(kLevel); - } - S2_linear[0].fill(kLevel); - for (auto& Y2_ch : Y2) { - Y2_ch.fill(kLevel); - } - - for (int k = 0; k < 1993; ++k) { - RandomizeSampleVector(&random_generator, x[0][0]); - render_delay_buffer->Insert(x); - if (k == 0) { - render_delay_buffer->Reset(); - } - render_delay_buffer->PrepareCaptureProcessing(); - - aec_state.Update(delay_estimate, H2, h, - *render_delay_buffer->GetRenderBuffer(), E2_main, Y2, - output); - - estimator.Estimate(aec_state, *render_delay_buffer->GetRenderBuffer(), - S2_linear, Y2, R2); - } + for (auto& H2_ch : H2) { + for (auto& H2_k : H2_ch) { + H2_k.fill(0.01f); } + H2_ch[2].fill(10.f); + H2_ch[2][0] = 0.1f; + } + + std::vector> h( + num_capture_channels, + std::vector(GetTimeDomainLength(config.filter.main.length_blocks), + 0.f)); + + for (auto& subtractor_output : output) { + subtractor_output.Reset(); + subtractor_output.s_main.fill(100.f); + } + y.fill(0.f); + + constexpr float kLevel = 10.f; + for (auto& E2_main_ch : E2_main) { + E2_main_ch.fill(kLevel); + } + S2_linear[0].fill(kLevel); + for (auto& Y2_ch : Y2) { + Y2_ch.fill(kLevel); + } + + for (int k = 0; k < 1993; ++k) { + RandomizeSampleVector(&random_generator, x[0][0]); + render_delay_buffer->Insert(x); + if (k == 0) { + render_delay_buffer->Reset(); + } + render_delay_buffer->PrepareCaptureProcessing(); + + aec_state.Update(delay_estimate, H2, h, + *render_delay_buffer->GetRenderBuffer(), E2_main, Y2, + output); + + estimator.Estimate(aec_state, *render_delay_buffer->GetRenderBuffer(), + S2_linear, Y2, R2); } } diff --git a/modules/audio_processing/aec3/shadow_filter_update_gain_unittest.cc b/modules/audio_processing/aec3/shadow_filter_update_gain_unittest.cc index d2d100588e..79bc7acfd4 100644 --- a/modules/audio_processing/aec3/shadow_filter_update_gain_unittest.cc +++ b/modules/audio_processing/aec3/shadow_filter_update_gain_unittest.cc @@ -27,7 +27,6 @@ namespace webrtc { namespace { - // Method for performing the simulations needed to test the main filter update // gain functionality. void RunFilterUpdateTest(int num_blocks_to_process, @@ -153,102 +152,119 @@ TEST(ShadowFilterUpdateGain, NullDataOutputGain) { #endif +class ShadowFilterUpdateGainOneTwoEightRenderChannels + : public ::testing::Test, + public ::testing::WithParamInterface {}; + +INSTANTIATE_TEST_SUITE_P(MultiChannel, + ShadowFilterUpdateGainOneTwoEightRenderChannels, + ::testing::Values(1, 2, 8)); + // Verifies that the gain formed causes the filter using it to converge. -TEST(ShadowFilterUpdateGain, GainCausesFilterToConverge) { +TEST_P(ShadowFilterUpdateGainOneTwoEightRenderChannels, + GainCausesFilterToConverge) { + const size_t num_render_channels = GetParam(); std::vector blocks_with_echo_path_changes; std::vector blocks_with_saturation; - for (size_t num_render_channels : {1, 2, 8}) { - for (size_t filter_length_blocks : {12, 20, 30}) { - for (size_t delay_samples : {0, 64, 150, 200, 301}) { - SCOPED_TRACE(ProduceDebugText(delay_samples, filter_length_blocks)); + for (size_t filter_length_blocks : {12, 20, 30}) { + for (size_t delay_samples : {0, 64, 150, 200, 301}) { + SCOPED_TRACE(ProduceDebugText(delay_samples, filter_length_blocks)); - std::array e; - std::array y; - FftData G; + std::array e; + std::array y; + FftData G; - RunFilterUpdateTest(5000, delay_samples, num_render_channels, - filter_length_blocks, blocks_with_saturation, &e, - &y, &G); + RunFilterUpdateTest(5000, delay_samples, num_render_channels, + filter_length_blocks, blocks_with_saturation, &e, &y, + &G); - // Verify that the main filter is able to perform well. - // Use different criteria to take overmodelling into account. - if (filter_length_blocks == 12) { - EXPECT_LT( - 1000 * std::inner_product(e.begin(), e.end(), e.begin(), 0.f), - std::inner_product(y.begin(), y.end(), y.begin(), 0.f)); - } else { - EXPECT_LT(std::inner_product(e.begin(), e.end(), e.begin(), 0.f), - std::inner_product(y.begin(), y.end(), y.begin(), 0.f)); - } + // Verify that the main filter is able to perform well. + // Use different criteria to take overmodelling into account. + if (filter_length_blocks == 12) { + EXPECT_LT(1000 * std::inner_product(e.begin(), e.end(), e.begin(), 0.f), + std::inner_product(y.begin(), y.end(), y.begin(), 0.f)); + } else { + EXPECT_LT(std::inner_product(e.begin(), e.end(), e.begin(), 0.f), + std::inner_product(y.begin(), y.end(), y.begin(), 0.f)); } } } } -// Verifies that the magnitude of the gain on average decreases for a -// persistently exciting signal. -TEST(ShadowFilterUpdateGain, DecreasingGain) { - for (size_t num_render_channels : {1, 2, 4}) { - for (size_t filter_length_blocks : {12, 20, 30}) { - SCOPED_TRACE(ProduceDebugText(filter_length_blocks)); - std::vector blocks_with_echo_path_changes; - std::vector blocks_with_saturation; - - std::array e; - std::array y; - FftData G_a; - FftData G_b; - FftData G_c; - std::array G_a_power; - std::array G_b_power; - std::array G_c_power; - - RunFilterUpdateTest(100, 65, num_render_channels, filter_length_blocks, - blocks_with_saturation, &e, &y, &G_a); - RunFilterUpdateTest(200, 65, num_render_channels, filter_length_blocks, - blocks_with_saturation, &e, &y, &G_b); - RunFilterUpdateTest(300, 65, num_render_channels, filter_length_blocks, - blocks_with_saturation, &e, &y, &G_c); - - G_a.Spectrum(Aec3Optimization::kNone, G_a_power); - G_b.Spectrum(Aec3Optimization::kNone, G_b_power); - G_c.Spectrum(Aec3Optimization::kNone, G_c_power); - - EXPECT_GT(std::accumulate(G_a_power.begin(), G_a_power.end(), 0.), - std::accumulate(G_b_power.begin(), G_b_power.end(), 0.)); - - EXPECT_GT(std::accumulate(G_b_power.begin(), G_b_power.end(), 0.), - std::accumulate(G_c_power.begin(), G_c_power.end(), 0.)); - } - } -} - // Verifies that the gain is zero when there is saturation. -TEST(ShadowFilterUpdateGain, SaturationBehavior) { +TEST_P(ShadowFilterUpdateGainOneTwoEightRenderChannels, SaturationBehavior) { + const size_t num_render_channels = GetParam(); std::vector blocks_with_echo_path_changes; std::vector blocks_with_saturation; for (int k = 99; k < 200; ++k) { blocks_with_saturation.push_back(k); } - for (size_t num_render_channels : {1, 2, 8}) { - for (size_t filter_length_blocks : {12, 20, 30}) { - SCOPED_TRACE(ProduceDebugText(filter_length_blocks)); + for (size_t filter_length_blocks : {12, 20, 30}) { + SCOPED_TRACE(ProduceDebugText(filter_length_blocks)); - std::array e; - std::array y; - FftData G_a; - FftData G_a_ref; - G_a_ref.re.fill(0.f); - G_a_ref.im.fill(0.f); + std::array e; + std::array y; + FftData G_a; + FftData G_a_ref; + G_a_ref.re.fill(0.f); + G_a_ref.im.fill(0.f); - RunFilterUpdateTest(100, 65, num_render_channels, filter_length_blocks, - blocks_with_saturation, &e, &y, &G_a); + RunFilterUpdateTest(100, 65, num_render_channels, filter_length_blocks, + blocks_with_saturation, &e, &y, &G_a); - EXPECT_EQ(G_a_ref.re, G_a.re); - EXPECT_EQ(G_a_ref.im, G_a.im); - } + EXPECT_EQ(G_a_ref.re, G_a.re); + EXPECT_EQ(G_a_ref.im, G_a.im); } } +class ShadowFilterUpdateGainOneTwoFourRenderChannels + : public ::testing::Test, + public ::testing::WithParamInterface {}; + +INSTANTIATE_TEST_SUITE_P( + MultiChannel, + ShadowFilterUpdateGainOneTwoFourRenderChannels, + ::testing::Values(1, 2, 4), + [](const ::testing::TestParamInfo< + ShadowFilterUpdateGainOneTwoFourRenderChannels::ParamType>& info) { + return (rtc::StringBuilder() << "Render" << info.param).str(); + }); + +// Verifies that the magnitude of the gain on average decreases for a +// persistently exciting signal. +TEST_P(ShadowFilterUpdateGainOneTwoFourRenderChannels, DecreasingGain) { + const size_t num_render_channels = GetParam(); + for (size_t filter_length_blocks : {12, 20, 30}) { + SCOPED_TRACE(ProduceDebugText(filter_length_blocks)); + std::vector blocks_with_echo_path_changes; + std::vector blocks_with_saturation; + + std::array e; + std::array y; + FftData G_a; + FftData G_b; + FftData G_c; + std::array G_a_power; + std::array G_b_power; + std::array G_c_power; + + RunFilterUpdateTest(100, 65, num_render_channels, filter_length_blocks, + blocks_with_saturation, &e, &y, &G_a); + RunFilterUpdateTest(200, 65, num_render_channels, filter_length_blocks, + blocks_with_saturation, &e, &y, &G_b); + RunFilterUpdateTest(300, 65, num_render_channels, filter_length_blocks, + blocks_with_saturation, &e, &y, &G_c); + + G_a.Spectrum(Aec3Optimization::kNone, G_a_power); + G_b.Spectrum(Aec3Optimization::kNone, G_b_power); + G_c.Spectrum(Aec3Optimization::kNone, G_c_power); + + EXPECT_GT(std::accumulate(G_a_power.begin(), G_a_power.end(), 0.), + std::accumulate(G_b_power.begin(), G_b_power.end(), 0.)); + + EXPECT_GT(std::accumulate(G_b_power.begin(), G_b_power.end(), 0.), + std::accumulate(G_c_power.begin(), G_c_power.end(), 0.)); + } +} } // namespace webrtc diff --git a/modules/audio_processing/aec3/signal_dependent_erle_estimator_unittest.cc b/modules/audio_processing/aec3/signal_dependent_erle_estimator_unittest.cc index 641c9158a0..5c69105b4d 100644 --- a/modules/audio_processing/aec3/signal_dependent_erle_estimator_unittest.cc +++ b/modules/audio_processing/aec3/signal_dependent_erle_estimator_unittest.cc @@ -138,36 +138,42 @@ void TestInputs::UpdateCurrentPowerSpectra() { } // namespace -TEST(SignalDependentErleEstimator, SweepSettings) { - for (size_t num_render_channels : {1, 2, 4}) { - for (size_t num_capture_channels : {1, 2, 4}) { - EchoCanceller3Config cfg; - size_t max_length_blocks = 50; - for (size_t blocks = 1; blocks < max_length_blocks; - blocks = blocks + 10) { - for (size_t delay_headroom = 0; delay_headroom < 5; ++delay_headroom) { - for (size_t num_sections = 2; num_sections < max_length_blocks; - ++num_sections) { - cfg.filter.main.length_blocks = blocks; - cfg.filter.main_initial.length_blocks = - std::min(cfg.filter.main_initial.length_blocks, blocks); - cfg.delay.delay_headroom_samples = delay_headroom * kBlockSize; - cfg.erle.num_sections = num_sections; - if (EchoCanceller3Config::Validate(&cfg)) { - SignalDependentErleEstimator s(cfg, num_capture_channels); - std::vector> average_erle( - num_capture_channels); - for (auto& e : average_erle) { - e.fill(cfg.erle.max_l); - } - TestInputs inputs(cfg, num_render_channels, num_capture_channels); - for (size_t n = 0; n < 10; ++n) { - inputs.Update(); - s.Update(inputs.GetRenderBuffer(), inputs.GetH2(), - inputs.GetX2(), inputs.GetY2(), inputs.GetE2(), - average_erle, inputs.GetConvergedFilters()); - } - } +class SignalDependentErleEstimatorMultiChannel + : public ::testing::Test, + public ::testing::WithParamInterface> {}; + +INSTANTIATE_TEST_SUITE_P(MultiChannel, + SignalDependentErleEstimatorMultiChannel, + ::testing::Combine(::testing::Values(1, 2, 4), + ::testing::Values(1, 2, 4))); + +TEST_P(SignalDependentErleEstimatorMultiChannel, SweepSettings) { + const size_t num_render_channels = std::get<0>(GetParam()); + const size_t num_capture_channels = std::get<1>(GetParam()); + EchoCanceller3Config cfg; + size_t max_length_blocks = 50; + for (size_t blocks = 1; blocks < max_length_blocks; blocks = blocks + 10) { + for (size_t delay_headroom = 0; delay_headroom < 5; ++delay_headroom) { + for (size_t num_sections = 2; num_sections < max_length_blocks; + ++num_sections) { + cfg.filter.main.length_blocks = blocks; + cfg.filter.main_initial.length_blocks = + std::min(cfg.filter.main_initial.length_blocks, blocks); + cfg.delay.delay_headroom_samples = delay_headroom * kBlockSize; + cfg.erle.num_sections = num_sections; + if (EchoCanceller3Config::Validate(&cfg)) { + SignalDependentErleEstimator s(cfg, num_capture_channels); + std::vector> average_erle( + num_capture_channels); + for (auto& e : average_erle) { + e.fill(cfg.erle.max_l); + } + TestInputs inputs(cfg, num_render_channels, num_capture_channels); + for (size_t n = 0; n < 10; ++n) { + inputs.Update(); + s.Update(inputs.GetRenderBuffer(), inputs.GetH2(), inputs.GetX2(), + inputs.GetY2(), inputs.GetE2(), average_erle, + inputs.GetConvergedFilters()); } } } @@ -175,30 +181,28 @@ TEST(SignalDependentErleEstimator, SweepSettings) { } } -TEST(SignalDependentErleEstimator, LongerRun) { - for (size_t num_render_channels : {1, 2, 4}) { - for (size_t num_capture_channels : {1, 2, 4}) { - EchoCanceller3Config cfg; - cfg.filter.main.length_blocks = 2; - cfg.filter.main_initial.length_blocks = 1; - cfg.delay.delay_headroom_samples = 0; - cfg.delay.hysteresis_limit_blocks = 0; - cfg.erle.num_sections = 2; - EXPECT_EQ(EchoCanceller3Config::Validate(&cfg), true); - std::vector> average_erle( - num_capture_channels); - for (auto& e : average_erle) { - e.fill(cfg.erle.max_l); - } - SignalDependentErleEstimator s(cfg, num_capture_channels); - TestInputs inputs(cfg, num_render_channels, num_capture_channels); - for (size_t n = 0; n < 200; ++n) { - inputs.Update(); - s.Update(inputs.GetRenderBuffer(), inputs.GetH2(), inputs.GetX2(), - inputs.GetY2(), inputs.GetE2(), average_erle, - inputs.GetConvergedFilters()); - } - } +TEST_P(SignalDependentErleEstimatorMultiChannel, LongerRun) { + const size_t num_render_channels = std::get<0>(GetParam()); + const size_t num_capture_channels = std::get<1>(GetParam()); + EchoCanceller3Config cfg; + cfg.filter.main.length_blocks = 2; + cfg.filter.main_initial.length_blocks = 1; + cfg.delay.delay_headroom_samples = 0; + cfg.delay.hysteresis_limit_blocks = 0; + cfg.erle.num_sections = 2; + EXPECT_EQ(EchoCanceller3Config::Validate(&cfg), true); + std::vector> average_erle( + num_capture_channels); + for (auto& e : average_erle) { + e.fill(cfg.erle.max_l); + } + SignalDependentErleEstimator s(cfg, num_capture_channels); + TestInputs inputs(cfg, num_render_channels, num_capture_channels); + for (size_t n = 0; n < 200; ++n) { + inputs.Update(); + s.Update(inputs.GetRenderBuffer(), inputs.GetH2(), inputs.GetX2(), + inputs.GetY2(), inputs.GetE2(), average_erle, + inputs.GetConvergedFilters()); } } diff --git a/modules/audio_processing/aec3/subtractor_unittest.cc b/modules/audio_processing/aec3/subtractor_unittest.cc index a49b205b95..a1ce41dcb1 100644 --- a/modules/audio_processing/aec3/subtractor_unittest.cc +++ b/modules/audio_processing/aec3/subtractor_unittest.cc @@ -231,33 +231,6 @@ TEST(Subtractor, Convergence) { } } -// Verifies that the subtractor is able to converge on correlated data. -TEST(Subtractor, ConvergenceMultiChannel) { -#if defined(NDEBUG) - const size_t kNumRenderChannelsToTest[] = {1, 2, 8}; - const size_t kNumCaptureChannelsToTest[] = {1, 2, 4}; -#else - const size_t kNumRenderChannelsToTest[] = {1, 2}; - const size_t kNumCaptureChannelsToTest[] = {1, 2}; -#endif - - std::vector blocks_with_echo_path_changes; - for (size_t num_render_channels : kNumRenderChannelsToTest) { - for (size_t num_capture_channels : kNumCaptureChannelsToTest) { - SCOPED_TRACE( - ProduceDebugText(num_render_channels, num_render_channels, 64, 20)); - size_t num_blocks_to_process = 2500 * num_render_channels; - std::vector echo_to_nearend_powers = RunSubtractorTest( - num_render_channels, num_capture_channels, num_blocks_to_process, 64, - 20, 20, false, blocks_with_echo_path_changes); - - for (float echo_to_nearend_power : echo_to_nearend_powers) { - EXPECT_GT(0.1f, echo_to_nearend_power); - } - } - } -} - // Verifies that the subtractor is able to handle the case when the main filter // is longer than the shadow filter. TEST(Subtractor, MainFilterLongerThanShadowFilter) { @@ -297,23 +270,68 @@ TEST(Subtractor, NonConvergenceOnUncorrelatedSignals) { } } -// Verifies that the subtractor does not converge on uncorrelated signals. -TEST(Subtractor, NonConvergenceOnUncorrelatedSignalsMultiChannel) { +class SubtractorMultiChannelUpToEightRender + : public ::testing::Test, + public ::testing::WithParamInterface> {}; + +#if defined(NDEBUG) +INSTANTIATE_TEST_SUITE_P(NonDebugMultiChannel, + SubtractorMultiChannelUpToEightRender, + ::testing::Combine(::testing::Values(1, 2, 8), + ::testing::Values(1, 2, 4))); +#else +INSTANTIATE_TEST_SUITE_P(DebugMultiChannel, + SubtractorMultiChannelUpToEightRender, + ::testing::Combine(::testing::Values(1, 2), + ::testing::Values(1, 2))); +#endif + +// Verifies that the subtractor is able to converge on correlated data. +TEST_P(SubtractorMultiChannelUpToEightRender, Convergence) { + const size_t num_render_channels = std::get<0>(GetParam()); + const size_t num_capture_channels = std::get<1>(GetParam()); + std::vector blocks_with_echo_path_changes; - for (size_t num_render_channels : {1, 2, 4}) { - for (size_t num_capture_channels : {1, 2, 4}) { - SCOPED_TRACE( - ProduceDebugText(num_render_channels, num_render_channels, 64, 20)); - size_t num_blocks_to_process = 5000 * num_render_channels; - std::vector echo_to_nearend_powers = RunSubtractorTest( - num_render_channels, num_capture_channels, num_blocks_to_process, 64, - 20, 20, true, blocks_with_echo_path_changes); - for (float echo_to_nearend_power : echo_to_nearend_powers) { - EXPECT_LT(.8f, echo_to_nearend_power); - EXPECT_NEAR(1.f, echo_to_nearend_power, 0.25f); - } - } + size_t num_blocks_to_process = 2500 * num_render_channels; + std::vector echo_to_nearend_powers = RunSubtractorTest( + num_render_channels, num_capture_channels, num_blocks_to_process, 64, 20, + 20, false, blocks_with_echo_path_changes); + + for (float echo_to_nearend_power : echo_to_nearend_powers) { + EXPECT_GT(0.1f, echo_to_nearend_power); } } +class SubtractorMultiChannelUpToFourRender + : public ::testing::Test, + public ::testing::WithParamInterface> {}; + +#if defined(NDEBUG) +INSTANTIATE_TEST_SUITE_P(NonDebugMultiChannel, + SubtractorMultiChannelUpToFourRender, + ::testing::Combine(::testing::Values(1, 2, 4), + ::testing::Values(1, 2, 4))); +#else +INSTANTIATE_TEST_SUITE_P(DebugMultiChannel, + SubtractorMultiChannelUpToFourRender, + ::testing::Combine(::testing::Values(1, 2), + ::testing::Values(1, 2))); +#endif + +// Verifies that the subtractor does not converge on uncorrelated signals. +TEST_P(SubtractorMultiChannelUpToFourRender, + NonConvergenceOnUncorrelatedSignals) { + const size_t num_render_channels = std::get<0>(GetParam()); + const size_t num_capture_channels = std::get<1>(GetParam()); + + std::vector blocks_with_echo_path_changes; + size_t num_blocks_to_process = 5000 * num_render_channels; + std::vector echo_to_nearend_powers = RunSubtractorTest( + num_render_channels, num_capture_channels, num_blocks_to_process, 64, 20, + 20, true, blocks_with_echo_path_changes); + for (float echo_to_nearend_power : echo_to_nearend_powers) { + EXPECT_LT(.8f, echo_to_nearend_power); + EXPECT_NEAR(1.f, echo_to_nearend_power, 0.25f); + } +} } // namespace webrtc