LCOV - code coverage report
Current view: top level - media/webrtc/trunk/webrtc/modules/audio_processing/agc - agc_manager_direct.cc (source / functions) Hit Total Coverage
Test: output.info Lines: 0 193 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 18 0.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13