xref: /aosp_15_r20/external/webrtc/modules/audio_processing/agc2/interpolated_gain_curve.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/interpolated_gain_curve.h"
12 
13 #include <algorithm>
14 #include <iterator>
15 
16 #include "absl/strings/string_view.h"
17 #include "modules/audio_processing/agc2/agc2_common.h"
18 #include "modules/audio_processing/logging/apm_data_dumper.h"
19 #include "rtc_base/checks.h"
20 #include "rtc_base/strings/string_builder.h"
21 
22 namespace webrtc {
23 
24 constexpr std::array<float, kInterpolatedGainCurveTotalPoints>
25     InterpolatedGainCurve::approximation_params_x_;
26 
27 constexpr std::array<float, kInterpolatedGainCurveTotalPoints>
28     InterpolatedGainCurve::approximation_params_m_;
29 
30 constexpr std::array<float, kInterpolatedGainCurveTotalPoints>
31     InterpolatedGainCurve::approximation_params_q_;
32 
InterpolatedGainCurve(ApmDataDumper * apm_data_dumper,absl::string_view histogram_name_prefix)33 InterpolatedGainCurve::InterpolatedGainCurve(
34     ApmDataDumper* apm_data_dumper,
35     absl::string_view histogram_name_prefix)
36     : region_logger_(
37           (rtc::StringBuilder("WebRTC.Audio.")
38            << histogram_name_prefix << ".FixedDigitalGainCurveRegion.Identity")
39               .str(),
40           (rtc::StringBuilder("WebRTC.Audio.")
41            << histogram_name_prefix << ".FixedDigitalGainCurveRegion.Knee")
42               .str(),
43           (rtc::StringBuilder("WebRTC.Audio.")
44            << histogram_name_prefix << ".FixedDigitalGainCurveRegion.Limiter")
45               .str(),
46           (rtc::StringBuilder("WebRTC.Audio.")
47            << histogram_name_prefix
48            << ".FixedDigitalGainCurveRegion.Saturation")
49               .str()),
50       apm_data_dumper_(apm_data_dumper) {}
51 
~InterpolatedGainCurve()52 InterpolatedGainCurve::~InterpolatedGainCurve() {
53   if (stats_.available) {
54     RTC_DCHECK(apm_data_dumper_);
55     apm_data_dumper_->DumpRaw("agc2_interp_gain_curve_lookups_identity",
56                               stats_.look_ups_identity_region);
57     apm_data_dumper_->DumpRaw("agc2_interp_gain_curve_lookups_knee",
58                               stats_.look_ups_knee_region);
59     apm_data_dumper_->DumpRaw("agc2_interp_gain_curve_lookups_limiter",
60                               stats_.look_ups_limiter_region);
61     apm_data_dumper_->DumpRaw("agc2_interp_gain_curve_lookups_saturation",
62                               stats_.look_ups_saturation_region);
63     region_logger_.LogRegionStats(stats_);
64   }
65 }
66 
RegionLogger(absl::string_view identity_histogram_name,absl::string_view knee_histogram_name,absl::string_view limiter_histogram_name,absl::string_view saturation_histogram_name)67 InterpolatedGainCurve::RegionLogger::RegionLogger(
68     absl::string_view identity_histogram_name,
69     absl::string_view knee_histogram_name,
70     absl::string_view limiter_histogram_name,
71     absl::string_view saturation_histogram_name)
72     : identity_histogram(
73           metrics::HistogramFactoryGetCounts(identity_histogram_name,
74                                              1,
75                                              10000,
76                                              50)),
77       knee_histogram(metrics::HistogramFactoryGetCounts(knee_histogram_name,
78                                                         1,
79                                                         10000,
80                                                         50)),
81       limiter_histogram(
82           metrics::HistogramFactoryGetCounts(limiter_histogram_name,
83                                              1,
84                                              10000,
85                                              50)),
86       saturation_histogram(
87           metrics::HistogramFactoryGetCounts(saturation_histogram_name,
88                                              1,
89                                              10000,
90                                              50)) {}
91 
92 InterpolatedGainCurve::RegionLogger::~RegionLogger() = default;
93 
LogRegionStats(const InterpolatedGainCurve::Stats & stats) const94 void InterpolatedGainCurve::RegionLogger::LogRegionStats(
95     const InterpolatedGainCurve::Stats& stats) const {
96   using Region = InterpolatedGainCurve::GainCurveRegion;
97   const int duration_s =
98       stats.region_duration_frames / (1000 / kFrameDurationMs);
99 
100   switch (stats.region) {
101     case Region::kIdentity: {
102       if (identity_histogram) {
103         metrics::HistogramAdd(identity_histogram, duration_s);
104       }
105       break;
106     }
107     case Region::kKnee: {
108       if (knee_histogram) {
109         metrics::HistogramAdd(knee_histogram, duration_s);
110       }
111       break;
112     }
113     case Region::kLimiter: {
114       if (limiter_histogram) {
115         metrics::HistogramAdd(limiter_histogram, duration_s);
116       }
117       break;
118     }
119     case Region::kSaturation: {
120       if (saturation_histogram) {
121         metrics::HistogramAdd(saturation_histogram, duration_s);
122       }
123       break;
124     }
125     default: {
126       RTC_DCHECK_NOTREACHED();
127     }
128   }
129 }
130 
UpdateStats(float input_level) const131 void InterpolatedGainCurve::UpdateStats(float input_level) const {
132   stats_.available = true;
133 
134   GainCurveRegion region;
135 
136   if (input_level < approximation_params_x_[0]) {
137     stats_.look_ups_identity_region++;
138     region = GainCurveRegion::kIdentity;
139   } else if (input_level <
140              approximation_params_x_[kInterpolatedGainCurveKneePoints - 1]) {
141     stats_.look_ups_knee_region++;
142     region = GainCurveRegion::kKnee;
143   } else if (input_level < kMaxInputLevelLinear) {
144     stats_.look_ups_limiter_region++;
145     region = GainCurveRegion::kLimiter;
146   } else {
147     stats_.look_ups_saturation_region++;
148     region = GainCurveRegion::kSaturation;
149   }
150 
151   if (region == stats_.region) {
152     ++stats_.region_duration_frames;
153   } else {
154     region_logger_.LogRegionStats(stats_);
155 
156     stats_.region_duration_frames = 0;
157     stats_.region = region;
158   }
159 }
160 
161 // Looks up a gain to apply given a non-negative input level.
162 // The cost of this operation depends on the region in which `input_level`
163 // falls.
164 // For the identity and the saturation regions the cost is O(1).
165 // For the other regions, namely knee and limiter, the cost is
166 // O(2 + log2(`LightkInterpolatedGainCurveTotalPoints`), plus O(1) for the
167 // linear interpolation (one product and one sum).
LookUpGainToApply(float input_level) const168 float InterpolatedGainCurve::LookUpGainToApply(float input_level) const {
169   UpdateStats(input_level);
170 
171   if (input_level <= approximation_params_x_[0]) {
172     // Identity region.
173     return 1.0f;
174   }
175 
176   if (input_level >= kMaxInputLevelLinear) {
177     // Saturating lower bound. The saturing samples exactly hit the clipping
178     // level. This method achieves has the lowest harmonic distorsion, but it
179     // may reduce the amplitude of the non-saturating samples too much.
180     return 32768.f / input_level;
181   }
182 
183   // Knee and limiter regions; find the linear piece index. Spelling
184   // out the complete type was the only way to silence both the clang
185   // plugin and the windows compilers.
186   std::array<float, kInterpolatedGainCurveTotalPoints>::const_iterator it =
187       std::lower_bound(approximation_params_x_.begin(),
188                        approximation_params_x_.end(), input_level);
189   const size_t index = std::distance(approximation_params_x_.begin(), it) - 1;
190   RTC_DCHECK_LE(0, index);
191   RTC_DCHECK_LT(index, approximation_params_m_.size());
192   RTC_DCHECK_LE(approximation_params_x_[index], input_level);
193   if (index < approximation_params_m_.size() - 1) {
194     RTC_DCHECK_LE(input_level, approximation_params_x_[index + 1]);
195   }
196 
197   // Piece-wise linear interploation.
198   const float gain = approximation_params_m_[index] * input_level +
199                      approximation_params_q_[index];
200   RTC_DCHECK_LE(0.f, gain);
201   return gain;
202 }
203 
204 }  // namespace webrtc
205