tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

input_volume_controller.cc (21662B)


      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 "modules/audio_processing/agc2/input_volume_controller.h"
     12 
     13 #include <algorithm>
     14 #include <cmath>
     15 #include <cstddef>
     16 #include <memory>
     17 #include <optional>
     18 
     19 #include "api/audio/audio_processing.h"
     20 #include "modules/audio_processing/agc2/clipping_predictor.h"
     21 #include "modules/audio_processing/agc2/gain_map_internal.h"
     22 #include "modules/audio_processing/agc2/input_volume_stats_reporter.h"
     23 #include "modules/audio_processing/audio_buffer.h"
     24 #include "modules/audio_processing/include/audio_frame_view.h"
     25 #include "rtc_base/checks.h"
     26 #include "rtc_base/logging.h"
     27 #include "rtc_base/numerics/safe_minmax.h"
     28 #include "system_wrappers/include/metrics.h"
     29 
     30 namespace webrtc {
     31 
     32 namespace {
     33 
     34 // Amount of error we tolerate in the microphone input volume (presumably due to
     35 // OS quantization) before we assume the user has manually adjusted the volume.
     36 constexpr int kVolumeQuantizationSlack = 25;
     37 
     38 constexpr int kMaxInputVolume = 255;
     39 static_assert(kGainMapSize > kMaxInputVolume, "gain map too small");
     40 
     41 // Maximum absolute RMS error.
     42 constexpr int KMaxAbsRmsErrorDbfs = 15;
     43 static_assert(KMaxAbsRmsErrorDbfs > 0, "");
     44 
     45 using Agc1ClippingPredictorConfig = AudioProcessing::Config::GainController1::
     46    AnalogGainController::ClippingPredictor;
     47 
     48 // TODO(webrtc:7494): Hardcode clipping predictor parameters and remove this
     49 // function after no longer needed in the ctor.
     50 Agc1ClippingPredictorConfig CreateClippingPredictorConfig(bool enabled) {
     51  Agc1ClippingPredictorConfig config;
     52  config.enabled = enabled;
     53 
     54  return config;
     55 }
     56 
     57 // Returns an input volume in the [`min_input_volume`, `kMaxInputVolume`] range
     58 // that reduces `gain_error_db`, which is a gain error estimated when
     59 // `input_volume` was applied, according to a fixed gain map.
     60 int ComputeVolumeUpdate(int gain_error_db,
     61                        int input_volume,
     62                        int min_input_volume) {
     63  RTC_DCHECK_GE(input_volume, 0);
     64  RTC_DCHECK_LE(input_volume, kMaxInputVolume);
     65  if (gain_error_db == 0) {
     66    return input_volume;
     67  }
     68 
     69  int new_volume = input_volume;
     70  if (gain_error_db > 0) {
     71    while (kGainMap[new_volume] - kGainMap[input_volume] < gain_error_db &&
     72           new_volume < kMaxInputVolume) {
     73      ++new_volume;
     74    }
     75  } else {
     76    while (kGainMap[new_volume] - kGainMap[input_volume] > gain_error_db &&
     77           new_volume > min_input_volume) {
     78      --new_volume;
     79    }
     80  }
     81  return new_volume;
     82 }
     83 
     84 // Returns the proportion of samples in the buffer which are at full-scale
     85 // (and presumably clipped).
     86 float ComputeClippedRatio(const float* const* audio,
     87                          size_t num_channels,
     88                          size_t samples_per_channel) {
     89  RTC_DCHECK_GT(samples_per_channel, 0);
     90  int num_clipped = 0;
     91  for (size_t ch = 0; ch < num_channels; ++ch) {
     92    int num_clipped_in_ch = 0;
     93    for (size_t i = 0; i < samples_per_channel; ++i) {
     94      RTC_DCHECK(audio[ch]);
     95      if (audio[ch][i] >= 32767.0f || audio[ch][i] <= -32768.0f) {
     96        ++num_clipped_in_ch;
     97      }
     98    }
     99    num_clipped = std::max(num_clipped, num_clipped_in_ch);
    100  }
    101  return static_cast<float>(num_clipped) / (samples_per_channel);
    102 }
    103 
    104 void LogClippingMetrics(int clipping_rate) {
    105  RTC_LOG(LS_INFO) << "[AGC2] Input clipping rate: " << clipping_rate << "%";
    106  RTC_HISTOGRAM_COUNTS_LINEAR(/*name=*/"WebRTC.Audio.Agc.InputClippingRate",
    107                              /*sample=*/clipping_rate, /*min=*/0, /*max=*/100,
    108                              /*bucket_count=*/50);
    109 }
    110 
    111 // Compares `speech_level_dbfs` to the [`target_range_min_dbfs`,
    112 // `target_range_max_dbfs`] range and returns the error to be compensated via
    113 // input volume adjustment. Returns a positive value when the level is below
    114 // the range, a negative value when the level is above the range, zero
    115 // otherwise.
    116 int GetSpeechLevelRmsErrorDb(float speech_level_dbfs,
    117                             int target_range_min_dbfs,
    118                             int target_range_max_dbfs) {
    119  constexpr float kMinSpeechLevelDbfs = -90.0f;
    120  constexpr float kMaxSpeechLevelDbfs = 30.0f;
    121  RTC_DCHECK_GE(speech_level_dbfs, kMinSpeechLevelDbfs);
    122  RTC_DCHECK_LE(speech_level_dbfs, kMaxSpeechLevelDbfs);
    123  speech_level_dbfs = SafeClamp<float>(speech_level_dbfs, kMinSpeechLevelDbfs,
    124                                       kMaxSpeechLevelDbfs);
    125 
    126  int rms_error_db = 0;
    127  if (speech_level_dbfs > target_range_max_dbfs) {
    128    rms_error_db = std::round(target_range_max_dbfs - speech_level_dbfs);
    129  } else if (speech_level_dbfs < target_range_min_dbfs) {
    130    rms_error_db = std::round(target_range_min_dbfs - speech_level_dbfs);
    131  }
    132 
    133  return rms_error_db;
    134 }
    135 
    136 }  // namespace
    137 
    138 MonoInputVolumeController::MonoInputVolumeController(
    139    int min_input_volume_after_clipping,
    140    int min_input_volume,
    141    int update_input_volume_wait_frames,
    142    float speech_probability_threshold,
    143    float speech_ratio_threshold)
    144    : min_input_volume_(min_input_volume),
    145      min_input_volume_after_clipping_(min_input_volume_after_clipping),
    146      max_input_volume_(kMaxInputVolume),
    147      update_input_volume_wait_frames_(
    148          std::max(update_input_volume_wait_frames, 1)),
    149      speech_probability_threshold_(speech_probability_threshold),
    150      speech_ratio_threshold_(speech_ratio_threshold) {
    151  RTC_DCHECK_GE(min_input_volume_, 0);
    152  RTC_DCHECK_LE(min_input_volume_, 255);
    153  RTC_DCHECK_GE(min_input_volume_after_clipping_, 0);
    154  RTC_DCHECK_LE(min_input_volume_after_clipping_, 255);
    155  RTC_DCHECK_GE(max_input_volume_, 0);
    156  RTC_DCHECK_LE(max_input_volume_, 255);
    157  RTC_DCHECK_GE(update_input_volume_wait_frames_, 0);
    158  RTC_DCHECK_GE(speech_probability_threshold_, 0.0f);
    159  RTC_DCHECK_LE(speech_probability_threshold_, 1.0f);
    160  RTC_DCHECK_GE(speech_ratio_threshold_, 0.0f);
    161  RTC_DCHECK_LE(speech_ratio_threshold_, 1.0f);
    162 }
    163 
    164 MonoInputVolumeController::~MonoInputVolumeController() = default;
    165 
    166 void MonoInputVolumeController::Initialize() {
    167  max_input_volume_ = kMaxInputVolume;
    168  capture_output_used_ = true;
    169  check_volume_on_next_process_ = true;
    170  frames_since_update_input_volume_ = 0;
    171  speech_frames_since_update_input_volume_ = 0;
    172  is_first_frame_ = true;
    173 }
    174 
    175 // A speeh segment is considered active if at least
    176 // `update_input_volume_wait_frames_` new frames have been processed since the
    177 // previous update and the ratio of non-silence frames (i.e., frames with a
    178 // `speech_probability` higher than `speech_probability_threshold_`) is at least
    179 // `speech_ratio_threshold_`.
    180 void MonoInputVolumeController::Process(std::optional<int> rms_error_db,
    181                                        float speech_probability) {
    182  if (check_volume_on_next_process_) {
    183    check_volume_on_next_process_ = false;
    184    // We have to wait until the first process call to check the volume,
    185    // because Chromium doesn't guarantee it to be valid any earlier.
    186    CheckVolumeAndReset();
    187  }
    188 
    189  // Count frames with a high speech probability as speech.
    190  if (speech_probability >= speech_probability_threshold_) {
    191    ++speech_frames_since_update_input_volume_;
    192  }
    193 
    194  // Reset the counters and maybe update the input volume.
    195  if (++frames_since_update_input_volume_ >= update_input_volume_wait_frames_) {
    196    const float speech_ratio =
    197        static_cast<float>(speech_frames_since_update_input_volume_) /
    198        static_cast<float>(update_input_volume_wait_frames_);
    199 
    200    // Always reset the counters regardless of whether the volume changes or
    201    // not.
    202    frames_since_update_input_volume_ = 0;
    203    speech_frames_since_update_input_volume_ = 0;
    204 
    205    // Update the input volume if allowed.
    206    if (!is_first_frame_ && speech_ratio >= speech_ratio_threshold_ &&
    207        rms_error_db.has_value()) {
    208      UpdateInputVolume(*rms_error_db);
    209    }
    210  }
    211 
    212  is_first_frame_ = false;
    213 }
    214 
    215 void MonoInputVolumeController::HandleClipping(int clipped_level_step) {
    216  RTC_DCHECK_GT(clipped_level_step, 0);
    217  // Always decrease the maximum input volume, even if the current input volume
    218  // is below threshold.
    219  SetMaxLevel(std::max(min_input_volume_after_clipping_,
    220                       max_input_volume_ - clipped_level_step));
    221  if (log_to_histograms_) {
    222    RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.AgcClippingAdjustmentAllowed",
    223                          last_recommended_input_volume_ - clipped_level_step >=
    224                              min_input_volume_after_clipping_);
    225  }
    226  if (last_recommended_input_volume_ > min_input_volume_after_clipping_) {
    227    // Don't try to adjust the input volume if we're already below the limit. As
    228    // a consequence, if the user has brought the input volume above the limit,
    229    // we will still not react until the postproc updates the input volume.
    230    SetInputVolume(
    231        std::max(min_input_volume_after_clipping_,
    232                 last_recommended_input_volume_ - clipped_level_step));
    233    frames_since_update_input_volume_ = 0;
    234    speech_frames_since_update_input_volume_ = 0;
    235    is_first_frame_ = false;
    236  }
    237 }
    238 
    239 void MonoInputVolumeController::SetInputVolume(int new_volume) {
    240  int applied_input_volume = recommended_input_volume_;
    241  if (applied_input_volume == 0) {
    242    RTC_DLOG(LS_INFO)
    243        << "[AGC2] The applied input volume is zero, taking no action.";
    244    return;
    245  }
    246  if (applied_input_volume < 0 || applied_input_volume > kMaxInputVolume) {
    247    RTC_LOG(LS_ERROR) << "[AGC2] Invalid value for the applied input volume: "
    248                      << applied_input_volume;
    249    return;
    250  }
    251 
    252  // Detect manual input volume adjustments by checking if the
    253  // `applied_input_volume` is outside of the `[last_recommended_input_volume_ -
    254  // kVolumeQuantizationSlack, last_recommended_input_volume_ +
    255  // kVolumeQuantizationSlack]` range.
    256  if (applied_input_volume >
    257          last_recommended_input_volume_ + kVolumeQuantizationSlack ||
    258      applied_input_volume <
    259          last_recommended_input_volume_ - kVolumeQuantizationSlack) {
    260    RTC_DLOG(LS_INFO)
    261        << "[AGC2] The input volume was manually adjusted. Updating "
    262           "stored input volume from "
    263        << last_recommended_input_volume_ << " to " << applied_input_volume;
    264    last_recommended_input_volume_ = applied_input_volume;
    265    // Always allow the user to increase the volume.
    266    if (last_recommended_input_volume_ > max_input_volume_) {
    267      SetMaxLevel(last_recommended_input_volume_);
    268    }
    269    // Take no action in this case, since we can't be sure when the volume
    270    // was manually adjusted.
    271    frames_since_update_input_volume_ = 0;
    272    speech_frames_since_update_input_volume_ = 0;
    273    is_first_frame_ = false;
    274    return;
    275  }
    276 
    277  new_volume = std::min(new_volume, max_input_volume_);
    278  if (new_volume == last_recommended_input_volume_) {
    279    return;
    280  }
    281 
    282  recommended_input_volume_ = new_volume;
    283  RTC_DLOG(LS_INFO) << "[AGC2] Applied input volume: " << applied_input_volume
    284                    << " | last recommended input volume: "
    285                    << last_recommended_input_volume_
    286                    << " | newly recommended input volume: " << new_volume;
    287  last_recommended_input_volume_ = new_volume;
    288 }
    289 
    290 void MonoInputVolumeController::SetMaxLevel(int input_volume) {
    291  RTC_DCHECK_GE(input_volume, min_input_volume_after_clipping_);
    292  max_input_volume_ = input_volume;
    293  RTC_DLOG(LS_INFO) << "[AGC2] Maximum input volume updated: "
    294                    << max_input_volume_;
    295 }
    296 
    297 void MonoInputVolumeController::HandleCaptureOutputUsedChange(
    298    bool capture_output_used) {
    299  if (capture_output_used_ == capture_output_used) {
    300    return;
    301  }
    302  capture_output_used_ = capture_output_used;
    303 
    304  if (capture_output_used) {
    305    // When we start using the output, we should reset things to be safe.
    306    check_volume_on_next_process_ = true;
    307  }
    308 }
    309 
    310 int MonoInputVolumeController::CheckVolumeAndReset() {
    311  int input_volume = recommended_input_volume_;
    312  // Reasons for taking action at startup:
    313  // 1) A person starting a call is expected to be heard.
    314  // 2) Independent of interpretation of `input_volume` == 0 we should raise it
    315  // so the AGC can do its job properly.
    316  if (input_volume == 0 && !startup_) {
    317    RTC_DLOG(LS_INFO)
    318        << "[AGC2] The applied input volume is zero, taking no action.";
    319    return 0;
    320  }
    321  if (input_volume < 0 || input_volume > kMaxInputVolume) {
    322    RTC_LOG(LS_ERROR) << "[AGC2] Invalid value for the applied input volume: "
    323                      << input_volume;
    324    return -1;
    325  }
    326  RTC_DLOG(LS_INFO) << "[AGC2] Initial input volume: " << input_volume;
    327 
    328  if (input_volume < min_input_volume_) {
    329    input_volume = min_input_volume_;
    330    RTC_DLOG(LS_INFO)
    331        << "[AGC2] The initial input volume is too low, raising to "
    332        << input_volume;
    333    recommended_input_volume_ = input_volume;
    334  }
    335 
    336  last_recommended_input_volume_ = input_volume;
    337  startup_ = false;
    338  frames_since_update_input_volume_ = 0;
    339  speech_frames_since_update_input_volume_ = 0;
    340  is_first_frame_ = true;
    341 
    342  return 0;
    343 }
    344 
    345 void MonoInputVolumeController::UpdateInputVolume(int rms_error_db) {
    346  RTC_DLOG(LS_INFO) << "[AGC2] RMS error: " << rms_error_db << " dB";
    347  // Prevent too large microphone input volume changes by clamping the RMS
    348  // error.
    349  rms_error_db =
    350      SafeClamp(rms_error_db, -KMaxAbsRmsErrorDbfs, KMaxAbsRmsErrorDbfs);
    351  if (rms_error_db == 0) {
    352    return;
    353  }
    354  SetInputVolume(ComputeVolumeUpdate(
    355      rms_error_db, last_recommended_input_volume_, min_input_volume_));
    356 }
    357 
    358 InputVolumeController::InputVolumeController(int num_capture_channels,
    359                                             const Config& config)
    360    : num_capture_channels_(num_capture_channels),
    361      min_input_volume_(config.min_input_volume),
    362      capture_output_used_(true),
    363      clipped_level_step_(config.clipped_level_step),
    364      clipped_ratio_threshold_(config.clipped_ratio_threshold),
    365      clipped_wait_frames_(config.clipped_wait_frames),
    366      clipping_predictor_(CreateClippingPredictor(
    367          num_capture_channels,
    368          CreateClippingPredictorConfig(config.enable_clipping_predictor))),
    369      use_clipping_predictor_step_(
    370          !!clipping_predictor_ &&
    371          CreateClippingPredictorConfig(config.enable_clipping_predictor)
    372              .use_predicted_step),
    373      frames_since_clipped_(config.clipped_wait_frames),
    374      clipping_rate_log_counter_(0),
    375      clipping_rate_log_(0.0f),
    376      target_range_max_dbfs_(config.target_range_max_dbfs),
    377      target_range_min_dbfs_(config.target_range_min_dbfs),
    378      channel_controllers_(num_capture_channels) {
    379  RTC_LOG(LS_INFO)
    380      << "[AGC2] Input volume controller enabled. Minimum input volume: "
    381      << min_input_volume_;
    382 
    383  for (auto& controller : channel_controllers_) {
    384    controller = std::make_unique<MonoInputVolumeController>(
    385        config.clipped_level_min, min_input_volume_,
    386        config.update_input_volume_wait_frames,
    387        config.speech_probability_threshold, config.speech_ratio_threshold);
    388  }
    389 
    390  RTC_DCHECK(!channel_controllers_.empty());
    391  RTC_DCHECK_GT(clipped_level_step_, 0);
    392  RTC_DCHECK_LE(clipped_level_step_, 255);
    393  RTC_DCHECK_GT(clipped_ratio_threshold_, 0.0f);
    394  RTC_DCHECK_LT(clipped_ratio_threshold_, 1.0f);
    395  RTC_DCHECK_GT(clipped_wait_frames_, 0);
    396  channel_controllers_[0]->ActivateLogging();
    397 }
    398 
    399 InputVolumeController::~InputVolumeController() {}
    400 
    401 void InputVolumeController::Initialize() {
    402  for (auto& controller : channel_controllers_) {
    403    controller->Initialize();
    404  }
    405  capture_output_used_ = true;
    406 
    407  AggregateChannelLevels();
    408  clipping_rate_log_ = 0.0f;
    409  clipping_rate_log_counter_ = 0;
    410 
    411  applied_input_volume_ = std::nullopt;
    412 }
    413 
    414 void InputVolumeController::AnalyzeInputAudio(int applied_input_volume,
    415                                              const AudioBuffer& audio_buffer) {
    416  RTC_DCHECK_GE(applied_input_volume, 0);
    417  RTC_DCHECK_LE(applied_input_volume, 255);
    418 
    419  SetAppliedInputVolume(applied_input_volume);
    420 
    421  RTC_DCHECK_EQ(audio_buffer.num_channels(), channel_controllers_.size());
    422  const float* const* audio = audio_buffer.channels_const();
    423  size_t samples_per_channel = audio_buffer.num_frames();
    424  RTC_DCHECK(audio);
    425 
    426  AggregateChannelLevels();
    427  if (!capture_output_used_) {
    428    return;
    429  }
    430 
    431  if (!!clipping_predictor_) {
    432    AudioFrameView<const float> frame = AudioFrameView<const float>(
    433        audio, num_capture_channels_, static_cast<int>(samples_per_channel));
    434    clipping_predictor_->Analyze(frame);
    435  }
    436 
    437  // Check for clipped samples. We do this in the preprocessing phase in order
    438  // to catch clipped echo as well.
    439  //
    440  // If we find a sufficiently clipped frame, drop the current microphone
    441  // input volume and enforce a new maximum input volume, dropped the same
    442  // amount from the current maximum. This harsh treatment is an effort to avoid
    443  // repeated clipped echo events.
    444  float clipped_ratio =
    445      ComputeClippedRatio(audio, num_capture_channels_, samples_per_channel);
    446  clipping_rate_log_ = std::max(clipped_ratio, clipping_rate_log_);
    447  clipping_rate_log_counter_++;
    448  constexpr int kNumFramesIn30Seconds = 3000;
    449  if (clipping_rate_log_counter_ == kNumFramesIn30Seconds) {
    450    LogClippingMetrics(std::round(100.0f * clipping_rate_log_));
    451    clipping_rate_log_ = 0.0f;
    452    clipping_rate_log_counter_ = 0;
    453  }
    454 
    455  if (frames_since_clipped_ < clipped_wait_frames_) {
    456    ++frames_since_clipped_;
    457    return;
    458  }
    459 
    460  const bool clipping_detected = clipped_ratio > clipped_ratio_threshold_;
    461  bool clipping_predicted = false;
    462  int predicted_step = 0;
    463  if (!!clipping_predictor_) {
    464    for (int channel = 0; channel < num_capture_channels_; ++channel) {
    465      const auto step = clipping_predictor_->EstimateClippedLevelStep(
    466          channel, recommended_input_volume_, clipped_level_step_,
    467          channel_controllers_[channel]->min_input_volume_after_clipping(),
    468          kMaxInputVolume);
    469      if (step.has_value()) {
    470        predicted_step = std::max(predicted_step, step.value());
    471        clipping_predicted = true;
    472      }
    473    }
    474  }
    475 
    476  if (clipping_detected) {
    477    RTC_DLOG(LS_INFO) << "[AGC2] Clipping detected (ratio: " << clipped_ratio
    478                      << ")";
    479  }
    480 
    481  int step = clipped_level_step_;
    482  if (clipping_predicted) {
    483    predicted_step = std::max(predicted_step, clipped_level_step_);
    484    RTC_DLOG(LS_INFO) << "[AGC2] Clipping predicted (volume down step: "
    485                      << predicted_step << ")";
    486    if (use_clipping_predictor_step_) {
    487      step = predicted_step;
    488    }
    489  }
    490 
    491  if (clipping_detected ||
    492      (clipping_predicted && use_clipping_predictor_step_)) {
    493    for (auto& state_ch : channel_controllers_) {
    494      state_ch->HandleClipping(step);
    495    }
    496    frames_since_clipped_ = 0;
    497    if (!!clipping_predictor_) {
    498      clipping_predictor_->Reset();
    499    }
    500  }
    501 
    502  AggregateChannelLevels();
    503 }
    504 
    505 std::optional<int> InputVolumeController::RecommendInputVolume(
    506    float speech_probability,
    507    std::optional<float> speech_level_dbfs) {
    508  // Only process if applied input volume is set.
    509  if (!applied_input_volume_.has_value()) {
    510    RTC_LOG(LS_ERROR) << "[AGC2] Applied input volume not set.";
    511    return std::nullopt;
    512  }
    513 
    514  AggregateChannelLevels();
    515  const int volume_after_clipping_handling = recommended_input_volume_;
    516 
    517  if (!capture_output_used_) {
    518    return applied_input_volume_;
    519  }
    520 
    521  std::optional<int> rms_error_db;
    522  if (speech_level_dbfs.has_value()) {
    523    // Compute the error for all frames (both speech and non-speech frames).
    524    rms_error_db = GetSpeechLevelRmsErrorDb(
    525        *speech_level_dbfs, target_range_min_dbfs_, target_range_max_dbfs_);
    526  }
    527 
    528  for (auto& controller : channel_controllers_) {
    529    controller->Process(rms_error_db, speech_probability);
    530  }
    531 
    532  AggregateChannelLevels();
    533  if (volume_after_clipping_handling != recommended_input_volume_) {
    534    // The recommended input volume was adjusted in order to match the target
    535    // level.
    536    UpdateHistogramOnRecommendedInputVolumeChangeToMatchTarget(
    537        recommended_input_volume_);
    538  }
    539 
    540  applied_input_volume_ = std::nullopt;
    541  return recommended_input_volume();
    542 }
    543 
    544 void InputVolumeController::HandleCaptureOutputUsedChange(
    545    bool capture_output_used) {
    546  for (auto& controller : channel_controllers_) {
    547    controller->HandleCaptureOutputUsedChange(capture_output_used);
    548  }
    549 
    550  capture_output_used_ = capture_output_used;
    551 }
    552 
    553 void InputVolumeController::SetAppliedInputVolume(int input_volume) {
    554  applied_input_volume_ = input_volume;
    555 
    556  for (auto& controller : channel_controllers_) {
    557    controller->set_stream_analog_level(input_volume);
    558  }
    559 
    560  AggregateChannelLevels();
    561 }
    562 
    563 void InputVolumeController::AggregateChannelLevels() {
    564  int new_recommended_input_volume =
    565      channel_controllers_[0]->recommended_analog_level();
    566  channel_controlling_gain_ = 0;
    567  for (size_t ch = 1; ch < channel_controllers_.size(); ++ch) {
    568    int input_volume = channel_controllers_[ch]->recommended_analog_level();
    569    if (input_volume < new_recommended_input_volume) {
    570      new_recommended_input_volume = input_volume;
    571      channel_controlling_gain_ = static_cast<int>(ch);
    572    }
    573  }
    574 
    575  // Enforce the minimum input volume when a recommendation is made.
    576  if (applied_input_volume_.has_value() && *applied_input_volume_ > 0) {
    577    new_recommended_input_volume =
    578        std::max(new_recommended_input_volume, min_input_volume_);
    579  }
    580 
    581  recommended_input_volume_ = new_recommended_input_volume;
    582 }
    583 
    584 }  // namespace webrtc