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