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