blob: 944cfb7b08b9b74a83d238cbda717e0cc137710e [file] [log] [blame]
pbos@webrtc.org788acd12014-12-15 09:41:24 +00001/*
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
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020011#include "modules/audio_processing/agc/agc_manager_direct.h"
pbos@webrtc.org788acd12014-12-15 09:41:24 +000012
pbos@webrtc.org788acd12014-12-15 09:41:24 +000013#include <cmath>
14
15#ifdef WEBRTC_AGC_DEBUG_DUMP
16#include <cstdio>
17#endif
18
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020019#include "modules/audio_processing/agc/gain_map_internal.h"
20#include "modules/audio_processing/gain_control_impl.h"
21#include "modules/include/module_common_types.h"
22#include "rtc_base/checks.h"
23#include "rtc_base/logging.h"
24#include "rtc_base/safe_minmax.h"
25#include "system_wrappers/include/metrics.h"
pbos@webrtc.org788acd12014-12-15 09:41:24 +000026
27namespace webrtc {
28
29namespace {
30
pbos@webrtc.org788acd12014-12-15 09:41:24 +000031// Amount the microphone level is lowered with every clipping event.
32const int kClippedLevelStep = 15;
33// Proportion of clipped samples required to declare a clipping event.
34const float kClippedRatioThreshold = 0.1f;
35// Time in frames to wait after a clipping event before checking again.
36const int kClippedWaitFrames = 300;
37
38// Amount of error we tolerate in the microphone level (presumably due to OS
39// quantization) before we assume the user has manually adjusted the microphone.
40const int kLevelQuantizationSlack = 25;
41
42const int kDefaultCompressionGain = 7;
43const int kMaxCompressionGain = 12;
44const int kMinCompressionGain = 2;
45// Controls the rate of compression changes towards the target.
46const float kCompressionGainStep = 0.05f;
47
48const int kMaxMicLevel = 255;
kwiberg@webrtc.org2ebfac52015-01-14 10:51:54 +000049static_assert(kGainMapSize > kMaxMicLevel, "gain map too small");
pbos@webrtc.org788acd12014-12-15 09:41:24 +000050const int kMinMicLevel = 12;
pbos@webrtc.org788acd12014-12-15 09:41:24 +000051
52// Prevent very large microphone level changes.
53const int kMaxResidualGainChange = 15;
54
55// Maximum additional gain allowed to compensate for microphone level
56// restrictions from clipping events.
57const int kSurplusCompressionGain = 6;
58
Bjorn Volckeradc46c42015-04-15 11:42:40 +020059int ClampLevel(int mic_level) {
kwiberg07038562017-06-12 11:40:47 -070060 return rtc::SafeClamp(mic_level, kMinMicLevel, kMaxMicLevel);
Bjorn Volckeradc46c42015-04-15 11:42:40 +020061}
62
pbos@webrtc.org788acd12014-12-15 09:41:24 +000063int LevelFromGainError(int gain_error, int level) {
kwiberg9e2be5f2016-09-14 05:23:22 -070064 RTC_DCHECK_GE(level, 0);
65 RTC_DCHECK_LE(level, kMaxMicLevel);
pbos@webrtc.org788acd12014-12-15 09:41:24 +000066 if (gain_error == 0) {
67 return level;
68 }
69 // TODO(ajm): Could be made more efficient with a binary search.
70 int new_level = level;
71 if (gain_error > 0) {
72 while (kGainMap[new_level] - kGainMap[level] < gain_error &&
73 new_level < kMaxMicLevel) {
74 ++new_level;
75 }
76 } else {
77 while (kGainMap[new_level] - kGainMap[level] > gain_error &&
78 new_level > kMinMicLevel) {
79 --new_level;
80 }
81 }
82 return new_level;
83}
84
85} // namespace
86
87// Facility for dumping debug audio files. All methods are no-ops in the
88// default case where WEBRTC_AGC_DEBUG_DUMP is undefined.
89class DebugFile {
90#ifdef WEBRTC_AGC_DEBUG_DUMP
91 public:
92 explicit DebugFile(const char* filename)
93 : file_(fopen(filename, "wb")) {
kwiberg9e2be5f2016-09-14 05:23:22 -070094 RTC_DCHECK(file_);
pbos@webrtc.org788acd12014-12-15 09:41:24 +000095 }
96 ~DebugFile() {
97 fclose(file_);
98 }
Peter Kastingdce40cf2015-08-24 14:52:23 -070099 void Write(const int16_t* data, size_t length_samples) {
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000100 fwrite(data, 1, length_samples * sizeof(int16_t), file_);
101 }
102 private:
103 FILE* file_;
104#else
105 public:
106 explicit DebugFile(const char* filename) {
107 }
108 ~DebugFile() {
109 }
Peter Kastingdce40cf2015-08-24 14:52:23 -0700110 void Write(const int16_t* data, size_t length_samples) {
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000111 }
112#endif // WEBRTC_AGC_DEBUG_DUMP
113};
114
115AgcManagerDirect::AgcManagerDirect(GainControl* gctrl,
Bjorn Volckeradc46c42015-04-15 11:42:40 +0200116 VolumeCallbacks* volume_callbacks,
henrik.lundinbd681b92016-12-05 09:08:42 -0800117 int startup_min_level,
118 int clipped_level_min)
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000119 : agc_(new Agc()),
120 gctrl_(gctrl),
121 volume_callbacks_(volume_callbacks),
122 frames_since_clipped_(kClippedWaitFrames),
123 level_(0),
124 max_level_(kMaxMicLevel),
125 max_compression_gain_(kMaxCompressionGain),
126 target_compression_(kDefaultCompressionGain),
127 compression_(target_compression_),
128 compression_accumulator_(compression_),
129 capture_muted_(false),
130 check_volume_on_next_process_(true), // Check at startup.
131 startup_(true),
Bjorn Volckeradc46c42015-04-15 11:42:40 +0200132 startup_min_level_(ClampLevel(startup_min_level)),
henrik.lundinbd681b92016-12-05 09:08:42 -0800133 clipped_level_min_(clipped_level_min),
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000134 file_preproc_(new DebugFile("agc_preproc.pcm")),
henrik.lundinbd681b92016-12-05 09:08:42 -0800135 file_postproc_(new DebugFile("agc_postproc.pcm")) {}
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000136
137AgcManagerDirect::AgcManagerDirect(Agc* agc,
138 GainControl* gctrl,
Bjorn Volckeradc46c42015-04-15 11:42:40 +0200139 VolumeCallbacks* volume_callbacks,
henrik.lundinbd681b92016-12-05 09:08:42 -0800140 int startup_min_level,
141 int clipped_level_min)
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000142 : agc_(agc),
143 gctrl_(gctrl),
144 volume_callbacks_(volume_callbacks),
145 frames_since_clipped_(kClippedWaitFrames),
146 level_(0),
147 max_level_(kMaxMicLevel),
148 max_compression_gain_(kMaxCompressionGain),
149 target_compression_(kDefaultCompressionGain),
150 compression_(target_compression_),
151 compression_accumulator_(compression_),
152 capture_muted_(false),
153 check_volume_on_next_process_(true), // Check at startup.
154 startup_(true),
Bjorn Volckeradc46c42015-04-15 11:42:40 +0200155 startup_min_level_(ClampLevel(startup_min_level)),
henrik.lundinbd681b92016-12-05 09:08:42 -0800156 clipped_level_min_(clipped_level_min),
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000157 file_preproc_(new DebugFile("agc_preproc.pcm")),
henrik.lundinbd681b92016-12-05 09:08:42 -0800158 file_postproc_(new DebugFile("agc_postproc.pcm")) {}
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000159
160AgcManagerDirect::~AgcManagerDirect() {}
161
162int AgcManagerDirect::Initialize() {
163 max_level_ = kMaxMicLevel;
164 max_compression_gain_ = kMaxCompressionGain;
165 target_compression_ = kDefaultCompressionGain;
166 compression_ = target_compression_;
167 compression_accumulator_ = compression_;
168 capture_muted_ = false;
169 check_volume_on_next_process_ = true;
170 // TODO(bjornv): Investigate if we need to reset |startup_| as well. For
171 // example, what happens when we change devices.
172
173 if (gctrl_->set_mode(GainControl::kFixedDigital) != 0) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100174 RTC_LOG(LS_ERROR) << "set_mode(GainControl::kFixedDigital) failed.";
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000175 return -1;
176 }
177 if (gctrl_->set_target_level_dbfs(2) != 0) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100178 RTC_LOG(LS_ERROR) << "set_target_level_dbfs(2) failed.";
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000179 return -1;
180 }
181 if (gctrl_->set_compression_gain_db(kDefaultCompressionGain) != 0) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100182 RTC_LOG(LS_ERROR)
183 << "set_compression_gain_db(kDefaultCompressionGain) failed.";
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000184 return -1;
185 }
186 if (gctrl_->enable_limiter(true) != 0) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100187 RTC_LOG(LS_ERROR) << "enable_limiter(true) failed.";
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000188 return -1;
189 }
190 return 0;
191}
192
193void AgcManagerDirect::AnalyzePreProcess(int16_t* audio,
194 int num_channels,
Peter Kastingdce40cf2015-08-24 14:52:23 -0700195 size_t samples_per_channel) {
196 size_t length = num_channels * samples_per_channel;
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000197 if (capture_muted_) {
198 return;
199 }
200
201 file_preproc_->Write(audio, length);
202
203 if (frames_since_clipped_ < kClippedWaitFrames) {
204 ++frames_since_clipped_;
205 return;
206 }
207
208 // Check for clipped samples, as the AGC has difficulty detecting pitch
209 // under clipping distortion. We do this in the preprocessing phase in order
210 // to catch clipped echo as well.
211 //
212 // If we find a sufficiently clipped frame, drop the current microphone level
213 // and enforce a new maximum level, dropped the same amount from the current
214 // maximum. This harsh treatment is an effort to avoid repeated clipped echo
215 // events. As compensation for this restriction, the maximum compression
216 // gain is increased, through SetMaxLevel().
217 float clipped_ratio = agc_->AnalyzePreproc(audio, length);
218 if (clipped_ratio > kClippedRatioThreshold) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100219 RTC_LOG(LS_INFO) << "[agc] Clipping detected. clipped_ratio="
220 << clipped_ratio;
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000221 // Always decrease the maximum level, even if the current level is below
222 // threshold.
henrik.lundinbd681b92016-12-05 09:08:42 -0800223 SetMaxLevel(std::max(clipped_level_min_, max_level_ - kClippedLevelStep));
henrik.lundin30a12fb2016-11-22 07:02:44 -0800224 RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.AgcClippingAdjustmentAllowed",
henrik.lundinbd681b92016-12-05 09:08:42 -0800225 level_ - kClippedLevelStep >= clipped_level_min_);
226 if (level_ > clipped_level_min_) {
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000227 // Don't try to adjust the level if we're already below the limit. As
228 // a consequence, if the user has brought the level above the limit, we
229 // will still not react until the postproc updates the level.
henrik.lundinbd681b92016-12-05 09:08:42 -0800230 SetLevel(std::max(clipped_level_min_, level_ - kClippedLevelStep));
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000231 // Reset the AGC since the level has changed.
232 agc_->Reset();
233 }
234 frames_since_clipped_ = 0;
235 }
236}
237
238void AgcManagerDirect::Process(const int16_t* audio,
Peter Kastingdce40cf2015-08-24 14:52:23 -0700239 size_t length,
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000240 int sample_rate_hz) {
241 if (capture_muted_) {
242 return;
243 }
244
245 if (check_volume_on_next_process_) {
246 check_volume_on_next_process_ = false;
247 // We have to wait until the first process call to check the volume,
248 // because Chromium doesn't guarantee it to be valid any earlier.
249 CheckVolumeAndReset();
250 }
251
252 if (agc_->Process(audio, length, sample_rate_hz) != 0) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100253 RTC_LOG(LS_ERROR) << "Agc::Process failed";
kwiberg9e2be5f2016-09-14 05:23:22 -0700254 RTC_NOTREACHED();
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000255 }
256
257 UpdateGain();
258 UpdateCompressor();
259
260 file_postproc_->Write(audio, length);
261}
262
263void AgcManagerDirect::SetLevel(int new_level) {
264 int voe_level = volume_callbacks_->GetMicVolume();
265 if (voe_level < 0) {
266 return;
267 }
268 if (voe_level == 0) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100269 RTC_LOG(LS_INFO)
270 << "[agc] VolumeCallbacks returned level=0, taking no action.";
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000271 return;
272 }
273 if (voe_level > kMaxMicLevel) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100274 RTC_LOG(LS_ERROR) << "VolumeCallbacks returned an invalid level="
275 << voe_level;
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000276 return;
277 }
278
279 if (voe_level > level_ + kLevelQuantizationSlack ||
280 voe_level < level_ - kLevelQuantizationSlack) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100281 RTC_LOG(LS_INFO) << "[agc] Mic volume was manually adjusted. Updating "
282 << "stored level from " << level_ << " to " << voe_level;
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000283 level_ = voe_level;
284 // Always allow the user to increase the volume.
285 if (level_ > max_level_) {
286 SetMaxLevel(level_);
287 }
288 // Take no action in this case, since we can't be sure when the volume
289 // was manually adjusted. The compressor will still provide some of the
290 // desired gain change.
291 agc_->Reset();
292 return;
293 }
294
295 new_level = std::min(new_level, max_level_);
296 if (new_level == level_) {
297 return;
298 }
299
300 volume_callbacks_->SetMicVolume(new_level);
Mirko Bonadei675513b2017-11-09 11:09:25 +0100301 RTC_LOG(LS_INFO) << "[agc] voe_level=" << voe_level << ", "
302 << "level_=" << level_ << ", "
303 << "new_level=" << new_level;
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000304 level_ = new_level;
305}
306
307void AgcManagerDirect::SetMaxLevel(int level) {
henrik.lundinbd681b92016-12-05 09:08:42 -0800308 RTC_DCHECK_GE(level, clipped_level_min_);
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000309 max_level_ = level;
310 // Scale the |kSurplusCompressionGain| linearly across the restricted
311 // level range.
henrik.lundinbd681b92016-12-05 09:08:42 -0800312 max_compression_gain_ =
313 kMaxCompressionGain + std::floor((1.f * kMaxMicLevel - max_level_) /
314 (kMaxMicLevel - clipped_level_min_) *
315 kSurplusCompressionGain +
316 0.5f);
Mirko Bonadei675513b2017-11-09 11:09:25 +0100317 RTC_LOG(LS_INFO) << "[agc] max_level_=" << max_level_
318 << ", max_compression_gain_=" << max_compression_gain_;
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000319}
320
321void AgcManagerDirect::SetCaptureMuted(bool muted) {
322 if (capture_muted_ == muted) {
323 return;
324 }
325 capture_muted_ = muted;
326
327 if (!muted) {
328 // When we unmute, we should reset things to be safe.
329 check_volume_on_next_process_ = true;
330 }
331}
332
333float AgcManagerDirect::voice_probability() {
aluebsecf6b812015-06-25 12:28:48 -0700334 return agc_->voice_probability();
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000335}
336
337int AgcManagerDirect::CheckVolumeAndReset() {
338 int level = volume_callbacks_->GetMicVolume();
339 if (level < 0) {
340 return -1;
341 }
342 // Reasons for taking action at startup:
343 // 1) A person starting a call is expected to be heard.
344 // 2) Independent of interpretation of |level| == 0 we should raise it so the
345 // AGC can do its job properly.
346 if (level == 0 && !startup_) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100347 RTC_LOG(LS_INFO)
348 << "[agc] VolumeCallbacks returned level=0, taking no action.";
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000349 return 0;
350 }
351 if (level > kMaxMicLevel) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100352 RTC_LOG(LS_ERROR) << "VolumeCallbacks returned an invalid level=" << level;
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000353 return -1;
354 }
Mirko Bonadei675513b2017-11-09 11:09:25 +0100355 RTC_LOG(LS_INFO) << "[agc] Initial GetMicVolume()=" << level;
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000356
Bjorn Volckeradc46c42015-04-15 11:42:40 +0200357 int minLevel = startup_ ? startup_min_level_ : kMinMicLevel;
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000358 if (level < minLevel) {
359 level = minLevel;
Mirko Bonadei675513b2017-11-09 11:09:25 +0100360 RTC_LOG(LS_INFO) << "[agc] Initial volume too low, raising to " << level;
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000361 volume_callbacks_->SetMicVolume(level);
362 }
363 agc_->Reset();
364 level_ = level;
365 startup_ = false;
366 return 0;
367}
368
369// Requests the RMS error from AGC and distributes the required gain change
370// between the digital compression stage and volume slider. We use the
371// compressor first, providing a slack region around the current slider
372// position to reduce movement.
373//
374// If the slider needs to be moved, we check first if the user has adjusted
375// it, in which case we take no action and cache the updated level.
376void AgcManagerDirect::UpdateGain() {
377 int rms_error = 0;
378 if (!agc_->GetRmsErrorDb(&rms_error)) {
379 // No error update ready.
380 return;
381 }
382 // The compressor will always add at least kMinCompressionGain. In effect,
383 // this adjusts our target gain upward by the same amount and rms_error
384 // needs to reflect that.
385 rms_error += kMinCompressionGain;
386
387 // Handle as much error as possible with the compressor first.
kwiberg07038562017-06-12 11:40:47 -0700388 int raw_compression =
389 rtc::SafeClamp(rms_error, kMinCompressionGain, max_compression_gain_);
390
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000391 // Deemphasize the compression gain error. Move halfway between the current
392 // target and the newly received target. This serves to soften perceptible
393 // intra-talkspurt adjustments, at the cost of some adaptation speed.
394 if ((raw_compression == max_compression_gain_ &&
395 target_compression_ == max_compression_gain_ - 1) ||
396 (raw_compression == kMinCompressionGain &&
397 target_compression_ == kMinCompressionGain + 1)) {
398 // Special case to allow the target to reach the endpoints of the
399 // compression range. The deemphasis would otherwise halt it at 1 dB shy.
400 target_compression_ = raw_compression;
401 } else {
402 target_compression_ = (raw_compression - target_compression_) / 2
403 + target_compression_;
404 }
405
406 // Residual error will be handled by adjusting the volume slider. Use the
407 // raw rather than deemphasized compression here as we would otherwise
408 // shrink the amount of slack the compressor provides.
kwiberg07038562017-06-12 11:40:47 -0700409 const int residual_gain =
410 rtc::SafeClamp(rms_error - raw_compression, -kMaxResidualGainChange,
411 kMaxResidualGainChange);
Mirko Bonadei675513b2017-11-09 11:09:25 +0100412 RTC_LOG(LS_INFO) << "[agc] rms_error=" << rms_error << ", "
413 << "target_compression=" << target_compression_ << ", "
414 << "residual_gain=" << residual_gain;
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000415 if (residual_gain == 0)
416 return;
417
henrik.lundin3edc7f02016-11-24 01:42:46 -0800418 int old_level = level_;
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000419 SetLevel(LevelFromGainError(residual_gain, level_));
henrik.lundin3edc7f02016-11-24 01:42:46 -0800420 if (old_level != level_) {
421 // level_ was updated by SetLevel; log the new value.
henrik.lundin45bb5132016-12-06 04:28:04 -0800422 RTC_HISTOGRAM_COUNTS_LINEAR("WebRTC.Audio.AgcSetLevel", level_, 1,
423 kMaxMicLevel, 50);
henrik.lundin3edc7f02016-11-24 01:42:46 -0800424 }
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000425}
426
427void AgcManagerDirect::UpdateCompressor() {
428 if (compression_ == target_compression_) {
429 return;
430 }
431
432 // Adapt the compression gain slowly towards the target, in order to avoid
433 // highly perceptible changes.
434 if (target_compression_ > compression_) {
435 compression_accumulator_ += kCompressionGainStep;
436 } else {
437 compression_accumulator_ -= kCompressionGainStep;
438 }
439
440 // The compressor accepts integer gains in dB. Adjust the gain when
441 // we've come within half a stepsize of the nearest integer. (We don't
442 // check for equality due to potential floating point imprecision).
443 int new_compression = compression_;
444 int nearest_neighbor = std::floor(compression_accumulator_ + 0.5);
445 if (std::fabs(compression_accumulator_ - nearest_neighbor) <
446 kCompressionGainStep / 2) {
447 new_compression = nearest_neighbor;
448 }
449
450 // Set the new compression gain.
451 if (new_compression != compression_) {
452 compression_ = new_compression;
453 compression_accumulator_ = new_compression;
454 if (gctrl_->set_compression_gain_db(compression_) != 0) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100455 RTC_LOG(LS_ERROR) << "set_compression_gain_db(" << compression_
456 << ") failed.";
pbos@webrtc.org788acd12014-12-15 09:41:24 +0000457 }
458 }
459}
460
461} // namespace webrtc