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