Home | History | Annotate | Download | only in agc
      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 
     59 int ClampLevel(int mic_level) {
     60   return std::min(std::max(kMinMicLevel, mic_level), kMaxMicLevel);
     61 }
     62 
     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:
     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 
    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 
    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 
    157 AgcManagerDirect::~AgcManagerDirect() {}
    158 
    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 
    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 
    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 
    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 
    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 
    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 
    323 float AgcManagerDirect::voice_probability() {
    324   return agc_->voice_probability();
    325 }
    326 
    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.
    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 
    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