1 /*
2 * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11 #include "modules/audio_processing/aec3/shadow_filter_update_gain.h"
12
13 #include <algorithm>
14 #include <numeric>
15 #include <string>
16 #include <vector>
17
18 #include "modules/audio_processing/aec3/adaptive_fir_filter.h"
19 #include "modules/audio_processing/aec3/aec3_common.h"
20 #include "modules/audio_processing/aec3/aec_state.h"
21 #include "modules/audio_processing/test/echo_canceller_test_tools.h"
22 #include "rtc_base/numerics/safe_minmax.h"
23 #include "rtc_base/random.h"
24 #include "test/gtest.h"
25
26 namespace webrtc {
27 namespace {
28
29 // Method for performing the simulations needed to test the main filter update
30 // gain functionality.
RunFilterUpdateTest(int num_blocks_to_process,size_t delay_samples,const std::vector<int> & blocks_with_saturation,std::array<float,kBlockSize> * e_last_block,std::array<float,kBlockSize> * y_last_block,FftData * G_last_block)31 void RunFilterUpdateTest(int num_blocks_to_process,
32 size_t delay_samples,
33 const std::vector<int>& blocks_with_saturation,
34 std::array<float, kBlockSize>* e_last_block,
35 std::array<float, kBlockSize>* y_last_block,
36 FftData* G_last_block) {
37 ApmDataDumper data_dumper(42);
38 AdaptiveFirFilter main_filter(9, DetectOptimization(), &data_dumper);
39 AdaptiveFirFilter shadow_filter(9, DetectOptimization(), &data_dumper);
40 Aec3Fft fft;
41 RenderBuffer render_buffer(
42 Aec3Optimization::kNone, 3, main_filter.SizePartitions(),
43 std::vector<size_t>(1, main_filter.SizePartitions()));
44 std::array<float, kBlockSize> x_old;
45 x_old.fill(0.f);
46 ShadowFilterUpdateGain shadow_gain;
47 Random random_generator(42U);
48 std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
49 std::vector<float> y(kBlockSize, 0.f);
50 AecState aec_state(EchoCanceller3Config{});
51 RenderSignalAnalyzer render_signal_analyzer;
52 std::array<float, kFftLength> s;
53 FftData S;
54 FftData G;
55 FftData E_shadow;
56 std::array<float, kBlockSize> e_shadow;
57
58 constexpr float kScale = 1.0f / kFftLengthBy2;
59
60 DelayBuffer<float> delay_buffer(delay_samples);
61 for (int k = 0; k < num_blocks_to_process; ++k) {
62 // Handle saturation.
63 bool saturation =
64 std::find(blocks_with_saturation.begin(), blocks_with_saturation.end(),
65 k) != blocks_with_saturation.end();
66
67 // Create the render signal.
68 RandomizeSampleVector(&random_generator, x[0]);
69 delay_buffer.Delay(x[0], y);
70 render_buffer.Insert(x);
71 render_signal_analyzer.Update(render_buffer, delay_samples / kBlockSize);
72
73 shadow_filter.Filter(render_buffer, &S);
74 fft.Ifft(S, &s);
75 std::transform(y.begin(), y.end(), s.begin() + kFftLengthBy2,
76 e_shadow.begin(),
77 [&](float a, float b) { return a - b * kScale; });
78 std::for_each(e_shadow.begin(), e_shadow.end(),
79 [](float& a) { a = rtc::SafeClamp(a, -32768.f, 32767.f); });
80 fft.ZeroPaddedFft(e_shadow, &E_shadow);
81
82 shadow_gain.Compute(render_buffer, render_signal_analyzer, E_shadow,
83 shadow_filter.SizePartitions(), saturation, &G);
84 shadow_filter.Adapt(render_buffer, G);
85 }
86
87 std::copy(e_shadow.begin(), e_shadow.end(), e_last_block->begin());
88 std::copy(y.begin(), y.end(), y_last_block->begin());
89 std::copy(G.re.begin(), G.re.end(), G_last_block->re.begin());
90 std::copy(G.im.begin(), G.im.end(), G_last_block->im.begin());
91 }
92
ProduceDebugText(size_t delay)93 std::string ProduceDebugText(size_t delay) {
94 std::ostringstream ss;
95 ss << ", Delay: " << delay;
96 return ss.str();
97 }
98
99 } // namespace
100
101 #if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
102
103 // Verifies that the check for non-null output gain parameter works.
TEST(ShadowFilterUpdateGain,NullDataOutputGain)104 TEST(ShadowFilterUpdateGain, NullDataOutputGain) {
105 ApmDataDumper data_dumper(42);
106 RenderBuffer render_buffer(Aec3Optimization::kNone, 3, 1,
107 std::vector<size_t>(1, 1));
108 RenderSignalAnalyzer analyzer;
109 FftData E;
110 ShadowFilterUpdateGain gain;
111 EXPECT_DEATH(gain.Compute(render_buffer, analyzer, E, 1, false, nullptr), "");
112 }
113
114 #endif
115
116 // Verifies that the gain formed causes the filter using it to converge.
TEST(ShadowFilterUpdateGain,GainCausesFilterToConverge)117 TEST(ShadowFilterUpdateGain, GainCausesFilterToConverge) {
118 std::vector<int> blocks_with_echo_path_changes;
119 std::vector<int> blocks_with_saturation;
120 for (size_t delay_samples : {0, 64, 150, 200, 301}) {
121 SCOPED_TRACE(ProduceDebugText(delay_samples));
122
123 std::array<float, kBlockSize> e;
124 std::array<float, kBlockSize> y;
125 FftData G;
126
127 RunFilterUpdateTest(500, delay_samples, blocks_with_saturation, &e, &y, &G);
128
129 // Verify that the main filter is able to perform well.
130 EXPECT_LT(1000 * std::inner_product(e.begin(), e.end(), e.begin(), 0.f),
131 std::inner_product(y.begin(), y.end(), y.begin(), 0.f));
132 }
133 }
134
135 // Verifies that the magnitude of the gain on average decreases for a
136 // persistently exciting signal.
TEST(ShadowFilterUpdateGain,DecreasingGain)137 TEST(ShadowFilterUpdateGain, DecreasingGain) {
138 std::vector<int> blocks_with_echo_path_changes;
139 std::vector<int> blocks_with_saturation;
140
141 std::array<float, kBlockSize> e;
142 std::array<float, kBlockSize> y;
143 FftData G_a;
144 FftData G_b;
145 FftData G_c;
146 std::array<float, kFftLengthBy2Plus1> G_a_power;
147 std::array<float, kFftLengthBy2Plus1> G_b_power;
148 std::array<float, kFftLengthBy2Plus1> G_c_power;
149
150 RunFilterUpdateTest(100, 65, blocks_with_saturation, &e, &y, &G_a);
151 RunFilterUpdateTest(200, 65, blocks_with_saturation, &e, &y, &G_b);
152 RunFilterUpdateTest(300, 65, blocks_with_saturation, &e, &y, &G_c);
153
154 G_a.Spectrum(Aec3Optimization::kNone, &G_a_power);
155 G_b.Spectrum(Aec3Optimization::kNone, &G_b_power);
156 G_c.Spectrum(Aec3Optimization::kNone, &G_c_power);
157
158 EXPECT_GT(std::accumulate(G_a_power.begin(), G_a_power.end(), 0.),
159 std::accumulate(G_b_power.begin(), G_b_power.end(), 0.));
160
161 EXPECT_GT(std::accumulate(G_b_power.begin(), G_b_power.end(), 0.),
162 std::accumulate(G_c_power.begin(), G_c_power.end(), 0.));
163 }
164
165 // Verifies that the gain is zero when there is saturation.
TEST(ShadowFilterUpdateGain,SaturationBehavior)166 TEST(ShadowFilterUpdateGain, SaturationBehavior) {
167 std::vector<int> blocks_with_echo_path_changes;
168 std::vector<int> blocks_with_saturation;
169 for (int k = 99; k < 200; ++k) {
170 blocks_with_saturation.push_back(k);
171 }
172
173 std::array<float, kBlockSize> e;
174 std::array<float, kBlockSize> y;
175 FftData G_a;
176 FftData G_a_ref;
177 G_a_ref.re.fill(0.f);
178 G_a_ref.im.fill(0.f);
179
180 RunFilterUpdateTest(100, 65, blocks_with_saturation, &e, &y, &G_a);
181
182 EXPECT_EQ(G_a_ref.re, G_a.re);
183 EXPECT_EQ(G_a_ref.im, G_a.im);
184 }
185
186 } // namespace webrtc
187