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