1 /*
2 * Copyright (c) 2017 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/gain_controller2.h"
12
13 #include <memory>
14 #include <utility>
15
16 #include "common_audio/include/audio_util.h"
17 #include "modules/audio_processing/agc2/cpu_features.h"
18 #include "modules/audio_processing/audio_buffer.h"
19 #include "modules/audio_processing/include/audio_frame_view.h"
20 #include "modules/audio_processing/logging/apm_data_dumper.h"
21 #include "rtc_base/checks.h"
22 #include "rtc_base/logging.h"
23 #include "rtc_base/strings/string_builder.h"
24 #include "system_wrappers/include/field_trial.h"
25
26 namespace webrtc {
27 namespace {
28
29 using Agc2Config = AudioProcessing::Config::GainController2;
30 using InputVolumeControllerConfig = InputVolumeController::Config;
31
32 constexpr int kLogLimiterStatsPeriodMs = 30'000;
33 constexpr int kFrameLengthMs = 10;
34 constexpr int kLogLimiterStatsPeriodNumFrames =
35 kLogLimiterStatsPeriodMs / kFrameLengthMs;
36
37 // Detects the available CPU features and applies any kill-switches.
GetAllowedCpuFeatures()38 AvailableCpuFeatures GetAllowedCpuFeatures() {
39 AvailableCpuFeatures features = GetAvailableCpuFeatures();
40 if (field_trial::IsEnabled("WebRTC-Agc2SimdSse2KillSwitch")) {
41 features.sse2 = false;
42 }
43 if (field_trial::IsEnabled("WebRTC-Agc2SimdAvx2KillSwitch")) {
44 features.avx2 = false;
45 }
46 if (field_trial::IsEnabled("WebRTC-Agc2SimdNeonKillSwitch")) {
47 features.neon = false;
48 }
49 return features;
50 }
51
52 // Creates an adaptive digital gain controller if enabled.
CreateAdaptiveDigitalController(const Agc2Config::AdaptiveDigital & config,int sample_rate_hz,int num_channels,ApmDataDumper * data_dumper)53 std::unique_ptr<AdaptiveDigitalGainController> CreateAdaptiveDigitalController(
54 const Agc2Config::AdaptiveDigital& config,
55 int sample_rate_hz,
56 int num_channels,
57 ApmDataDumper* data_dumper) {
58 if (config.enabled) {
59 return std::make_unique<AdaptiveDigitalGainController>(
60 data_dumper, config, sample_rate_hz, num_channels);
61 }
62 return nullptr;
63 }
64
65 // Creates an input volume controller if `enabled` is true.
CreateInputVolumeController(bool enabled,const InputVolumeControllerConfig & config,int num_channels)66 std::unique_ptr<InputVolumeController> CreateInputVolumeController(
67 bool enabled,
68 const InputVolumeControllerConfig& config,
69 int num_channels) {
70 if (enabled) {
71 return std::make_unique<InputVolumeController>(num_channels, config);
72 }
73 return nullptr;
74 }
75
76 } // namespace
77
78 std::atomic<int> GainController2::instance_count_(0);
79
GainController2(const Agc2Config & config,const InputVolumeControllerConfig & input_volume_controller_config,int sample_rate_hz,int num_channels,bool use_internal_vad)80 GainController2::GainController2(
81 const Agc2Config& config,
82 const InputVolumeControllerConfig& input_volume_controller_config,
83 int sample_rate_hz,
84 int num_channels,
85 bool use_internal_vad)
86 : cpu_features_(GetAllowedCpuFeatures()),
87 data_dumper_(instance_count_.fetch_add(1) + 1),
88 fixed_gain_applier_(
89 /*hard_clip_samples=*/false,
90 /*initial_gain_factor=*/DbToRatio(config.fixed_digital.gain_db)),
91 adaptive_digital_controller_(
92 CreateAdaptiveDigitalController(config.adaptive_digital,
93 sample_rate_hz,
94 num_channels,
95 &data_dumper_)),
96 input_volume_controller_(
97 CreateInputVolumeController(config.input_volume_controller.enabled,
98 input_volume_controller_config,
99 num_channels)),
100 limiter_(sample_rate_hz, &data_dumper_, /*histogram_name_prefix=*/"Agc2"),
101 calls_since_last_limiter_log_(0) {
102 RTC_DCHECK(Validate(config));
103 data_dumper_.InitiateNewSetOfRecordings();
104 const bool use_vad = config.adaptive_digital.enabled;
105 if (use_vad && use_internal_vad) {
106 // TODO(bugs.webrtc.org/7494): Move `vad_reset_period_ms` from adaptive
107 // digital to gain controller 2 config.
108 vad_ = std::make_unique<VoiceActivityDetectorWrapper>(
109 config.adaptive_digital.vad_reset_period_ms, cpu_features_,
110 sample_rate_hz);
111 }
112 if (input_volume_controller_) {
113 input_volume_controller_->Initialize();
114 }
115 }
116
117 GainController2::~GainController2() = default;
118
119 // TODO(webrtc:7494): Pass the flag also to the other components.
SetCaptureOutputUsed(bool capture_output_used)120 void GainController2::SetCaptureOutputUsed(bool capture_output_used) {
121 if (input_volume_controller_) {
122 input_volume_controller_->HandleCaptureOutputUsedChange(
123 capture_output_used);
124 }
125 }
126
SetFixedGainDb(float gain_db)127 void GainController2::SetFixedGainDb(float gain_db) {
128 const float gain_factor = DbToRatio(gain_db);
129 if (fixed_gain_applier_.GetGainFactor() != gain_factor) {
130 // Reset the limiter to quickly react on abrupt level changes caused by
131 // large changes of the fixed gain.
132 limiter_.Reset();
133 }
134 fixed_gain_applier_.SetGainFactor(gain_factor);
135 }
136
Analyze(int applied_input_volume,const AudioBuffer & audio_buffer)137 void GainController2::Analyze(int applied_input_volume,
138 const AudioBuffer& audio_buffer) {
139 RTC_DCHECK_GE(applied_input_volume, 0);
140 RTC_DCHECK_LE(applied_input_volume, 255);
141
142 if (input_volume_controller_) {
143 input_volume_controller_->set_stream_analog_level(applied_input_volume);
144 input_volume_controller_->AnalyzePreProcess(audio_buffer);
145 }
146 }
147
GetRecommendedInputVolume() const148 absl::optional<int> GainController2::GetRecommendedInputVolume() const {
149 return input_volume_controller_
150 ? absl::optional<int>(
151 input_volume_controller_->recommended_analog_level())
152 : absl::nullopt;
153 }
154
Process(absl::optional<float> speech_probability,bool input_volume_changed,AudioBuffer * audio)155 void GainController2::Process(absl::optional<float> speech_probability,
156 bool input_volume_changed,
157 AudioBuffer* audio) {
158 data_dumper_.DumpRaw("agc2_applied_input_volume_changed",
159 input_volume_changed);
160 if (input_volume_changed && !!adaptive_digital_controller_) {
161 adaptive_digital_controller_->HandleInputGainChange();
162 }
163
164 AudioFrameView<float> float_frame(audio->channels(), audio->num_channels(),
165 audio->num_frames());
166 if (vad_) {
167 speech_probability = vad_->Analyze(float_frame);
168 } else if (speech_probability.has_value()) {
169 RTC_DCHECK_GE(speech_probability.value(), 0.0f);
170 RTC_DCHECK_LE(speech_probability.value(), 1.0f);
171 }
172 if (speech_probability.has_value()) {
173 data_dumper_.DumpRaw("agc2_speech_probability", speech_probability.value());
174 }
175
176 if (input_volume_controller_) {
177 // TODO(bugs.webrtc.org/7494): A temprorary check, remove once not needed.
178 RTC_DCHECK(adaptive_digital_controller_);
179 absl::optional<float> speech_level;
180 if (adaptive_digital_controller_) {
181 speech_level =
182 adaptive_digital_controller_->GetSpeechLevelDbfsIfConfident();
183 }
184 RTC_DCHECK(speech_probability.has_value());
185 if (speech_probability.has_value()) {
186 input_volume_controller_->Process(*speech_probability, speech_level);
187 }
188 }
189
190 if (adaptive_digital_controller_) {
191 RTC_DCHECK(speech_probability.has_value());
192 adaptive_digital_controller_->Process(
193 float_frame, speech_probability.value(), limiter_.LastAudioLevel());
194 }
195
196 fixed_gain_applier_.ApplyGain(float_frame);
197
198 limiter_.Process(float_frame);
199
200 // Periodically log limiter stats.
201 if (++calls_since_last_limiter_log_ == kLogLimiterStatsPeriodNumFrames) {
202 calls_since_last_limiter_log_ = 0;
203 InterpolatedGainCurve::Stats stats = limiter_.GetGainCurveStats();
204 RTC_LOG(LS_INFO) << "AGC2 limiter stats"
205 << " | identity: " << stats.look_ups_identity_region
206 << " | knee: " << stats.look_ups_knee_region
207 << " | limiter: " << stats.look_ups_limiter_region
208 << " | saturation: " << stats.look_ups_saturation_region;
209 }
210 }
211
Validate(const AudioProcessing::Config::GainController2 & config)212 bool GainController2::Validate(
213 const AudioProcessing::Config::GainController2& config) {
214 const auto& fixed = config.fixed_digital;
215 const auto& adaptive = config.adaptive_digital;
216 return fixed.gain_db >= 0.0f && fixed.gain_db < 50.f &&
217 adaptive.headroom_db >= 0.0f && adaptive.max_gain_db > 0.0f &&
218 adaptive.initial_gain_db >= 0.0f &&
219 adaptive.max_gain_change_db_per_second > 0.0f &&
220 adaptive.max_output_noise_level_dbfs <= 0.0f;
221 }
222
223 } // namespace webrtc
224