xref: /aosp_15_r20/external/webrtc/modules/audio_processing/agc2/saturation_protector_unittest.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
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