1 /*
2 * Copyright (c) 2018 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/agc2/interpolated_gain_curve.h"
12
13 #include <array>
14 #include <type_traits>
15 #include <vector>
16
17 #include "api/array_view.h"
18 #include "common_audio/include/audio_util.h"
19 #include "modules/audio_processing/agc2/agc2_common.h"
20 #include "modules/audio_processing/agc2/compute_interpolated_gain_curve.h"
21 #include "modules/audio_processing/agc2/limiter_db_gain_curve.h"
22 #include "modules/audio_processing/logging/apm_data_dumper.h"
23 #include "rtc_base/checks.h"
24 #include "rtc_base/gunit.h"
25
26 namespace webrtc {
27 namespace {
28
29 constexpr double kLevelEpsilon = 1e-2 * kMaxAbsFloatS16Value;
30 constexpr float kInterpolatedGainCurveTolerance = 1.f / 32768.f;
31 ApmDataDumper apm_data_dumper(0);
32 static_assert(std::is_trivially_destructible<LimiterDbGainCurve>::value, "");
33 const LimiterDbGainCurve limiter;
34
35 } // namespace
36
TEST(AutomaticGainController2InterpolatedGainCurve,CreateUse)37 TEST(AutomaticGainController2InterpolatedGainCurve, CreateUse) {
38 InterpolatedGainCurve igc(&apm_data_dumper, "");
39
40 const auto levels = test::LinSpace(
41 kLevelEpsilon, DbfsToFloatS16(limiter.max_input_level_db() + 1), 500);
42 for (const auto level : levels) {
43 EXPECT_GE(igc.LookUpGainToApply(level), 0.0f);
44 }
45 }
46
TEST(AutomaticGainController2InterpolatedGainCurve,CheckValidOutput)47 TEST(AutomaticGainController2InterpolatedGainCurve, CheckValidOutput) {
48 InterpolatedGainCurve igc(&apm_data_dumper, "");
49
50 const auto levels = test::LinSpace(
51 kLevelEpsilon, limiter.max_input_level_linear() * 2.0, 500);
52 for (const auto level : levels) {
53 SCOPED_TRACE(std::to_string(level));
54 const float gain = igc.LookUpGainToApply(level);
55 EXPECT_LE(0.0f, gain);
56 EXPECT_LE(gain, 1.0f);
57 }
58 }
59
TEST(AutomaticGainController2InterpolatedGainCurve,CheckMonotonicity)60 TEST(AutomaticGainController2InterpolatedGainCurve, CheckMonotonicity) {
61 InterpolatedGainCurve igc(&apm_data_dumper, "");
62
63 const auto levels = test::LinSpace(
64 kLevelEpsilon, limiter.max_input_level_linear() + kLevelEpsilon + 0.5,
65 500);
66 float prev_gain = igc.LookUpGainToApply(0.0f);
67 for (const auto level : levels) {
68 const float gain = igc.LookUpGainToApply(level);
69 EXPECT_GE(prev_gain, gain);
70 prev_gain = gain;
71 }
72 }
73
TEST(AutomaticGainController2InterpolatedGainCurve,CheckApproximation)74 TEST(AutomaticGainController2InterpolatedGainCurve, CheckApproximation) {
75 InterpolatedGainCurve igc(&apm_data_dumper, "");
76
77 const auto levels = test::LinSpace(
78 kLevelEpsilon, limiter.max_input_level_linear() - kLevelEpsilon, 500);
79 for (const auto level : levels) {
80 SCOPED_TRACE(std::to_string(level));
81 EXPECT_LT(
82 std::fabs(limiter.GetGainLinear(level) - igc.LookUpGainToApply(level)),
83 kInterpolatedGainCurveTolerance);
84 }
85 }
86
TEST(AutomaticGainController2InterpolatedGainCurve,CheckRegionBoundaries)87 TEST(AutomaticGainController2InterpolatedGainCurve, CheckRegionBoundaries) {
88 InterpolatedGainCurve igc(&apm_data_dumper, "");
89
90 const std::vector<double> levels{
91 {kLevelEpsilon, limiter.knee_start_linear() + kLevelEpsilon,
92 limiter.limiter_start_linear() + kLevelEpsilon,
93 limiter.max_input_level_linear() + kLevelEpsilon}};
94 for (const auto level : levels) {
95 igc.LookUpGainToApply(level);
96 }
97
98 const auto stats = igc.get_stats();
99 EXPECT_EQ(1ul, stats.look_ups_identity_region);
100 EXPECT_EQ(1ul, stats.look_ups_knee_region);
101 EXPECT_EQ(1ul, stats.look_ups_limiter_region);
102 EXPECT_EQ(1ul, stats.look_ups_saturation_region);
103 }
104
TEST(AutomaticGainController2InterpolatedGainCurve,CheckIdentityRegion)105 TEST(AutomaticGainController2InterpolatedGainCurve, CheckIdentityRegion) {
106 constexpr size_t kNumSteps = 10;
107 InterpolatedGainCurve igc(&apm_data_dumper, "");
108
109 const auto levels =
110 test::LinSpace(kLevelEpsilon, limiter.knee_start_linear(), kNumSteps);
111 for (const auto level : levels) {
112 SCOPED_TRACE(std::to_string(level));
113 EXPECT_EQ(1.0f, igc.LookUpGainToApply(level));
114 }
115
116 const auto stats = igc.get_stats();
117 EXPECT_EQ(kNumSteps - 1, stats.look_ups_identity_region);
118 EXPECT_EQ(1ul, stats.look_ups_knee_region);
119 EXPECT_EQ(0ul, stats.look_ups_limiter_region);
120 EXPECT_EQ(0ul, stats.look_ups_saturation_region);
121 }
122
TEST(AutomaticGainController2InterpolatedGainCurve,CheckNoOverApproximationKnee)123 TEST(AutomaticGainController2InterpolatedGainCurve,
124 CheckNoOverApproximationKnee) {
125 constexpr size_t kNumSteps = 10;
126 InterpolatedGainCurve igc(&apm_data_dumper, "");
127
128 const auto levels =
129 test::LinSpace(limiter.knee_start_linear() + kLevelEpsilon,
130 limiter.limiter_start_linear(), kNumSteps);
131 for (const auto level : levels) {
132 SCOPED_TRACE(std::to_string(level));
133 // Small tolerance added (needed because comparing a float with a double).
134 EXPECT_LE(igc.LookUpGainToApply(level),
135 limiter.GetGainLinear(level) + 1e-7);
136 }
137
138 const auto stats = igc.get_stats();
139 EXPECT_EQ(0ul, stats.look_ups_identity_region);
140 EXPECT_EQ(kNumSteps - 1, stats.look_ups_knee_region);
141 EXPECT_EQ(1ul, stats.look_ups_limiter_region);
142 EXPECT_EQ(0ul, stats.look_ups_saturation_region);
143 }
144
TEST(AutomaticGainController2InterpolatedGainCurve,CheckNoOverApproximationBeyondKnee)145 TEST(AutomaticGainController2InterpolatedGainCurve,
146 CheckNoOverApproximationBeyondKnee) {
147 constexpr size_t kNumSteps = 10;
148 InterpolatedGainCurve igc(&apm_data_dumper, "");
149
150 const auto levels = test::LinSpace(
151 limiter.limiter_start_linear() + kLevelEpsilon,
152 limiter.max_input_level_linear() - kLevelEpsilon, kNumSteps);
153 for (const auto level : levels) {
154 SCOPED_TRACE(std::to_string(level));
155 // Small tolerance added (needed because comparing a float with a double).
156 EXPECT_LE(igc.LookUpGainToApply(level),
157 limiter.GetGainLinear(level) + 1e-7);
158 }
159
160 const auto stats = igc.get_stats();
161 EXPECT_EQ(0ul, stats.look_ups_identity_region);
162 EXPECT_EQ(0ul, stats.look_ups_knee_region);
163 EXPECT_EQ(kNumSteps, stats.look_ups_limiter_region);
164 EXPECT_EQ(0ul, stats.look_ups_saturation_region);
165 }
166
TEST(AutomaticGainController2InterpolatedGainCurve,CheckNoOverApproximationWithSaturation)167 TEST(AutomaticGainController2InterpolatedGainCurve,
168 CheckNoOverApproximationWithSaturation) {
169 constexpr size_t kNumSteps = 3;
170 InterpolatedGainCurve igc(&apm_data_dumper, "");
171
172 const auto levels = test::LinSpace(
173 limiter.max_input_level_linear() + kLevelEpsilon,
174 limiter.max_input_level_linear() + kLevelEpsilon + 0.5, kNumSteps);
175 for (const auto level : levels) {
176 SCOPED_TRACE(std::to_string(level));
177 EXPECT_LE(igc.LookUpGainToApply(level), limiter.GetGainLinear(level));
178 }
179
180 const auto stats = igc.get_stats();
181 EXPECT_EQ(0ul, stats.look_ups_identity_region);
182 EXPECT_EQ(0ul, stats.look_ups_knee_region);
183 EXPECT_EQ(0ul, stats.look_ups_limiter_region);
184 EXPECT_EQ(kNumSteps, stats.look_ups_saturation_region);
185 }
186
TEST(AutomaticGainController2InterpolatedGainCurve,CheckApproximationParams)187 TEST(AutomaticGainController2InterpolatedGainCurve, CheckApproximationParams) {
188 test::InterpolatedParameters parameters =
189 test::ComputeInterpolatedGainCurveApproximationParams();
190
191 InterpolatedGainCurve igc(&apm_data_dumper, "");
192
193 for (size_t i = 0; i < kInterpolatedGainCurveTotalPoints; ++i) {
194 // The tolerance levels are chosen to account for deviations due
195 // to computing with single precision floating point numbers.
196 EXPECT_NEAR(igc.approximation_params_x_[i],
197 parameters.computed_approximation_params_x[i], 0.9f);
198 EXPECT_NEAR(igc.approximation_params_m_[i],
199 parameters.computed_approximation_params_m[i], 0.00001f);
200 EXPECT_NEAR(igc.approximation_params_q_[i],
201 parameters.computed_approximation_params_q[i], 0.001f);
202 }
203 }
204
205 } // namespace webrtc
206