Line data Source code
1 : /*
2 : * Copyright (c) 2013 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 "webrtc/modules/audio_processing/agc/agc_manager_direct.h"
12 :
13 : #include <cmath>
14 :
15 : #ifdef WEBRTC_AGC_DEBUG_DUMP
16 : #include <cstdio>
17 : #endif
18 :
19 : #include "webrtc/base/checks.h"
20 : #include "webrtc/modules/audio_processing/agc/gain_map_internal.h"
21 : #include "webrtc/modules/audio_processing/gain_control_impl.h"
22 : #include "webrtc/modules/include/module_common_types.h"
23 : #include "webrtc/system_wrappers/include/logging.h"
24 : #include "webrtc/system_wrappers/include/metrics.h"
25 :
26 : namespace webrtc {
27 :
28 : namespace {
29 :
30 : // Amount the microphone level is lowered with every clipping event.
31 : const int kClippedLevelStep = 15;
32 : // Proportion of clipped samples required to declare a clipping event.
33 : const float kClippedRatioThreshold = 0.1f;
34 : // Time in frames to wait after a clipping event before checking again.
35 : const int kClippedWaitFrames = 300;
36 :
37 : // Amount of error we tolerate in the microphone level (presumably due to OS
38 : // quantization) before we assume the user has manually adjusted the microphone.
39 : const int kLevelQuantizationSlack = 25;
40 :
41 : const int kDefaultCompressionGain = 7;
42 : const int kMaxCompressionGain = 12;
43 : const int kMinCompressionGain = 2;
44 : // Controls the rate of compression changes towards the target.
45 : const float kCompressionGainStep = 0.05f;
46 :
47 : const int kMaxMicLevel = 255;
48 : static_assert(kGainMapSize > kMaxMicLevel, "gain map too small");
49 : const int kMinMicLevel = 12;
50 :
51 : // Prevent very large microphone level changes.
52 : const int kMaxResidualGainChange = 15;
53 :
54 : // Maximum additional gain allowed to compensate for microphone level
55 : // restrictions from clipping events.
56 : const int kSurplusCompressionGain = 6;
57 :
58 0 : int ClampLevel(int mic_level) {
59 0 : return std::min(std::max(kMinMicLevel, mic_level), kMaxMicLevel);
60 : }
61 :
62 0 : int LevelFromGainError(int gain_error, int level) {
63 0 : RTC_DCHECK_GE(level, 0);
64 0 : RTC_DCHECK_LE(level, kMaxMicLevel);
65 0 : if (gain_error == 0) {
66 0 : return level;
67 : }
68 : // TODO(ajm): Could be made more efficient with a binary search.
69 0 : int new_level = level;
70 0 : if (gain_error > 0) {
71 0 : while (kGainMap[new_level] - kGainMap[level] < gain_error &&
72 : new_level < kMaxMicLevel) {
73 0 : ++new_level;
74 : }
75 : } else {
76 0 : while (kGainMap[new_level] - kGainMap[level] > gain_error &&
77 : new_level > kMinMicLevel) {
78 0 : --new_level;
79 : }
80 : }
81 0 : return new_level;
82 : }
83 :
84 : } // namespace
85 :
86 : // Facility for dumping debug audio files. All methods are no-ops in the
87 : // default case where WEBRTC_AGC_DEBUG_DUMP is undefined.
88 : class DebugFile {
89 : #ifdef WEBRTC_AGC_DEBUG_DUMP
90 : public:
91 : explicit DebugFile(const char* filename)
92 : : file_(fopen(filename, "wb")) {
93 : RTC_DCHECK(file_);
94 : }
95 : ~DebugFile() {
96 : fclose(file_);
97 : }
98 : void Write(const int16_t* data, size_t length_samples) {
99 : fwrite(data, 1, length_samples * sizeof(int16_t), file_);
100 : }
101 : private:
102 : FILE* file_;
103 : #else
104 : public:
105 0 : explicit DebugFile(const char* filename) {
106 0 : }
107 0 : ~DebugFile() {
108 0 : }
109 0 : void Write(const int16_t* data, size_t length_samples) {
110 0 : }
111 : #endif // WEBRTC_AGC_DEBUG_DUMP
112 : };
113 :
114 0 : AgcManagerDirect::AgcManagerDirect(GainControl* gctrl,
115 : VolumeCallbacks* volume_callbacks,
116 : int startup_min_level,
117 0 : int clipped_level_min)
118 0 : : agc_(new Agc()),
119 : gctrl_(gctrl),
120 : volume_callbacks_(volume_callbacks),
121 : frames_since_clipped_(kClippedWaitFrames),
122 : level_(0),
123 : max_level_(kMaxMicLevel),
124 : max_compression_gain_(kMaxCompressionGain),
125 : target_compression_(kDefaultCompressionGain),
126 0 : compression_(target_compression_),
127 0 : compression_accumulator_(compression_),
128 : capture_muted_(false),
129 : check_volume_on_next_process_(true), // Check at startup.
130 : startup_(true),
131 0 : startup_min_level_(ClampLevel(startup_min_level)),
132 : clipped_level_min_(clipped_level_min),
133 0 : file_preproc_(new DebugFile("agc_preproc.pcm")),
134 0 : file_postproc_(new DebugFile("agc_postproc.pcm")) {}
135 :
136 0 : AgcManagerDirect::AgcManagerDirect(Agc* agc,
137 : GainControl* gctrl,
138 : VolumeCallbacks* volume_callbacks,
139 : int startup_min_level,
140 0 : int clipped_level_min)
141 : : agc_(agc),
142 : gctrl_(gctrl),
143 : volume_callbacks_(volume_callbacks),
144 : frames_since_clipped_(kClippedWaitFrames),
145 : level_(0),
146 : max_level_(kMaxMicLevel),
147 : max_compression_gain_(kMaxCompressionGain),
148 : target_compression_(kDefaultCompressionGain),
149 0 : compression_(target_compression_),
150 0 : compression_accumulator_(compression_),
151 : capture_muted_(false),
152 : check_volume_on_next_process_(true), // Check at startup.
153 : startup_(true),
154 0 : startup_min_level_(ClampLevel(startup_min_level)),
155 : clipped_level_min_(clipped_level_min),
156 0 : file_preproc_(new DebugFile("agc_preproc.pcm")),
157 0 : file_postproc_(new DebugFile("agc_postproc.pcm")) {}
158 :
159 0 : AgcManagerDirect::~AgcManagerDirect() {}
160 :
161 0 : int AgcManagerDirect::Initialize() {
162 0 : max_level_ = kMaxMicLevel;
163 0 : max_compression_gain_ = kMaxCompressionGain;
164 0 : target_compression_ = kDefaultCompressionGain;
165 0 : compression_ = target_compression_;
166 0 : compression_accumulator_ = compression_;
167 0 : capture_muted_ = false;
168 0 : check_volume_on_next_process_ = true;
169 : // TODO(bjornv): Investigate if we need to reset |startup_| as well. For
170 : // example, what happens when we change devices.
171 :
172 0 : if (gctrl_->set_mode(GainControl::kFixedDigital) != 0) {
173 0 : LOG(LS_ERROR) << "set_mode(GainControl::kFixedDigital) failed.";
174 0 : return -1;
175 : }
176 0 : if (gctrl_->set_target_level_dbfs(2) != 0) {
177 0 : LOG(LS_ERROR) << "set_target_level_dbfs(2) failed.";
178 0 : return -1;
179 : }
180 0 : if (gctrl_->set_compression_gain_db(kDefaultCompressionGain) != 0) {
181 0 : LOG(LS_ERROR) << "set_compression_gain_db(kDefaultCompressionGain) failed.";
182 0 : return -1;
183 : }
184 0 : if (gctrl_->enable_limiter(true) != 0) {
185 0 : LOG(LS_ERROR) << "enable_limiter(true) failed.";
186 0 : return -1;
187 : }
188 0 : return 0;
189 : }
190 :
191 0 : void AgcManagerDirect::AnalyzePreProcess(int16_t* audio,
192 : int num_channels,
193 : size_t samples_per_channel) {
194 0 : size_t length = num_channels * samples_per_channel;
195 0 : if (capture_muted_) {
196 0 : return;
197 : }
198 :
199 0 : file_preproc_->Write(audio, length);
200 :
201 0 : if (frames_since_clipped_ < kClippedWaitFrames) {
202 0 : ++frames_since_clipped_;
203 0 : return;
204 : }
205 :
206 : // Check for clipped samples, as the AGC has difficulty detecting pitch
207 : // under clipping distortion. We do this in the preprocessing phase in order
208 : // to catch clipped echo as well.
209 : //
210 : // If we find a sufficiently clipped frame, drop the current microphone level
211 : // and enforce a new maximum level, dropped the same amount from the current
212 : // maximum. This harsh treatment is an effort to avoid repeated clipped echo
213 : // events. As compensation for this restriction, the maximum compression
214 : // gain is increased, through SetMaxLevel().
215 0 : float clipped_ratio = agc_->AnalyzePreproc(audio, length);
216 0 : if (clipped_ratio > kClippedRatioThreshold) {
217 0 : LOG(LS_INFO) << "[agc] Clipping detected. clipped_ratio="
218 0 : << clipped_ratio;
219 : // Always decrease the maximum level, even if the current level is below
220 : // threshold.
221 0 : SetMaxLevel(std::max(clipped_level_min_, max_level_ - kClippedLevelStep));
222 0 : RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.AgcClippingAdjustmentAllowed",
223 : level_ - kClippedLevelStep >= clipped_level_min_);
224 0 : if (level_ > clipped_level_min_) {
225 : // Don't try to adjust the level if we're already below the limit. As
226 : // a consequence, if the user has brought the level above the limit, we
227 : // will still not react until the postproc updates the level.
228 0 : SetLevel(std::max(clipped_level_min_, level_ - kClippedLevelStep));
229 : // Reset the AGC since the level has changed.
230 0 : agc_->Reset();
231 : }
232 0 : frames_since_clipped_ = 0;
233 : }
234 : }
235 :
236 0 : void AgcManagerDirect::Process(const int16_t* audio,
237 : size_t length,
238 : int sample_rate_hz) {
239 0 : if (capture_muted_) {
240 0 : return;
241 : }
242 :
243 0 : if (check_volume_on_next_process_) {
244 0 : check_volume_on_next_process_ = false;
245 : // We have to wait until the first process call to check the volume,
246 : // because Chromium doesn't guarantee it to be valid any earlier.
247 0 : CheckVolumeAndReset();
248 : }
249 :
250 0 : if (agc_->Process(audio, length, sample_rate_hz) != 0) {
251 0 : LOG(LS_ERROR) << "Agc::Process failed";
252 0 : RTC_NOTREACHED();
253 : }
254 :
255 0 : UpdateGain();
256 0 : UpdateCompressor();
257 :
258 0 : file_postproc_->Write(audio, length);
259 : }
260 :
261 0 : void AgcManagerDirect::SetLevel(int new_level) {
262 0 : int voe_level = volume_callbacks_->GetMicVolume();
263 0 : if (voe_level < 0) {
264 0 : return;
265 : }
266 0 : if (voe_level == 0) {
267 0 : LOG(LS_INFO) << "[agc] VolumeCallbacks returned level=0, taking no action.";
268 0 : return;
269 : }
270 0 : if (voe_level > kMaxMicLevel) {
271 0 : LOG(LS_ERROR) << "VolumeCallbacks returned an invalid level=" << voe_level;
272 0 : return;
273 : }
274 :
275 0 : if (voe_level > level_ + kLevelQuantizationSlack ||
276 0 : voe_level < level_ - kLevelQuantizationSlack) {
277 0 : LOG(LS_INFO) << "[agc] Mic volume was manually adjusted. Updating "
278 0 : << "stored level from " << level_ << " to " << voe_level;
279 0 : level_ = voe_level;
280 : // Always allow the user to increase the volume.
281 0 : if (level_ > max_level_) {
282 0 : SetMaxLevel(level_);
283 : }
284 : // Take no action in this case, since we can't be sure when the volume
285 : // was manually adjusted. The compressor will still provide some of the
286 : // desired gain change.
287 0 : agc_->Reset();
288 0 : return;
289 : }
290 :
291 0 : new_level = std::min(new_level, max_level_);
292 0 : if (new_level == level_) {
293 0 : return;
294 : }
295 :
296 0 : volume_callbacks_->SetMicVolume(new_level);
297 0 : LOG(LS_INFO) << "[agc] voe_level=" << voe_level << ", "
298 0 : << "level_=" << level_ << ", "
299 0 : << "new_level=" << new_level;
300 0 : level_ = new_level;
301 : }
302 :
303 0 : void AgcManagerDirect::SetMaxLevel(int level) {
304 0 : RTC_DCHECK_GE(level, clipped_level_min_);
305 0 : max_level_ = level;
306 : // Scale the |kSurplusCompressionGain| linearly across the restricted
307 : // level range.
308 0 : max_compression_gain_ =
309 0 : kMaxCompressionGain + std::floor((1.f * kMaxMicLevel - max_level_) /
310 0 : (kMaxMicLevel - clipped_level_min_) *
311 : kSurplusCompressionGain +
312 0 : 0.5f);
313 0 : LOG(LS_INFO) << "[agc] max_level_=" << max_level_
314 0 : << ", max_compression_gain_=" << max_compression_gain_;
315 0 : }
316 :
317 0 : void AgcManagerDirect::SetCaptureMuted(bool muted) {
318 0 : if (capture_muted_ == muted) {
319 0 : return;
320 : }
321 0 : capture_muted_ = muted;
322 :
323 0 : if (!muted) {
324 : // When we unmute, we should reset things to be safe.
325 0 : check_volume_on_next_process_ = true;
326 : }
327 : }
328 :
329 0 : float AgcManagerDirect::voice_probability() {
330 0 : return agc_->voice_probability();
331 : }
332 :
333 0 : int AgcManagerDirect::CheckVolumeAndReset() {
334 0 : int level = volume_callbacks_->GetMicVolume();
335 0 : if (level < 0) {
336 0 : return -1;
337 : }
338 : // Reasons for taking action at startup:
339 : // 1) A person starting a call is expected to be heard.
340 : // 2) Independent of interpretation of |level| == 0 we should raise it so the
341 : // AGC can do its job properly.
342 0 : if (level == 0 && !startup_) {
343 0 : LOG(LS_INFO) << "[agc] VolumeCallbacks returned level=0, taking no action.";
344 0 : return 0;
345 : }
346 0 : if (level > kMaxMicLevel) {
347 0 : LOG(LS_ERROR) << "VolumeCallbacks returned an invalid level=" << level;
348 0 : return -1;
349 : }
350 0 : LOG(LS_INFO) << "[agc] Initial GetMicVolume()=" << level;
351 :
352 0 : int minLevel = startup_ ? startup_min_level_ : kMinMicLevel;
353 0 : if (level < minLevel) {
354 0 : level = minLevel;
355 0 : LOG(LS_INFO) << "[agc] Initial volume too low, raising to " << level;
356 0 : volume_callbacks_->SetMicVolume(level);
357 : }
358 0 : agc_->Reset();
359 0 : level_ = level;
360 0 : startup_ = false;
361 0 : return 0;
362 : }
363 :
364 : // Requests the RMS error from AGC and distributes the required gain change
365 : // between the digital compression stage and volume slider. We use the
366 : // compressor first, providing a slack region around the current slider
367 : // position to reduce movement.
368 : //
369 : // If the slider needs to be moved, we check first if the user has adjusted
370 : // it, in which case we take no action and cache the updated level.
371 0 : void AgcManagerDirect::UpdateGain() {
372 0 : int rms_error = 0;
373 0 : if (!agc_->GetRmsErrorDb(&rms_error)) {
374 : // No error update ready.
375 0 : return;
376 : }
377 : // The compressor will always add at least kMinCompressionGain. In effect,
378 : // this adjusts our target gain upward by the same amount and rms_error
379 : // needs to reflect that.
380 0 : rms_error += kMinCompressionGain;
381 :
382 : // Handle as much error as possible with the compressor first.
383 : int raw_compression = std::max(std::min(rms_error, max_compression_gain_),
384 0 : kMinCompressionGain);
385 : // Deemphasize the compression gain error. Move halfway between the current
386 : // target and the newly received target. This serves to soften perceptible
387 : // intra-talkspurt adjustments, at the cost of some adaptation speed.
388 0 : if ((raw_compression == max_compression_gain_ &&
389 0 : target_compression_ == max_compression_gain_ - 1) ||
390 0 : (raw_compression == kMinCompressionGain &&
391 0 : target_compression_ == kMinCompressionGain + 1)) {
392 : // Special case to allow the target to reach the endpoints of the
393 : // compression range. The deemphasis would otherwise halt it at 1 dB shy.
394 0 : target_compression_ = raw_compression;
395 : } else {
396 0 : target_compression_ = (raw_compression - target_compression_) / 2
397 0 : + target_compression_;
398 : }
399 :
400 : // Residual error will be handled by adjusting the volume slider. Use the
401 : // raw rather than deemphasized compression here as we would otherwise
402 : // shrink the amount of slack the compressor provides.
403 0 : int residual_gain = rms_error - raw_compression;
404 0 : residual_gain = std::min(std::max(residual_gain, -kMaxResidualGainChange),
405 0 : kMaxResidualGainChange);
406 0 : LOG(LS_INFO) << "[agc] rms_error=" << rms_error << ", "
407 0 : << "target_compression=" << target_compression_ << ", "
408 0 : << "residual_gain=" << residual_gain;
409 0 : if (residual_gain == 0)
410 0 : return;
411 :
412 0 : int old_level = level_;
413 0 : SetLevel(LevelFromGainError(residual_gain, level_));
414 0 : if (old_level != level_) {
415 : // level_ was updated by SetLevel; log the new value.
416 0 : RTC_HISTOGRAM_COUNTS_LINEAR("WebRTC.Audio.AgcSetLevel", level_, 1,
417 : kMaxMicLevel, 50);
418 : }
419 : }
420 :
421 0 : void AgcManagerDirect::UpdateCompressor() {
422 0 : if (compression_ == target_compression_) {
423 0 : return;
424 : }
425 :
426 : // Adapt the compression gain slowly towards the target, in order to avoid
427 : // highly perceptible changes.
428 0 : if (target_compression_ > compression_) {
429 0 : compression_accumulator_ += kCompressionGainStep;
430 : } else {
431 0 : compression_accumulator_ -= kCompressionGainStep;
432 : }
433 :
434 : // The compressor accepts integer gains in dB. Adjust the gain when
435 : // we've come within half a stepsize of the nearest integer. (We don't
436 : // check for equality due to potential floating point imprecision).
437 0 : int new_compression = compression_;
438 0 : int nearest_neighbor = std::floor(compression_accumulator_ + 0.5);
439 0 : if (std::fabs(compression_accumulator_ - nearest_neighbor) <
440 : kCompressionGainStep / 2) {
441 0 : new_compression = nearest_neighbor;
442 : }
443 :
444 : // Set the new compression gain.
445 0 : if (new_compression != compression_) {
446 0 : compression_ = new_compression;
447 0 : compression_accumulator_ = new_compression;
448 0 : if (gctrl_->set_compression_gain_db(compression_) != 0) {
449 0 : LOG(LS_ERROR) << "set_compression_gain_db(" << compression_
450 0 : << ") failed.";
451 : }
452 : }
453 : }
454 :
455 : } // namespace webrtc
|