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 <algorithm>
14
15 #include "modules/audio_processing/agc2/agc2_common.h"
16 #include "modules/audio_processing/logging/apm_data_dumper.h"
17 #include "rtc_base/gunit.h"
18
19 namespace webrtc {
20 namespace {
RunOnConstantLevel(int num_iterations,VadWithLevel::LevelAndProbability vad_data,float estimated_level_dbfs,SaturationProtector * saturation_protector)21 float RunOnConstantLevel(int num_iterations,
22 VadWithLevel::LevelAndProbability vad_data,
23 float estimated_level_dbfs,
24 SaturationProtector* saturation_protector) {
25 float last_margin = saturation_protector->LastMargin();
26 float max_difference = 0.f;
27 for (int i = 0; i < num_iterations; ++i) {
28 saturation_protector->UpdateMargin(vad_data, estimated_level_dbfs);
29 const float new_margin = saturation_protector->LastMargin();
30 max_difference =
31 std::max(max_difference, std::abs(new_margin - last_margin));
32 last_margin = new_margin;
33 saturation_protector->DebugDumpEstimate();
34 }
35 return max_difference;
36 }
37 } // namespace
38
TEST(AutomaticGainController2SaturationProtector,ProtectorShouldNotCrash)39 TEST(AutomaticGainController2SaturationProtector, ProtectorShouldNotCrash) {
40 ApmDataDumper apm_data_dumper(0);
41 SaturationProtector saturation_protector(&apm_data_dumper);
42 VadWithLevel::LevelAndProbability vad_data(1.f, -20.f, -10.f);
43
44 saturation_protector.UpdateMargin(vad_data, -20.f);
45 static_cast<void>(saturation_protector.LastMargin());
46 saturation_protector.DebugDumpEstimate();
47 }
48
49 // Check that the estimate converges to the ratio between peaks and
50 // level estimator values after a while.
TEST(AutomaticGainController2SaturationProtector,ProtectorEstimatesCrestRatio)51 TEST(AutomaticGainController2SaturationProtector,
52 ProtectorEstimatesCrestRatio) {
53 ApmDataDumper apm_data_dumper(0);
54 SaturationProtector saturation_protector(&apm_data_dumper);
55
56 constexpr float kPeakLevel = -20.f;
57 const float kCrestFactor = GetInitialSaturationMarginDb() + 1.f;
58 const float kSpeechLevel = kPeakLevel - kCrestFactor;
59 const float kMaxDifference =
60 0.5 * std::abs(GetInitialSaturationMarginDb() - kCrestFactor);
61
62 static_cast<void>(RunOnConstantLevel(
63 2000, VadWithLevel::LevelAndProbability(1.f, -90.f, kPeakLevel),
64 kSpeechLevel, &saturation_protector));
65
66 EXPECT_NEAR(
67 saturation_protector.LastMargin() - GetExtraSaturationMarginOffsetDb(),
68 kCrestFactor, kMaxDifference);
69 }
70
TEST(AutomaticGainController2SaturationProtector,ProtectorChangesSlowly)71 TEST(AutomaticGainController2SaturationProtector, ProtectorChangesSlowly) {
72 ApmDataDumper apm_data_dumper(0);
73 SaturationProtector saturation_protector(&apm_data_dumper);
74
75 constexpr float kPeakLevel = -20.f;
76 const float kCrestFactor = GetInitialSaturationMarginDb() - 5.f;
77 const float kOtherCrestFactor = GetInitialSaturationMarginDb();
78 const float kSpeechLevel = kPeakLevel - kCrestFactor;
79 const float kOtherSpeechLevel = kPeakLevel - kOtherCrestFactor;
80
81 constexpr int kNumIterations = 1000;
82 float max_difference = RunOnConstantLevel(
83 kNumIterations, VadWithLevel::LevelAndProbability(1.f, -90.f, kPeakLevel),
84 kSpeechLevel, &saturation_protector);
85
86 max_difference =
87 std::max(RunOnConstantLevel(
88 kNumIterations,
89 VadWithLevel::LevelAndProbability(1.f, -90.f, kPeakLevel),
90 kOtherSpeechLevel, &saturation_protector),
91 max_difference);
92
93 constexpr float kMaxChangeSpeedDbPerSecond = 0.5; // 1 db / 2 seconds.
94
95 EXPECT_LE(max_difference,
96 kMaxChangeSpeedDbPerSecond / 1000 * kFrameDurationMs);
97 }
98
TEST(AutomaticGainController2SaturationProtector,ProtectorAdaptsToDelayedChanges)99 TEST(AutomaticGainController2SaturationProtector,
100 ProtectorAdaptsToDelayedChanges) {
101 ApmDataDumper apm_data_dumper(0);
102 SaturationProtector saturation_protector(&apm_data_dumper);
103
104 constexpr int kDelayIterations = kFullBufferSizeMs / kFrameDurationMs;
105 constexpr float kInitialSpeechLevelDbfs = -30;
106 constexpr float kLaterSpeechLevelDbfs = -15;
107
108 // First run on initial level.
109 float max_difference = RunOnConstantLevel(
110 kDelayIterations,
111 VadWithLevel::LevelAndProbability(
112 1.f, -90.f, kInitialSpeechLevelDbfs + GetInitialSaturationMarginDb()),
113 kInitialSpeechLevelDbfs, &saturation_protector);
114
115 // Then peak changes, but not RMS.
116 max_difference =
117 std::max(RunOnConstantLevel(
118 kDelayIterations,
119 VadWithLevel::LevelAndProbability(
120 1.f, -90.f,
121 kLaterSpeechLevelDbfs + GetInitialSaturationMarginDb()),
122 kInitialSpeechLevelDbfs, &saturation_protector),
123 max_difference);
124
125 // Then both change.
126 max_difference =
127 std::max(RunOnConstantLevel(
128 kDelayIterations,
129 VadWithLevel::LevelAndProbability(
130 1.f, -90.f,
131 kLaterSpeechLevelDbfs + GetInitialSaturationMarginDb()),
132 kLaterSpeechLevelDbfs, &saturation_protector),
133 max_difference);
134
135 // The saturation protector expects that the RMS changes roughly
136 // 'kFullBufferSizeMs' after peaks change. This is to account for
137 // delay introduces by the level estimator. Therefore, the input
138 // above is 'normal' and 'expected', and shouldn't influence the
139 // margin by much.
140
141 const float total_difference = std::abs(saturation_protector.LastMargin() -
142 GetExtraSaturationMarginOffsetDb() -
143 GetInitialSaturationMarginDb());
144
145 EXPECT_LE(total_difference, 0.05f);
146 EXPECT_LE(max_difference, 0.01f);
147 }
148
149 } // namespace webrtc
150