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