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