• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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