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(GainController2InterpolatedGainCurve,CreateUse)37 TEST(GainController2InterpolatedGainCurve, 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(GainController2InterpolatedGainCurve,CheckValidOutput)47 TEST(GainController2InterpolatedGainCurve, 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(GainController2InterpolatedGainCurve,CheckMonotonicity)60 TEST(GainController2InterpolatedGainCurve, 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(GainController2InterpolatedGainCurve,CheckApproximation)74 TEST(GainController2InterpolatedGainCurve, 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(GainController2InterpolatedGainCurve,CheckRegionBoundaries)87 TEST(GainController2InterpolatedGainCurve, 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(GainController2InterpolatedGainCurve,CheckIdentityRegion)105 TEST(GainController2InterpolatedGainCurve, 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(GainController2InterpolatedGainCurve,CheckNoOverApproximationKnee)123 TEST(GainController2InterpolatedGainCurve, CheckNoOverApproximationKnee) {
124 constexpr size_t kNumSteps = 10;
125 InterpolatedGainCurve igc(&apm_data_dumper, "");
126
127 const auto levels =
128 test::LinSpace(limiter.knee_start_linear() + kLevelEpsilon,
129 limiter.limiter_start_linear(), kNumSteps);
130 for (const auto level : levels) {
131 SCOPED_TRACE(std::to_string(level));
132 // Small tolerance added (needed because comparing a float with a double).
133 EXPECT_LE(igc.LookUpGainToApply(level),
134 limiter.GetGainLinear(level) + 1e-7);
135 }
136
137 const auto stats = igc.get_stats();
138 EXPECT_EQ(0ul, stats.look_ups_identity_region);
139 EXPECT_EQ(kNumSteps - 1, stats.look_ups_knee_region);
140 EXPECT_EQ(1ul, stats.look_ups_limiter_region);
141 EXPECT_EQ(0ul, stats.look_ups_saturation_region);
142 }
143
TEST(GainController2InterpolatedGainCurve,CheckNoOverApproximationBeyondKnee)144 TEST(GainController2InterpolatedGainCurve, CheckNoOverApproximationBeyondKnee) {
145 constexpr size_t kNumSteps = 10;
146 InterpolatedGainCurve igc(&apm_data_dumper, "");
147
148 const auto levels = test::LinSpace(
149 limiter.limiter_start_linear() + kLevelEpsilon,
150 limiter.max_input_level_linear() - kLevelEpsilon, kNumSteps);
151 for (const auto level : levels) {
152 SCOPED_TRACE(std::to_string(level));
153 // Small tolerance added (needed because comparing a float with a double).
154 EXPECT_LE(igc.LookUpGainToApply(level),
155 limiter.GetGainLinear(level) + 1e-7);
156 }
157
158 const auto stats = igc.get_stats();
159 EXPECT_EQ(0ul, stats.look_ups_identity_region);
160 EXPECT_EQ(0ul, stats.look_ups_knee_region);
161 EXPECT_EQ(kNumSteps, stats.look_ups_limiter_region);
162 EXPECT_EQ(0ul, stats.look_ups_saturation_region);
163 }
164
TEST(GainController2InterpolatedGainCurve,CheckNoOverApproximationWithSaturation)165 TEST(GainController2InterpolatedGainCurve,
166 CheckNoOverApproximationWithSaturation) {
167 constexpr size_t kNumSteps = 3;
168 InterpolatedGainCurve igc(&apm_data_dumper, "");
169
170 const auto levels = test::LinSpace(
171 limiter.max_input_level_linear() + kLevelEpsilon,
172 limiter.max_input_level_linear() + kLevelEpsilon + 0.5, kNumSteps);
173 for (const auto level : levels) {
174 SCOPED_TRACE(std::to_string(level));
175 EXPECT_LE(igc.LookUpGainToApply(level), limiter.GetGainLinear(level));
176 }
177
178 const auto stats = igc.get_stats();
179 EXPECT_EQ(0ul, stats.look_ups_identity_region);
180 EXPECT_EQ(0ul, stats.look_ups_knee_region);
181 EXPECT_EQ(0ul, stats.look_ups_limiter_region);
182 EXPECT_EQ(kNumSteps, stats.look_ups_saturation_region);
183 }
184
TEST(GainController2InterpolatedGainCurve,CheckApproximationParams)185 TEST(GainController2InterpolatedGainCurve, CheckApproximationParams) {
186 test::InterpolatedParameters parameters =
187 test::ComputeInterpolatedGainCurveApproximationParams();
188
189 InterpolatedGainCurve igc(&apm_data_dumper, "");
190
191 for (size_t i = 0; i < kInterpolatedGainCurveTotalPoints; ++i) {
192 // The tolerance levels are chosen to account for deviations due
193 // to computing with single precision floating point numbers.
194 EXPECT_NEAR(igc.approximation_params_x_[i],
195 parameters.computed_approximation_params_x[i], 0.9f);
196 EXPECT_NEAR(igc.approximation_params_m_[i],
197 parameters.computed_approximation_params_m[i], 0.00001f);
198 EXPECT_NEAR(igc.approximation_params_q_[i],
199 parameters.computed_approximation_params_q[i], 0.001f);
200 }
201 }
202
203 } // namespace webrtc
204