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
25namespace webrtc {
26
27namespace {
28
29// Lowest the microphone level can be lowered due to clipping.
30const int kClippedLevelMin = 170;
31// 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;
49static_assert(kGainMapSize > kMaxMicLevel, "gain map too small");
50const int kMinMicLevel = 12;
51
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
59int ClampLevel(int mic_level) {
60  return std::min(std::max(kMinMicLevel, mic_level), kMaxMicLevel);
61}
62
63int 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.
88class DebugFile {
89#ifdef WEBRTC_AGC_DEBUG_DUMP
90 public:
91  explicit DebugFile(const char* filename)
92      : file_(fopen(filename, "wb")) {
93    assert(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  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
114AgcManagerDirect::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
135AgcManagerDirect::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
157AgcManagerDirect::~AgcManagerDirect() {}
158
159int 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
189void 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
232void 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
257void 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
299void 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
311void 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
323float AgcManagerDirect::voice_probability() {
324  return agc_->voice_probability();
325}
326
327int 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.
365void 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
409void 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