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/saturation_protector.h"
12
13 #include "modules/audio_processing/agc2/agc2_common.h"
14 #include "modules/audio_processing/logging/apm_data_dumper.h"
15 #include "rtc_base/gunit.h"
16
17 namespace webrtc {
18 namespace {
19
20 constexpr float kInitialHeadroomDb = 20.0f;
21 constexpr int kNoAdjacentSpeechFramesRequired = 1;
22 constexpr float kMaxSpeechProbability = 1.0f;
23
24 // Calls `Analyze(speech_probability, peak_dbfs, speech_level_dbfs)`
25 // `num_iterations` times on `saturation_protector` and return the largest
26 // headroom difference between two consecutive calls.
RunOnConstantLevel(int num_iterations,float speech_probability,float peak_dbfs,float speech_level_dbfs,SaturationProtector & saturation_protector)27 float RunOnConstantLevel(int num_iterations,
28 float speech_probability,
29 float peak_dbfs,
30 float speech_level_dbfs,
31 SaturationProtector& saturation_protector) {
32 float last_headroom = saturation_protector.HeadroomDb();
33 float max_difference = 0.0f;
34 for (int i = 0; i < num_iterations; ++i) {
35 saturation_protector.Analyze(speech_probability, peak_dbfs,
36 speech_level_dbfs);
37 const float new_headroom = saturation_protector.HeadroomDb();
38 max_difference =
39 std::max(max_difference, std::fabs(new_headroom - last_headroom));
40 last_headroom = new_headroom;
41 }
42 return max_difference;
43 }
44
45 // Checks that the returned headroom value is correctly reset.
TEST(GainController2SaturationProtector,Reset)46 TEST(GainController2SaturationProtector, Reset) {
47 ApmDataDumper apm_data_dumper(0);
48 auto saturation_protector = CreateSaturationProtector(
49 kInitialHeadroomDb, kNoAdjacentSpeechFramesRequired, &apm_data_dumper);
50 const float initial_headroom_db = saturation_protector->HeadroomDb();
51 RunOnConstantLevel(/*num_iterations=*/10, kMaxSpeechProbability,
52 /*peak_dbfs=*/0.0f,
53 /*speech_level_dbfs=*/-10.0f, *saturation_protector);
54 // Make sure that there are side-effects.
55 ASSERT_NE(initial_headroom_db, saturation_protector->HeadroomDb());
56 saturation_protector->Reset();
57 EXPECT_EQ(initial_headroom_db, saturation_protector->HeadroomDb());
58 }
59
60 // Checks that the estimate converges to the ratio between peaks and level
61 // estimator values after a while.
TEST(GainController2SaturationProtector,EstimatesCrestRatio)62 TEST(GainController2SaturationProtector, EstimatesCrestRatio) {
63 constexpr int kNumIterations = 2000;
64 constexpr float kPeakLevelDbfs = -20.0f;
65 constexpr float kCrestFactorDb = kInitialHeadroomDb + 1.0f;
66 constexpr float kSpeechLevelDbfs = kPeakLevelDbfs - kCrestFactorDb;
67 const float kMaxDifferenceDb =
68 0.5f * std::fabs(kInitialHeadroomDb - kCrestFactorDb);
69
70 ApmDataDumper apm_data_dumper(0);
71 auto saturation_protector = CreateSaturationProtector(
72 kInitialHeadroomDb, kNoAdjacentSpeechFramesRequired, &apm_data_dumper);
73 RunOnConstantLevel(kNumIterations, kMaxSpeechProbability, kPeakLevelDbfs,
74 kSpeechLevelDbfs, *saturation_protector);
75 EXPECT_NEAR(saturation_protector->HeadroomDb(), kCrestFactorDb,
76 kMaxDifferenceDb);
77 }
78
79 // Checks that the headroom does not change too quickly.
TEST(GainController2SaturationProtector,ChangeSlowly)80 TEST(GainController2SaturationProtector, ChangeSlowly) {
81 constexpr int kNumIterations = 1000;
82 constexpr float kPeakLevelDbfs = -20.f;
83 constexpr float kCrestFactorDb = kInitialHeadroomDb - 5.f;
84 constexpr float kOtherCrestFactorDb = kInitialHeadroomDb;
85 constexpr float kSpeechLevelDbfs = kPeakLevelDbfs - kCrestFactorDb;
86 constexpr float kOtherSpeechLevelDbfs = kPeakLevelDbfs - kOtherCrestFactorDb;
87
88 ApmDataDumper apm_data_dumper(0);
89 auto saturation_protector = CreateSaturationProtector(
90 kInitialHeadroomDb, kNoAdjacentSpeechFramesRequired, &apm_data_dumper);
91 float max_difference_db =
92 RunOnConstantLevel(kNumIterations, kMaxSpeechProbability, kPeakLevelDbfs,
93 kSpeechLevelDbfs, *saturation_protector);
94 max_difference_db = std::max(
95 RunOnConstantLevel(kNumIterations, kMaxSpeechProbability, kPeakLevelDbfs,
96 kOtherSpeechLevelDbfs, *saturation_protector),
97 max_difference_db);
98 constexpr float kMaxChangeSpeedDbPerSecond = 0.5f; // 1 db / 2 seconds.
99 EXPECT_LE(max_difference_db,
100 kMaxChangeSpeedDbPerSecond / 1000 * kFrameDurationMs);
101 }
102
103 class SaturationProtectorParametrization
104 : public ::testing::TestWithParam<int> {
105 protected:
adjacent_speech_frames_threshold() const106 int adjacent_speech_frames_threshold() const { return GetParam(); }
107 };
108
TEST_P(SaturationProtectorParametrization,DoNotAdaptToShortSpeechSegments)109 TEST_P(SaturationProtectorParametrization, DoNotAdaptToShortSpeechSegments) {
110 ApmDataDumper apm_data_dumper(0);
111 auto saturation_protector = CreateSaturationProtector(
112 kInitialHeadroomDb, adjacent_speech_frames_threshold(), &apm_data_dumper);
113 const float initial_headroom_db = saturation_protector->HeadroomDb();
114 RunOnConstantLevel(/*num_iterations=*/adjacent_speech_frames_threshold() - 1,
115 kMaxSpeechProbability,
116 /*peak_dbfs=*/0.0f,
117 /*speech_level_dbfs=*/-10.0f, *saturation_protector);
118 // No adaptation expected.
119 EXPECT_EQ(initial_headroom_db, saturation_protector->HeadroomDb());
120 }
121
TEST_P(SaturationProtectorParametrization,AdaptToEnoughSpeechSegments)122 TEST_P(SaturationProtectorParametrization, AdaptToEnoughSpeechSegments) {
123 ApmDataDumper apm_data_dumper(0);
124 auto saturation_protector = CreateSaturationProtector(
125 kInitialHeadroomDb, adjacent_speech_frames_threshold(), &apm_data_dumper);
126 const float initial_headroom_db = saturation_protector->HeadroomDb();
127 RunOnConstantLevel(/*num_iterations=*/adjacent_speech_frames_threshold() + 1,
128 kMaxSpeechProbability,
129 /*peak_dbfs=*/0.0f,
130 /*speech_level_dbfs=*/-10.0f, *saturation_protector);
131 // Adaptation expected.
132 EXPECT_NE(initial_headroom_db, saturation_protector->HeadroomDb());
133 }
134
135 INSTANTIATE_TEST_SUITE_P(GainController2,
136 SaturationProtectorParametrization,
137 ::testing::Values(2, 9, 17));
138
139 } // namespace
140 } // namespace webrtc
141