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