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