InputVolumeController: readability improvements
- use the new naming convention 'input volume' - fix Yoda-style expressions in the unit tests - clarify how the gain map values are generated Bug: webrtc:7494 Change-Id: I4d6ee897a93cdefa6735733b053c57326d01a528 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/285467 Commit-Queue: Alessio Bazzica <alessiob@webrtc.org> Reviewed-by: Hanna Silen <silen@webrtc.org> Cr-Commit-Position: refs/heads/main@{#38795}
This commit is contained in:
parent
59ade0172f
commit
e001474407
@ -25,30 +25,31 @@ namespace {
|
||||
|
||||
constexpr int kClippingPredictorMaxGainChange = 15;
|
||||
|
||||
// Estimates the new level from the gain error; a copy of the function
|
||||
// `LevelFromGainError` in agc_manager_direct.cc.
|
||||
int LevelFromGainError(int gain_error,
|
||||
int level,
|
||||
int min_mic_level,
|
||||
int max_mic_level) {
|
||||
RTC_DCHECK_GE(level, 0);
|
||||
RTC_DCHECK_LE(level, max_mic_level);
|
||||
if (gain_error == 0) {
|
||||
return level;
|
||||
// Returns an input volume in the [`min_input_volume`, `max_input_volume`] range
|
||||
// that reduces `gain_error_db`, which is a gain error estimated when
|
||||
// `input_volume` was applied, according to a fixed gain map.
|
||||
int ComputeVolumeUpdate(int gain_error_db,
|
||||
int input_volume,
|
||||
int min_input_volume,
|
||||
int max_input_volume) {
|
||||
RTC_DCHECK_GE(input_volume, 0);
|
||||
RTC_DCHECK_LE(input_volume, max_input_volume);
|
||||
if (gain_error_db == 0) {
|
||||
return input_volume;
|
||||
}
|
||||
int new_level = level;
|
||||
if (gain_error > 0) {
|
||||
while (kGainMap[new_level] - kGainMap[level] < gain_error &&
|
||||
new_level < max_mic_level) {
|
||||
++new_level;
|
||||
int new_volume = input_volume;
|
||||
if (gain_error_db > 0) {
|
||||
while (kGainMap[new_volume] - kGainMap[input_volume] < gain_error_db &&
|
||||
new_volume < max_input_volume) {
|
||||
++new_volume;
|
||||
}
|
||||
} else {
|
||||
while (kGainMap[new_level] - kGainMap[level] > gain_error &&
|
||||
new_level > min_mic_level) {
|
||||
--new_level;
|
||||
while (kGainMap[new_volume] - kGainMap[input_volume] > gain_error_db &&
|
||||
new_volume > min_input_volume) {
|
||||
--new_volume;
|
||||
}
|
||||
}
|
||||
return new_level;
|
||||
return new_volume;
|
||||
}
|
||||
|
||||
float ComputeCrestFactor(const ClippingPredictorLevelBuffer::Level& level) {
|
||||
@ -298,8 +299,8 @@ class ClippingPeakPredictor : public ClippingPredictor {
|
||||
rtc::SafeClamp(-static_cast<int>(std::ceil(estimate_db.value())),
|
||||
-kClippingPredictorMaxGainChange, 0);
|
||||
step =
|
||||
std::max(level - LevelFromGainError(estimated_gain_change, level,
|
||||
min_mic_level, max_mic_level),
|
||||
std::max(level - ComputeVolumeUpdate(estimated_gain_change, level,
|
||||
min_mic_level, max_mic_level),
|
||||
default_step);
|
||||
}
|
||||
const int new_level =
|
||||
@ -354,10 +355,10 @@ std::unique_ptr<ClippingPredictor> CreateClippingPredictor(
|
||||
const AudioProcessing::Config::GainController1::AnalogGainController::
|
||||
ClippingPredictor& config) {
|
||||
if (!config.enabled) {
|
||||
RTC_LOG(LS_INFO) << "[agc] Clipping prediction disabled.";
|
||||
RTC_LOG(LS_INFO) << "[AGC2] Clipping prediction disabled.";
|
||||
return nullptr;
|
||||
}
|
||||
RTC_LOG(LS_INFO) << "[agc] Clipping prediction enabled.";
|
||||
RTC_LOG(LS_INFO) << "[AGC2] Clipping prediction enabled.";
|
||||
using ClippingPredictorMode = AudioProcessing::Config::GainController1::
|
||||
AnalogGainController::ClippingPredictor::Mode;
|
||||
switch (config.mode) {
|
||||
|
||||
@ -13,8 +13,14 @@
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
static const int kGainMapSize = 256;
|
||||
// Uses parameters: si = 2, sf = 0.25, D = 8/256
|
||||
static constexpr int kGainMapSize = 256;
|
||||
// Maps input volumes, which are values in the [0, 255] range, to gains in dB.
|
||||
// The values below are generated with numpy as follows:
|
||||
// SI = 2 # Initial slope.
|
||||
// SF = 0.25 # Final slope.
|
||||
// D = 8/256 # Quantization factor.
|
||||
// x = np.linspace(0, 255, 256) # Input volumes.
|
||||
// y = (SF * x + (SI - SF) * (1 - np.exp(-D*x)) / D - 56).round()
|
||||
static const int kGainMap[kGainMapSize] = {
|
||||
-56, -54, -52, -50, -48, -47, -45, -43, -42, -40, -38, -37, -35, -34, -33,
|
||||
-31, -30, -29, -27, -26, -25, -24, -23, -22, -20, -19, -18, -17, -16, -15,
|
||||
|
||||
@ -26,15 +26,16 @@ namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
// Amount of error we tolerate in the microphone level (presumably due to OS
|
||||
// quantization) before we assume the user has manually adjusted the microphone.
|
||||
constexpr int kLevelQuantizationSlack = 25;
|
||||
// Amount of error we tolerate in the microphone input volume (presumably due to
|
||||
// OS quantization) before we assume the user has manually adjusted the volume.
|
||||
constexpr int kVolumeQuantizationSlack = 25;
|
||||
|
||||
constexpr int kMaxMicLevel = 255;
|
||||
static_assert(kGainMapSize > kMaxMicLevel, "gain map too small");
|
||||
constexpr int kMaxInputVolume = 255;
|
||||
static_assert(kGainMapSize > kMaxInputVolume, "gain map too small");
|
||||
|
||||
// Prevent very large microphone level changes.
|
||||
constexpr int kMaxResidualGainChange = 15;
|
||||
// Maximum absolute RMS error.
|
||||
constexpr int KMaxAbsRmsErrorDbfs = 15;
|
||||
static_assert(KMaxAbsRmsErrorDbfs > 0, "");
|
||||
|
||||
using Agc1ClippingPredictorConfig = AudioProcessing::Config::GainController1::
|
||||
AnalogGainController::ClippingPredictor;
|
||||
@ -66,30 +67,36 @@ int GetMinInputVolume() {
|
||||
if (min_input_volume >= 0 && min_input_volume <= 255) {
|
||||
return min_input_volume;
|
||||
}
|
||||
RTC_LOG(LS_WARNING) << "Invalid volume for " << kFieldTrial << ", ignored.";
|
||||
RTC_LOG(LS_WARNING) << "[AGC2] Invalid volume for " << kFieldTrial
|
||||
<< ", ignored.";
|
||||
return kDefaultMinInputVolume;
|
||||
}
|
||||
|
||||
int LevelFromGainError(int gain_error, int level, int min_input_volume) {
|
||||
RTC_DCHECK_GE(level, 0);
|
||||
RTC_DCHECK_LE(level, kMaxMicLevel);
|
||||
if (gain_error == 0) {
|
||||
return level;
|
||||
// Returns an input volume in the [`min_input_volume`, `kMaxInputVolume`] range
|
||||
// that reduces `gain_error_db`, which is a gain error estimated when
|
||||
// `input_volume` was applied, according to a fixed gain map.
|
||||
int ComputeVolumeUpdate(int gain_error_db,
|
||||
int input_volume,
|
||||
int min_input_volume) {
|
||||
RTC_DCHECK_GE(input_volume, 0);
|
||||
RTC_DCHECK_LE(input_volume, kMaxInputVolume);
|
||||
if (gain_error_db == 0) {
|
||||
return input_volume;
|
||||
}
|
||||
|
||||
int new_level = level;
|
||||
if (gain_error > 0) {
|
||||
while (kGainMap[new_level] - kGainMap[level] < gain_error &&
|
||||
new_level < kMaxMicLevel) {
|
||||
++new_level;
|
||||
int new_volume = input_volume;
|
||||
if (gain_error_db > 0) {
|
||||
while (kGainMap[new_volume] - kGainMap[input_volume] < gain_error_db &&
|
||||
new_volume < kMaxInputVolume) {
|
||||
++new_volume;
|
||||
}
|
||||
} else {
|
||||
while (kGainMap[new_level] - kGainMap[level] > gain_error &&
|
||||
new_level > min_input_volume) {
|
||||
--new_level;
|
||||
while (kGainMap[new_volume] - kGainMap[input_volume] > gain_error_db &&
|
||||
new_volume > min_input_volume) {
|
||||
--new_volume;
|
||||
}
|
||||
}
|
||||
return new_level;
|
||||
return new_volume;
|
||||
}
|
||||
|
||||
// Returns the proportion of samples in the buffer which are at full-scale
|
||||
@ -113,59 +120,58 @@ float ComputeClippedRatio(const float* const* audio,
|
||||
}
|
||||
|
||||
void LogClippingMetrics(int clipping_rate) {
|
||||
RTC_LOG(LS_INFO) << "Input clipping rate: " << clipping_rate << "%";
|
||||
RTC_LOG(LS_INFO) << "[AGC2] Input clipping rate: " << clipping_rate << "%";
|
||||
RTC_HISTOGRAM_COUNTS_LINEAR(/*name=*/"WebRTC.Audio.Agc.InputClippingRate",
|
||||
/*sample=*/clipping_rate, /*min=*/0, /*max=*/100,
|
||||
/*bucket_count=*/50);
|
||||
}
|
||||
|
||||
// Computes the speech level error in dB. The value of `speech_level_dbfs` is
|
||||
// required to be in the range [-90.0f, 30.0f]. Returns a positive value when
|
||||
// the speech level is below the target range and a negative value when the
|
||||
// speech level is above the target range.
|
||||
int GetSpeechLevelErrorDb(float speech_level_dbfs,
|
||||
int target_range_min_dbfs,
|
||||
int target_range_max_dbfs) {
|
||||
// Compares `speech_level_dbfs` to the [`target_range_min_dbfs`,
|
||||
// `target_range_max_dbfs`] range and returns the error to be compensated via
|
||||
// input volume adjustment. Returns a positive value when the level is below
|
||||
// the range, a negative value when the level is above the range, zero
|
||||
// otherwise.
|
||||
int GetSpeechLevelRmsErrorDb(float speech_level_dbfs,
|
||||
int target_range_min_dbfs,
|
||||
int target_range_max_dbfs) {
|
||||
constexpr float kMinSpeechLevelDbfs = -90.0f;
|
||||
constexpr float kMaxSpeechLevelDbfs = 30.0f;
|
||||
RTC_DCHECK_GE(speech_level_dbfs, kMinSpeechLevelDbfs);
|
||||
RTC_DCHECK_LE(speech_level_dbfs, kMaxSpeechLevelDbfs);
|
||||
|
||||
// Ensure the speech level is in the range [-90.0f, 30.0f].
|
||||
speech_level_dbfs = rtc::SafeClamp<float>(
|
||||
speech_level_dbfs, kMinSpeechLevelDbfs, kMaxSpeechLevelDbfs);
|
||||
|
||||
// Compute the speech level distance to the target range
|
||||
// [`target_range_min_dbfs`, `target_range_max_dbfs`].
|
||||
int rms_error_dbfs = 0;
|
||||
int rms_error_db = 0;
|
||||
if (speech_level_dbfs > target_range_max_dbfs) {
|
||||
rms_error_dbfs = std::round(target_range_max_dbfs - speech_level_dbfs);
|
||||
rms_error_db = std::round(target_range_max_dbfs - speech_level_dbfs);
|
||||
} else if (speech_level_dbfs < target_range_min_dbfs) {
|
||||
rms_error_dbfs = std::round(target_range_min_dbfs - speech_level_dbfs);
|
||||
rms_error_db = std::round(target_range_min_dbfs - speech_level_dbfs);
|
||||
}
|
||||
|
||||
return rms_error_dbfs;
|
||||
return rms_error_db;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MonoInputVolumeController::MonoInputVolumeController(
|
||||
int clipped_level_min,
|
||||
int min_input_volume_after_clipping,
|
||||
int min_input_volume,
|
||||
int update_input_volume_wait_frames,
|
||||
float speech_probability_threshold,
|
||||
float speech_ratio_threshold)
|
||||
: min_input_volume_(min_input_volume),
|
||||
max_level_(kMaxMicLevel),
|
||||
clipped_level_min_(clipped_level_min),
|
||||
min_input_volume_after_clipping_(min_input_volume_after_clipping),
|
||||
max_input_volume_(kMaxInputVolume),
|
||||
update_input_volume_wait_frames_(
|
||||
std::max(update_input_volume_wait_frames, 1)),
|
||||
speech_probability_threshold_(speech_probability_threshold),
|
||||
speech_ratio_threshold_(speech_ratio_threshold) {
|
||||
RTC_DCHECK_GE(clipped_level_min_, 0);
|
||||
RTC_DCHECK_LE(clipped_level_min_, 255);
|
||||
RTC_DCHECK_GE(min_input_volume_, 0);
|
||||
RTC_DCHECK_LE(min_input_volume_, 255);
|
||||
RTC_DCHECK_GE(min_input_volume_after_clipping_, 0);
|
||||
RTC_DCHECK_LE(min_input_volume_after_clipping_, 255);
|
||||
RTC_DCHECK_GE(max_input_volume_, 0);
|
||||
RTC_DCHECK_LE(max_input_volume_, 255);
|
||||
RTC_DCHECK_GE(update_input_volume_wait_frames_, 0);
|
||||
RTC_DCHECK_GE(speech_probability_threshold_, 0.0f);
|
||||
RTC_DCHECK_LE(speech_probability_threshold_, 1.0f);
|
||||
@ -176,7 +182,7 @@ MonoInputVolumeController::MonoInputVolumeController(
|
||||
MonoInputVolumeController::~MonoInputVolumeController() = default;
|
||||
|
||||
void MonoInputVolumeController::Initialize() {
|
||||
max_level_ = kMaxMicLevel;
|
||||
max_input_volume_ = kMaxInputVolume;
|
||||
capture_output_used_ = true;
|
||||
check_volume_on_next_process_ = true;
|
||||
frames_since_update_input_volume_ = 0;
|
||||
@ -189,7 +195,7 @@ void MonoInputVolumeController::Initialize() {
|
||||
// previous update and the ratio of non-silence frames (i.e., frames with a
|
||||
// `speech_probability` higher than `speech_probability_threshold_`) is at least
|
||||
// `speech_ratio_threshold_`.
|
||||
void MonoInputVolumeController::Process(absl::optional<int> rms_error_dbfs,
|
||||
void MonoInputVolumeController::Process(absl::optional<int> rms_error_db,
|
||||
float speech_probability) {
|
||||
if (check_volume_on_next_process_) {
|
||||
check_volume_on_next_process_ = false;
|
||||
@ -216,8 +222,8 @@ void MonoInputVolumeController::Process(absl::optional<int> rms_error_dbfs,
|
||||
|
||||
// Update the input volume if allowed.
|
||||
if (!is_first_frame_ && speech_ratio >= speech_ratio_threshold_) {
|
||||
if (rms_error_dbfs.has_value()) {
|
||||
UpdateInputVolume(*rms_error_dbfs);
|
||||
if (rms_error_db.has_value()) {
|
||||
UpdateInputVolume(*rms_error_db);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -227,50 +233,53 @@ void MonoInputVolumeController::Process(absl::optional<int> rms_error_dbfs,
|
||||
|
||||
void MonoInputVolumeController::HandleClipping(int clipped_level_step) {
|
||||
RTC_DCHECK_GT(clipped_level_step, 0);
|
||||
// Always decrease the maximum level, even if the current level is below
|
||||
// threshold.
|
||||
SetMaxLevel(std::max(clipped_level_min_, max_level_ - clipped_level_step));
|
||||
// Always decrease the maximum input volume, even if the current input volume
|
||||
// is below threshold.
|
||||
SetMaxLevel(std::max(min_input_volume_after_clipping_,
|
||||
max_input_volume_ - clipped_level_step));
|
||||
if (log_to_histograms_) {
|
||||
RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.AgcClippingAdjustmentAllowed",
|
||||
level_ - clipped_level_step >= clipped_level_min_);
|
||||
RTC_HISTOGRAM_BOOLEAN(
|
||||
"WebRTC.Audio.AgcClippingAdjustmentAllowed",
|
||||
input_volume_ - clipped_level_step >= min_input_volume_after_clipping_);
|
||||
}
|
||||
if (level_ > clipped_level_min_) {
|
||||
// Don't try to adjust the level if we're already below the limit. As
|
||||
// a consequence, if the user has brought the level above the limit, we
|
||||
// will still not react until the postproc updates the level.
|
||||
SetLevel(std::max(clipped_level_min_, level_ - clipped_level_step));
|
||||
if (input_volume_ > min_input_volume_after_clipping_) {
|
||||
// Don't try to adjust the input volume if we're already below the limit. As
|
||||
// a consequence, if the user has brought the input volume above the limit,
|
||||
// we will still not react until the postproc updates the input volume.
|
||||
SetInputVolume(std::max(min_input_volume_after_clipping_,
|
||||
input_volume_ - clipped_level_step));
|
||||
frames_since_update_input_volume_ = 0;
|
||||
speech_frames_since_update_input_volume_ = 0;
|
||||
is_first_frame_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void MonoInputVolumeController::SetLevel(int new_level) {
|
||||
int voe_level = recommended_input_volume_;
|
||||
if (voe_level == 0) {
|
||||
void MonoInputVolumeController::SetInputVolume(int new_volume) {
|
||||
int applied_input_volume = recommended_input_volume_;
|
||||
if (applied_input_volume == 0) {
|
||||
RTC_DLOG(LS_INFO)
|
||||
<< "[agc] VolumeCallbacks returned level=0, taking no action.";
|
||||
<< "[AGC2] The applied input volume is zero, taking no action.";
|
||||
return;
|
||||
}
|
||||
if (voe_level < 0 || voe_level > kMaxMicLevel) {
|
||||
RTC_LOG(LS_ERROR) << "VolumeCallbacks returned an invalid level="
|
||||
<< voe_level;
|
||||
if (applied_input_volume < 0 || applied_input_volume > kMaxInputVolume) {
|
||||
RTC_LOG(LS_ERROR) << "[AGC2] Invalid value for the applied input volume: "
|
||||
<< applied_input_volume;
|
||||
return;
|
||||
}
|
||||
|
||||
// Detect manual input volume adjustments by checking if the current level
|
||||
// `voe_level` is outside of the `[level_ - kLevelQuantizationSlack, level_ +
|
||||
// kLevelQuantizationSlack]` range where `level_` is the last input volume
|
||||
// known by this gain controller.
|
||||
if (voe_level > level_ + kLevelQuantizationSlack ||
|
||||
voe_level < level_ - kLevelQuantizationSlack) {
|
||||
RTC_DLOG(LS_INFO) << "[agc] Mic volume was manually adjusted. Updating "
|
||||
"stored level from "
|
||||
<< level_ << " to " << voe_level;
|
||||
level_ = voe_level;
|
||||
// Detect manual input volume adjustments by checking if the
|
||||
// `applied_input_volume` is outside of the `[input_volume_ -
|
||||
// kVolumeQuantizationSlack, input_volume_ + kVolumeQuantizationSlack]` range.
|
||||
if (applied_input_volume > input_volume_ + kVolumeQuantizationSlack ||
|
||||
applied_input_volume < input_volume_ - kVolumeQuantizationSlack) {
|
||||
RTC_DLOG(LS_INFO)
|
||||
<< "[AGC2] The input volume was manually adjusted. Updating "
|
||||
"stored input volume from "
|
||||
<< input_volume_ << " to " << applied_input_volume;
|
||||
input_volume_ = applied_input_volume;
|
||||
// Always allow the user to increase the volume.
|
||||
if (level_ > max_level_) {
|
||||
SetMaxLevel(level_);
|
||||
if (input_volume_ > max_input_volume_) {
|
||||
SetMaxLevel(input_volume_);
|
||||
}
|
||||
// Take no action in this case, since we can't be sure when the volume
|
||||
// was manually adjusted.
|
||||
@ -280,21 +289,23 @@ void MonoInputVolumeController::SetLevel(int new_level) {
|
||||
return;
|
||||
}
|
||||
|
||||
new_level = std::min(new_level, max_level_);
|
||||
if (new_level == level_) {
|
||||
new_volume = std::min(new_volume, max_input_volume_);
|
||||
if (new_volume == input_volume_) {
|
||||
return;
|
||||
}
|
||||
|
||||
recommended_input_volume_ = new_level;
|
||||
RTC_DLOG(LS_INFO) << "[agc] voe_level=" << voe_level << ", level_=" << level_
|
||||
<< ", new_level=" << new_level;
|
||||
level_ = new_level;
|
||||
recommended_input_volume_ = new_volume;
|
||||
RTC_DLOG(LS_INFO) << "[AGC2] Applied input volume: " << applied_input_volume
|
||||
<< " | last recommended input volume: " << input_volume_
|
||||
<< " | newly recommended input volume: " << new_volume;
|
||||
input_volume_ = new_volume;
|
||||
}
|
||||
|
||||
void MonoInputVolumeController::SetMaxLevel(int level) {
|
||||
RTC_DCHECK_GE(level, clipped_level_min_);
|
||||
max_level_ = level;
|
||||
RTC_DLOG(LS_INFO) << "[agc] max_level_=" << max_level_;
|
||||
void MonoInputVolumeController::SetMaxLevel(int input_volume) {
|
||||
RTC_DCHECK_GE(input_volume, min_input_volume_after_clipping_);
|
||||
max_input_volume_ = input_volume;
|
||||
RTC_DLOG(LS_INFO) << "[AGC2] Maximum input volume updated: "
|
||||
<< max_input_volume_;
|
||||
}
|
||||
|
||||
void MonoInputVolumeController::HandleCaptureOutputUsedChange(
|
||||
@ -311,30 +322,32 @@ void MonoInputVolumeController::HandleCaptureOutputUsedChange(
|
||||
}
|
||||
|
||||
int MonoInputVolumeController::CheckVolumeAndReset() {
|
||||
int level = recommended_input_volume_;
|
||||
int input_volume = recommended_input_volume_;
|
||||
// Reasons for taking action at startup:
|
||||
// 1) A person starting a call is expected to be heard.
|
||||
// 2) Independent of interpretation of `level` == 0 we should raise it so the
|
||||
// AGC can do its job properly.
|
||||
if (level == 0 && !startup_) {
|
||||
// 2) Independent of interpretation of `input_volume` == 0 we should raise it
|
||||
// so the AGC can do its job properly.
|
||||
if (input_volume == 0 && !startup_) {
|
||||
RTC_DLOG(LS_INFO)
|
||||
<< "[agc] VolumeCallbacks returned level=0, taking no action.";
|
||||
<< "[AGC2] The applied input volume is zero, taking no action.";
|
||||
return 0;
|
||||
}
|
||||
if (level < 0 || level > kMaxMicLevel) {
|
||||
RTC_LOG(LS_ERROR) << "[agc] VolumeCallbacks returned an invalid level="
|
||||
<< level;
|
||||
if (input_volume < 0 || input_volume > kMaxInputVolume) {
|
||||
RTC_LOG(LS_ERROR) << "[AGC2] Invalid value for the applied input volume: "
|
||||
<< input_volume;
|
||||
return -1;
|
||||
}
|
||||
RTC_DLOG(LS_INFO) << "[agc] Initial GetMicVolume()=" << level;
|
||||
RTC_DLOG(LS_INFO) << "[AGC2] Initial input volume: " << input_volume;
|
||||
|
||||
if (level < min_input_volume_) {
|
||||
level = min_input_volume_;
|
||||
RTC_DLOG(LS_INFO) << "[agc] Initial volume too low, raising to " << level;
|
||||
recommended_input_volume_ = level;
|
||||
if (input_volume < min_input_volume_) {
|
||||
input_volume = min_input_volume_;
|
||||
RTC_DLOG(LS_INFO)
|
||||
<< "[AGC2] The initial input volume is too low, raising to "
|
||||
<< input_volume;
|
||||
recommended_input_volume_ = input_volume;
|
||||
}
|
||||
|
||||
level_ = level;
|
||||
input_volume_ = input_volume;
|
||||
startup_ = false;
|
||||
frames_since_update_input_volume_ = 0;
|
||||
speech_frames_since_update_input_volume_ = 0;
|
||||
@ -343,18 +356,17 @@ int MonoInputVolumeController::CheckVolumeAndReset() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MonoInputVolumeController::UpdateInputVolume(int rms_error_dbfs) {
|
||||
const int residual_gain = rtc::SafeClamp(
|
||||
rms_error_dbfs, -kMaxResidualGainChange, kMaxResidualGainChange);
|
||||
|
||||
RTC_DLOG(LS_INFO) << "[agc] rms_error_dbfs=" << rms_error_dbfs
|
||||
<< ", residual_gain=" << residual_gain;
|
||||
|
||||
if (residual_gain == 0) {
|
||||
void MonoInputVolumeController::UpdateInputVolume(int rms_error_db) {
|
||||
RTC_DLOG(LS_INFO) << "[AGC2] RMS error: " << rms_error_db << " dB";
|
||||
// Prevent too large microphone input volume changes by clamping the RMS
|
||||
// error.
|
||||
rms_error_db =
|
||||
rtc::SafeClamp(rms_error_db, -KMaxAbsRmsErrorDbfs, KMaxAbsRmsErrorDbfs);
|
||||
if (rms_error_db == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetLevel(LevelFromGainError(residual_gain, level_, min_input_volume_));
|
||||
SetInputVolume(
|
||||
ComputeVolumeUpdate(rms_error_db, input_volume_, min_input_volume_));
|
||||
}
|
||||
|
||||
InputVolumeController::InputVolumeController(int num_capture_channels,
|
||||
@ -379,8 +391,9 @@ InputVolumeController::InputVolumeController(int num_capture_channels,
|
||||
target_range_min_dbfs_(config.target_range_min_dbfs),
|
||||
channel_controllers_(num_capture_channels) {
|
||||
RTC_LOG(LS_INFO)
|
||||
<< "[agc] Input volume controller enabled (min input volume: "
|
||||
<< min_input_volume_ << ")";
|
||||
<< "[AGC2] Input volume controller enabled. Minimum input volume: "
|
||||
<< min_input_volume_;
|
||||
|
||||
for (auto& controller : channel_controllers_) {
|
||||
controller = std::make_unique<MonoInputVolumeController>(
|
||||
config.clipped_level_min, min_input_volume_,
|
||||
@ -400,7 +413,6 @@ InputVolumeController::InputVolumeController(int num_capture_channels,
|
||||
InputVolumeController::~InputVolumeController() {}
|
||||
|
||||
void InputVolumeController::Initialize() {
|
||||
RTC_DLOG(LS_INFO) << "InputVolumeController::Initialize";
|
||||
for (auto& controller : channel_controllers_) {
|
||||
controller->Initialize();
|
||||
}
|
||||
@ -430,10 +442,10 @@ void InputVolumeController::AnalyzePreProcess(const AudioBuffer& audio_buffer) {
|
||||
// Check for clipped samples. We do this in the preprocessing phase in order
|
||||
// to catch clipped echo as well.
|
||||
//
|
||||
// If we find a sufficiently clipped frame, drop the current microphone level
|
||||
// and enforce a new maximum level, dropped the same amount from the current
|
||||
// maximum. This harsh treatment is an effort to avoid repeated clipped echo
|
||||
// events.
|
||||
// If we find a sufficiently clipped frame, drop the current microphone
|
||||
// input volume and enforce a new maximum input volume, dropped the same
|
||||
// amount from the current maximum. This harsh treatment is an effort to avoid
|
||||
// repeated clipped echo events.
|
||||
float clipped_ratio =
|
||||
ComputeClippedRatio(audio, num_capture_channels_, samples_per_channel);
|
||||
clipping_rate_log_ = std::max(clipped_ratio, clipping_rate_log_);
|
||||
@ -457,7 +469,8 @@ void InputVolumeController::AnalyzePreProcess(const AudioBuffer& audio_buffer) {
|
||||
for (int channel = 0; channel < num_capture_channels_; ++channel) {
|
||||
const auto step = clipping_predictor_->EstimateClippedLevelStep(
|
||||
channel, recommended_input_volume_, clipped_level_step_,
|
||||
channel_controllers_[channel]->clipped_level_min(), kMaxMicLevel);
|
||||
channel_controllers_[channel]->min_input_volume_after_clipping(),
|
||||
kMaxInputVolume);
|
||||
if (step.has_value()) {
|
||||
predicted_step = std::max(predicted_step, step.value());
|
||||
clipping_predicted = true;
|
||||
@ -466,14 +479,15 @@ void InputVolumeController::AnalyzePreProcess(const AudioBuffer& audio_buffer) {
|
||||
}
|
||||
|
||||
if (clipping_detected) {
|
||||
RTC_DLOG(LS_INFO) << "[agc] Clipping detected. clipped_ratio="
|
||||
<< clipped_ratio;
|
||||
RTC_DLOG(LS_INFO) << "[AGC2] Clipping detected (ratio: " << clipped_ratio
|
||||
<< ")";
|
||||
}
|
||||
|
||||
int step = clipped_level_step_;
|
||||
if (clipping_predicted) {
|
||||
predicted_step = std::max(predicted_step, clipped_level_step_);
|
||||
RTC_DLOG(LS_INFO) << "[agc] Clipping predicted. step=" << predicted_step;
|
||||
RTC_DLOG(LS_INFO) << "[AGC2] Clipping predicted (volume down step: "
|
||||
<< predicted_step << ")";
|
||||
if (use_clipping_predictor_step_) {
|
||||
step = predicted_step;
|
||||
}
|
||||
@ -501,15 +515,15 @@ void InputVolumeController::Process(float speech_probability,
|
||||
return;
|
||||
}
|
||||
|
||||
absl::optional<int> rms_error_dbfs;
|
||||
absl::optional<int> rms_error_db;
|
||||
if (speech_level_dbfs.has_value()) {
|
||||
// Compute the error for all frames (both speech and non-speech frames).
|
||||
rms_error_dbfs = GetSpeechLevelErrorDb(
|
||||
rms_error_db = GetSpeechLevelRmsErrorDb(
|
||||
*speech_level_dbfs, target_range_min_dbfs_, target_range_max_dbfs_);
|
||||
}
|
||||
|
||||
for (auto& controller : channel_controllers_) {
|
||||
controller->Process(rms_error_dbfs, speech_probability);
|
||||
controller->Process(rms_error_db, speech_probability);
|
||||
}
|
||||
|
||||
AggregateChannelLevels();
|
||||
@ -524,9 +538,9 @@ void InputVolumeController::HandleCaptureOutputUsedChange(
|
||||
capture_output_used_ = capture_output_used;
|
||||
}
|
||||
|
||||
void InputVolumeController::set_stream_analog_level(int level) {
|
||||
void InputVolumeController::set_stream_analog_level(int input_volume) {
|
||||
for (auto& controller : channel_controllers_) {
|
||||
controller->set_stream_analog_level(level);
|
||||
controller->set_stream_analog_level(input_volume);
|
||||
}
|
||||
|
||||
AggregateChannelLevels();
|
||||
@ -537,13 +551,14 @@ void InputVolumeController::AggregateChannelLevels() {
|
||||
channel_controllers_[0]->recommended_analog_level();
|
||||
channel_controlling_gain_ = 0;
|
||||
for (size_t ch = 1; ch < channel_controllers_.size(); ++ch) {
|
||||
int level = channel_controllers_[ch]->recommended_analog_level();
|
||||
if (level < new_recommended_input_volume) {
|
||||
new_recommended_input_volume = level;
|
||||
int input_volume = channel_controllers_[ch]->recommended_analog_level();
|
||||
if (input_volume < new_recommended_input_volume) {
|
||||
new_recommended_input_volume = input_volume;
|
||||
channel_controlling_gain_ = static_cast<int>(ch);
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce the minimum input volume when a recommendation is made.
|
||||
if (new_recommended_input_volume > 0) {
|
||||
new_recommended_input_volume =
|
||||
std::max(new_recommended_input_volume, min_input_volume_);
|
||||
|
||||
@ -126,16 +126,13 @@ class InputVolumeController final {
|
||||
private:
|
||||
friend class InputVolumeControllerTestHelper;
|
||||
|
||||
FRIEND_TEST_ALL_PREFIXES(InputVolumeControllerTest, MinInputVolumeDefault);
|
||||
FRIEND_TEST_ALL_PREFIXES(InputVolumeControllerTest, MinInputVolumeDisabled);
|
||||
FRIEND_TEST_ALL_PREFIXES(InputVolumeControllerTest,
|
||||
AgcMinMicLevelExperimentDefault);
|
||||
MinInputVolumeOutOfRangeAbove);
|
||||
FRIEND_TEST_ALL_PREFIXES(InputVolumeControllerTest,
|
||||
AgcMinMicLevelExperimentDisabled);
|
||||
FRIEND_TEST_ALL_PREFIXES(InputVolumeControllerTest,
|
||||
AgcMinMicLevelExperimentOutOfRangeAbove);
|
||||
FRIEND_TEST_ALL_PREFIXES(InputVolumeControllerTest,
|
||||
AgcMinMicLevelExperimentOutOfRangeBelow);
|
||||
FRIEND_TEST_ALL_PREFIXES(InputVolumeControllerTest,
|
||||
AgcMinMicLevelExperimentEnabled50);
|
||||
MinInputVolumeOutOfRangeBelow);
|
||||
FRIEND_TEST_ALL_PREFIXES(InputVolumeControllerTest, MinInputVolumeEnabled50);
|
||||
FRIEND_TEST_ALL_PREFIXES(InputVolumeControllerParametrizedTest,
|
||||
ClippingParametersVerified);
|
||||
|
||||
@ -184,8 +181,8 @@ class InputVolumeController final {
|
||||
// convention.
|
||||
class MonoInputVolumeController {
|
||||
public:
|
||||
MonoInputVolumeController(int clipped_level_min,
|
||||
int min_mic_level,
|
||||
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);
|
||||
@ -198,7 +195,9 @@ class MonoInputVolumeController {
|
||||
void HandleCaptureOutputUsedChange(bool capture_output_used);
|
||||
|
||||
// Sets the current input volume.
|
||||
void set_stream_analog_level(int level) { recommended_input_volume_ = level; }
|
||||
void set_stream_analog_level(int input_volume) {
|
||||
recommended_input_volume_ = input_volume;
|
||||
}
|
||||
|
||||
// Lowers the recommended input volume in response to clipping based on the
|
||||
// suggested reduction `clipped_level_step`. Must be called after
|
||||
@ -217,7 +216,9 @@ class MonoInputVolumeController {
|
||||
|
||||
void ActivateLogging() { log_to_histograms_ = true; }
|
||||
|
||||
int clipped_level_min() const { return clipped_level_min_; }
|
||||
int min_input_volume_after_clipping() const {
|
||||
return min_input_volume_after_clipping_;
|
||||
}
|
||||
|
||||
// Only used for testing.
|
||||
int min_input_volume() const { return min_input_volume_; }
|
||||
@ -225,7 +226,7 @@ class MonoInputVolumeController {
|
||||
private:
|
||||
// Sets a new input volume, after first checking that it hasn't been updated
|
||||
// by the user, in which case no action is taken.
|
||||
void SetLevel(int new_level);
|
||||
void SetInputVolume(int new_volume);
|
||||
|
||||
// Sets the maximum input volume that the input volume controller is allowed
|
||||
// to apply. The volume must be at least `kClippedLevelMin`.
|
||||
@ -239,9 +240,11 @@ class MonoInputVolumeController {
|
||||
void UpdateInputVolume(int rms_error_dbfs);
|
||||
|
||||
const int min_input_volume_;
|
||||
const int min_input_volume_after_clipping_;
|
||||
int max_input_volume_;
|
||||
|
||||
int level_ = 0;
|
||||
int max_level_;
|
||||
// Last recommended input volume.
|
||||
int input_volume_ = 0;
|
||||
|
||||
bool capture_output_used_ = true;
|
||||
bool check_volume_on_next_process_ = true;
|
||||
@ -257,8 +260,6 @@ class MonoInputVolumeController {
|
||||
|
||||
bool log_to_histograms_ = false;
|
||||
|
||||
const int clipped_level_min_;
|
||||
|
||||
// Counters for frames and speech frames since the last update in the
|
||||
// recommended input volume.
|
||||
const int update_input_volume_wait_frames_;
|
||||
|
||||
@ -382,8 +382,7 @@ class InputVolumeControllerParametrizedTest
|
||||
InputVolumeControllerParametrizedTest()
|
||||
: field_trials_(GetAgcMinInputVolumeFieldTrial(GetParam())) {}
|
||||
|
||||
bool IsMinMicLevelOverridden() const { return GetParam().has_value(); }
|
||||
int GetMinMicLevel() const { return GetParam().value_or(kMinMicLevel); }
|
||||
int GetMinInputVolume() const { return GetParam().value_or(kMinMicLevel); }
|
||||
|
||||
private:
|
||||
test::ScopedFieldTrials field_trials_;
|
||||
@ -416,10 +415,10 @@ TEST_P(InputVolumeControllerParametrizedTest, MicVolumeResponseToRmsError) {
|
||||
|
||||
// Above the digital gain's window; volume should be increased.
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, -29.0f);
|
||||
EXPECT_EQ(128, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 128);
|
||||
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, -38.0f);
|
||||
EXPECT_EQ(156, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 156);
|
||||
|
||||
// Inside the digital gain's window; no change of volume.
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, -23.0f);
|
||||
@ -427,13 +426,13 @@ TEST_P(InputVolumeControllerParametrizedTest, MicVolumeResponseToRmsError) {
|
||||
|
||||
// Below the digial gain's window; volume should be decreased.
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, -17.0f);
|
||||
EXPECT_EQ(155, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 155);
|
||||
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, -17.0f);
|
||||
EXPECT_EQ(151, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 151);
|
||||
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, -9.0f);
|
||||
EXPECT_EQ(119, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 119);
|
||||
}
|
||||
|
||||
TEST_P(InputVolumeControllerParametrizedTest, MicVolumeIsLimited) {
|
||||
@ -443,42 +442,42 @@ TEST_P(InputVolumeControllerParametrizedTest, MicVolumeIsLimited) {
|
||||
|
||||
// Maximum upwards change is limited.
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, -48.0f);
|
||||
EXPECT_EQ(183, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 183);
|
||||
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, -48.0f);
|
||||
EXPECT_EQ(243, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 243);
|
||||
|
||||
// Won't go higher than the maximum.
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, -48.0f);
|
||||
EXPECT_EQ(255, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 255);
|
||||
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, -17.0f);
|
||||
EXPECT_EQ(254, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 254);
|
||||
|
||||
// Maximum downwards change is limited.
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, 22.0f);
|
||||
EXPECT_EQ(194, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 194);
|
||||
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, 22.0f);
|
||||
EXPECT_EQ(137, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 137);
|
||||
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, 22.0f);
|
||||
EXPECT_EQ(88, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 88);
|
||||
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, 22.0f);
|
||||
EXPECT_EQ(54, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 54);
|
||||
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, 22.0f);
|
||||
EXPECT_EQ(33, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 33);
|
||||
|
||||
// Won't go lower than the minimum.
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, 22.0f);
|
||||
EXPECT_EQ(std::max(18, GetMinMicLevel()),
|
||||
helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(),
|
||||
std::max(18, GetMinInputVolume()));
|
||||
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, 22.0f);
|
||||
EXPECT_EQ(std::max(12, GetMinMicLevel()),
|
||||
helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(),
|
||||
std::max(12, GetMinInputVolume()));
|
||||
}
|
||||
|
||||
TEST_P(InputVolumeControllerParametrizedTest, NoActionWhileMuted) {
|
||||
@ -504,7 +503,7 @@ TEST_P(InputVolumeControllerParametrizedTest,
|
||||
|
||||
// SetMicVolume should not be called.
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, kSpeechLevel);
|
||||
EXPECT_EQ(127, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 127);
|
||||
}
|
||||
|
||||
TEST_P(InputVolumeControllerParametrizedTest, UnmutingRaisesTooLowVolume) {
|
||||
@ -519,7 +518,7 @@ TEST_P(InputVolumeControllerParametrizedTest, UnmutingRaisesTooLowVolume) {
|
||||
helper.manager.set_stream_analog_level(kInputVolume);
|
||||
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, kSpeechLevel);
|
||||
EXPECT_EQ(GetMinMicLevel(), helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), GetMinInputVolume());
|
||||
}
|
||||
|
||||
TEST_P(InputVolumeControllerParametrizedTest,
|
||||
@ -533,16 +532,16 @@ TEST_P(InputVolumeControllerParametrizedTest,
|
||||
ASSERT_NE(helper.manager.recommended_analog_level(), 154);
|
||||
helper.manager.set_stream_analog_level(154);
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, -29.0f);
|
||||
EXPECT_EQ(154, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 154);
|
||||
|
||||
// Do the same thing, except downwards now.
|
||||
helper.manager.set_stream_analog_level(100);
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, -17.0f);
|
||||
EXPECT_EQ(100, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 100);
|
||||
|
||||
// And finally verify the AGC continues working without a manual change.
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, -17.0f);
|
||||
EXPECT_EQ(99, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 99);
|
||||
}
|
||||
|
||||
TEST_P(InputVolumeControllerParametrizedTest,
|
||||
@ -554,21 +553,21 @@ TEST_P(InputVolumeControllerParametrizedTest,
|
||||
// Force the mic up to max volume. Takes a few steps due to the residual
|
||||
// gain limitation.
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, -48.0f);
|
||||
EXPECT_EQ(183, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 183);
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, -48.0f);
|
||||
EXPECT_EQ(243, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 243);
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, -48.0f);
|
||||
EXPECT_EQ(255, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 255);
|
||||
|
||||
// Manual change does not result in SetMicVolume call.
|
||||
helper.manager.set_stream_analog_level(50);
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, -17.0f);
|
||||
EXPECT_EQ(50, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 50);
|
||||
|
||||
// Continues working as usual afterwards.
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, -38.0f);
|
||||
|
||||
EXPECT_EQ(65, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 65);
|
||||
}
|
||||
|
||||
// Checks that the minimum input volume is enforced during the upward adjustment
|
||||
@ -585,16 +584,16 @@ TEST_P(InputVolumeControllerParametrizedTest,
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, -17.0f);
|
||||
|
||||
// Trigger an upward adjustment of the input volume.
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), GetMinMicLevel());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), GetMinInputVolume());
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, -29.0f);
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), GetMinMicLevel());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), GetMinInputVolume());
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, -48.0f);
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), GetMinMicLevel());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), GetMinInputVolume());
|
||||
|
||||
// After a number of consistently low speech level observations, the input
|
||||
// volume is eventually raised above the minimum.
|
||||
helper.CallProcess(/*num_calls=*/10, kHighSpeechProbability, -38.0f);
|
||||
EXPECT_GT(helper.manager.recommended_analog_level(), GetMinMicLevel());
|
||||
EXPECT_GT(helper.manager.recommended_analog_level(), GetMinInputVolume());
|
||||
}
|
||||
|
||||
// Checks that, when the min mic level override is specified, AGC immediately
|
||||
@ -610,7 +609,7 @@ TEST_P(InputVolumeControllerParametrizedTest,
|
||||
// AGC won't take any action.
|
||||
helper.manager.set_stream_analog_level(1);
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, -17.0f);
|
||||
EXPECT_EQ(GetMinMicLevel(), helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(GetMinInputVolume(), helper.manager.recommended_analog_level());
|
||||
}
|
||||
|
||||
TEST_P(InputVolumeControllerParametrizedTest, NoClippingHasNoImpact) {
|
||||
@ -619,7 +618,7 @@ TEST_P(InputVolumeControllerParametrizedTest, NoClippingHasNoImpact) {
|
||||
kSpeechLevel);
|
||||
|
||||
helper.CallPreProc(/*num_calls=*/100, /*clipped_ratio=*/0);
|
||||
EXPECT_EQ(128, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 128);
|
||||
}
|
||||
|
||||
TEST_P(InputVolumeControllerParametrizedTest,
|
||||
@ -629,7 +628,7 @@ TEST_P(InputVolumeControllerParametrizedTest,
|
||||
kSpeechLevel);
|
||||
|
||||
helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/0.099);
|
||||
EXPECT_EQ(128, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 128);
|
||||
}
|
||||
|
||||
TEST_P(InputVolumeControllerParametrizedTest, ClippingLowersVolume) {
|
||||
@ -638,7 +637,7 @@ TEST_P(InputVolumeControllerParametrizedTest, ClippingLowersVolume) {
|
||||
kSpeechLevel);
|
||||
|
||||
helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/0.2);
|
||||
EXPECT_EQ(240, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 240);
|
||||
}
|
||||
|
||||
TEST_P(InputVolumeControllerParametrizedTest,
|
||||
@ -648,14 +647,14 @@ TEST_P(InputVolumeControllerParametrizedTest,
|
||||
kSpeechLevel);
|
||||
|
||||
helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold);
|
||||
EXPECT_EQ(240, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 240);
|
||||
|
||||
helper.CallPreProc(/*num_calls=*/300,
|
||||
/*clipped_ratio=*/kAboveClippedThreshold);
|
||||
EXPECT_EQ(240, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 240);
|
||||
|
||||
helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold);
|
||||
EXPECT_EQ(225, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 225);
|
||||
}
|
||||
|
||||
TEST_P(InputVolumeControllerParametrizedTest, ClippingLoweringIsLimited) {
|
||||
@ -664,11 +663,11 @@ TEST_P(InputVolumeControllerParametrizedTest, ClippingLoweringIsLimited) {
|
||||
kSpeechLevel);
|
||||
|
||||
helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold);
|
||||
EXPECT_EQ(kClippedMin, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), kClippedMin);
|
||||
|
||||
helper.CallPreProc(/*num_calls=*/1000,
|
||||
/*clipped_ratio=*/kAboveClippedThreshold);
|
||||
EXPECT_EQ(kClippedMin, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), kClippedMin);
|
||||
}
|
||||
|
||||
TEST_P(InputVolumeControllerParametrizedTest,
|
||||
@ -678,10 +677,10 @@ TEST_P(InputVolumeControllerParametrizedTest,
|
||||
kSpeechLevel);
|
||||
|
||||
helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold);
|
||||
EXPECT_EQ(240, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 240);
|
||||
|
||||
helper.CallProcess(/*num_calls=*/10, kHighSpeechProbability, -48.0f);
|
||||
EXPECT_EQ(240, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 240);
|
||||
}
|
||||
|
||||
TEST_P(InputVolumeControllerParametrizedTest,
|
||||
@ -691,12 +690,12 @@ TEST_P(InputVolumeControllerParametrizedTest,
|
||||
kSpeechLevel);
|
||||
|
||||
helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold);
|
||||
EXPECT_EQ(185, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 185);
|
||||
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, -58.0f);
|
||||
EXPECT_EQ(240, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 240);
|
||||
helper.CallProcess(/*num_calls=*/10, kHighSpeechProbability, -58.0f);
|
||||
EXPECT_EQ(240, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 240);
|
||||
}
|
||||
|
||||
TEST_P(InputVolumeControllerParametrizedTest, UserCanRaiseVolumeAfterClipping) {
|
||||
@ -705,22 +704,22 @@ TEST_P(InputVolumeControllerParametrizedTest, UserCanRaiseVolumeAfterClipping) {
|
||||
kSpeechLevel);
|
||||
|
||||
helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold);
|
||||
EXPECT_EQ(210, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 210);
|
||||
|
||||
// User changed the volume.
|
||||
helper.manager.set_stream_analog_level(250);
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, -32.0f);
|
||||
EXPECT_EQ(250, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 250);
|
||||
|
||||
// Move down...
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, -8.0f);
|
||||
EXPECT_EQ(210, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 210);
|
||||
// And back up to the new max established by the user.
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, -58.0f);
|
||||
EXPECT_EQ(250, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 250);
|
||||
// Will not move above new maximum.
|
||||
helper.CallProcess(/*num_calls=*/1, kHighSpeechProbability, -48.0f);
|
||||
EXPECT_EQ(250, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 250);
|
||||
}
|
||||
|
||||
TEST_P(InputVolumeControllerParametrizedTest,
|
||||
@ -741,7 +740,7 @@ TEST_P(InputVolumeControllerParametrizedTest, TakesNoActionOnZeroMicVolume) {
|
||||
|
||||
helper.manager.set_stream_analog_level(0);
|
||||
helper.CallProcess(/*num_calls=*/10, kHighSpeechProbability, -48.0f);
|
||||
EXPECT_EQ(0, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 0);
|
||||
}
|
||||
|
||||
TEST_P(InputVolumeControllerParametrizedTest, ClippingDetectionLowersVolume) {
|
||||
@ -749,11 +748,11 @@ TEST_P(InputVolumeControllerParametrizedTest, ClippingDetectionLowersVolume) {
|
||||
helper.CallAgcSequence(/*applied_input_volume=*/255, kHighSpeechProbability,
|
||||
kSpeechLevel);
|
||||
|
||||
EXPECT_EQ(255, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 255);
|
||||
helper.CallPreProcForChangingAudio(/*num_calls=*/100, /*peak_ratio=*/0.99f);
|
||||
EXPECT_EQ(255, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 255);
|
||||
helper.CallPreProcForChangingAudio(/*num_calls=*/100, /*peak_ratio=*/1.0f);
|
||||
EXPECT_EQ(240, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 240);
|
||||
}
|
||||
|
||||
TEST_P(InputVolumeControllerParametrizedTest,
|
||||
@ -763,20 +762,20 @@ TEST_P(InputVolumeControllerParametrizedTest,
|
||||
kSpeechLevel);
|
||||
|
||||
EXPECT_FALSE(helper.manager.clipping_predictor_enabled());
|
||||
EXPECT_EQ(255, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 255);
|
||||
helper.CallPreProcForChangingAudio(/*num_calls=*/100, /*peak_ratio=*/0.99f);
|
||||
EXPECT_EQ(255, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 255);
|
||||
helper.CallPreProcForChangingAudio(/*num_calls=*/100, /*peak_ratio=*/0.99f);
|
||||
EXPECT_EQ(255, helper.manager.recommended_analog_level());
|
||||
EXPECT_EQ(helper.manager.recommended_analog_level(), 255);
|
||||
}
|
||||
|
||||
TEST(InputVolumeControllerTest, AgcMinMicLevelExperimentDefault) {
|
||||
TEST(InputVolumeControllerTest, MinInputVolumeDefault) {
|
||||
std::unique_ptr<InputVolumeController> manager = CreateInputVolumeController(
|
||||
kClippedLevelStep, kClippedRatioThreshold, kClippedWaitFrames);
|
||||
EXPECT_EQ(manager->channel_controllers_[0]->min_input_volume(), kMinMicLevel);
|
||||
}
|
||||
|
||||
TEST(InputVolumeControllerTest, AgcMinMicLevelExperimentDisabled) {
|
||||
TEST(InputVolumeControllerTest, MinInputVolumeDisabled) {
|
||||
for (const std::string& field_trial_suffix : {"", "_20220210"}) {
|
||||
test::ScopedFieldTrials field_trial(
|
||||
GetAgcMinInputVolumeFieldTrial("Disabled" + field_trial_suffix));
|
||||
@ -791,7 +790,7 @@ TEST(InputVolumeControllerTest, AgcMinMicLevelExperimentDisabled) {
|
||||
|
||||
// Checks that a field-trial parameter outside of the valid range [0,255] is
|
||||
// ignored.
|
||||
TEST(InputVolumeControllerTest, AgcMinMicLevelExperimentOutOfRangeAbove) {
|
||||
TEST(InputVolumeControllerTest, MinInputVolumeOutOfRangeAbove) {
|
||||
test::ScopedFieldTrials field_trial(
|
||||
GetAgcMinInputVolumeFieldTrial("Enabled-256"));
|
||||
std::unique_ptr<InputVolumeController> manager = CreateInputVolumeController(
|
||||
@ -801,7 +800,7 @@ TEST(InputVolumeControllerTest, AgcMinMicLevelExperimentOutOfRangeAbove) {
|
||||
|
||||
// Checks that a field-trial parameter outside of the valid range [0,255] is
|
||||
// ignored.
|
||||
TEST(InputVolumeControllerTest, AgcMinMicLevelExperimentOutOfRangeBelow) {
|
||||
TEST(InputVolumeControllerTest, MinInputVolumeOutOfRangeBelow) {
|
||||
test::ScopedFieldTrials field_trial(
|
||||
GetAgcMinInputVolumeFieldTrial("Enabled--1"));
|
||||
std::unique_ptr<InputVolumeController> manager = CreateInputVolumeController(
|
||||
@ -812,27 +811,26 @@ TEST(InputVolumeControllerTest, AgcMinMicLevelExperimentOutOfRangeBelow) {
|
||||
// Verifies that a valid experiment changes the minimum microphone level. The
|
||||
// start volume is larger than the min level and should therefore not be
|
||||
// changed.
|
||||
TEST(InputVolumeControllerTest, AgcMinMicLevelExperimentEnabled50) {
|
||||
constexpr int kMinMicLevelOverride = 50;
|
||||
TEST(InputVolumeControllerTest, MinInputVolumeEnabled50) {
|
||||
constexpr int kMinInputVolume = 50;
|
||||
for (const std::string& field_trial_suffix : {"", "_20220210"}) {
|
||||
SCOPED_TRACE(field_trial_suffix);
|
||||
test::ScopedFieldTrials field_trial(GetAgcMinInputVolumeFieldTrialEnabled(
|
||||
kMinMicLevelOverride, field_trial_suffix));
|
||||
kMinInputVolume, field_trial_suffix));
|
||||
std::unique_ptr<InputVolumeController> manager =
|
||||
CreateInputVolumeController(kClippedLevelStep, kClippedRatioThreshold,
|
||||
kClippedWaitFrames);
|
||||
|
||||
EXPECT_EQ(manager->channel_controllers_[0]->min_input_volume(),
|
||||
kMinMicLevelOverride);
|
||||
kMinInputVolume);
|
||||
}
|
||||
}
|
||||
|
||||
// Checks that, when the "WebRTC-Audio-AgcMinMicLevelExperiment" field trial is
|
||||
// Checks that, when the "WebRTC-Audio-Agc2-MinInputVolume" field trial is
|
||||
// specified with a valid value, the mic level never gets lowered beyond the
|
||||
// override value in the presence of clipping.
|
||||
TEST(InputVolumeControllerTest,
|
||||
AgcMinMicLevelExperimentCheckMinLevelWithClipping) {
|
||||
constexpr int kMinMicLevelOverride = 250;
|
||||
TEST(InputVolumeControllerTest, MinInputVolumeCheckMinLevelWithClipping) {
|
||||
constexpr int kMinInputVolume = 250;
|
||||
|
||||
// Create and initialize two AGCs by specifying and leaving unspecified the
|
||||
// relevant field trial.
|
||||
@ -848,7 +846,7 @@ TEST(InputVolumeControllerTest,
|
||||
std::unique_ptr<InputVolumeController> manager_with_override;
|
||||
{
|
||||
test::ScopedFieldTrials field_trial(
|
||||
GetAgcMinInputVolumeFieldTrialEnabled(kMinMicLevelOverride));
|
||||
GetAgcMinInputVolumeFieldTrialEnabled(kMinInputVolume));
|
||||
manager_with_override = factory();
|
||||
}
|
||||
|
||||
@ -877,19 +875,18 @@ TEST(InputVolumeControllerTest,
|
||||
manager->recommended_analog_level());
|
||||
// Check that the gain selected by `manager_with_override` equals the
|
||||
// minimum value overridden via field trial.
|
||||
EXPECT_EQ(manager_with_override->recommended_analog_level(),
|
||||
kMinMicLevelOverride);
|
||||
EXPECT_EQ(manager_with_override->recommended_analog_level(), kMinInputVolume);
|
||||
}
|
||||
|
||||
// Checks that, when the "WebRTC-Audio-AgcMinMicLevelExperiment" field trial is
|
||||
// Checks that, when the "WebRTC-Audio-Agc2-MinInputVolume" field trial is
|
||||
// specified with a valid value, the mic level never gets lowered beyond the
|
||||
// override value in the presence of clipping when RMS error is not empty.
|
||||
// TODO(webrtc:7494): Revisit the test after moving the number of update wait
|
||||
// frames to APM config. The test passes but internally the gain update timing
|
||||
// differs.
|
||||
TEST(InputVolumeControllerTest,
|
||||
AgcMinMicLevelExperimentCheckMinLevelWithClippingWithRmsError) {
|
||||
constexpr int kMinMicLevelOverride = 250;
|
||||
MinInputVolumeCheckMinLevelWithClippingWithRmsError) {
|
||||
constexpr int kMinInputVolume = 250;
|
||||
|
||||
// Create and initialize two AGCs by specifying and leaving unspecified the
|
||||
// relevant field trial.
|
||||
@ -905,7 +902,7 @@ TEST(InputVolumeControllerTest,
|
||||
std::unique_ptr<InputVolumeController> manager_with_override;
|
||||
{
|
||||
test::ScopedFieldTrials field_trial(
|
||||
GetAgcMinInputVolumeFieldTrialEnabled(kMinMicLevelOverride));
|
||||
GetAgcMinInputVolumeFieldTrialEnabled(kMinInputVolume));
|
||||
manager_with_override = factory();
|
||||
}
|
||||
|
||||
@ -933,16 +930,14 @@ TEST(InputVolumeControllerTest,
|
||||
manager->recommended_analog_level());
|
||||
// Check that the gain selected by `manager_with_override` equals the minimum
|
||||
// value overridden via field trial.
|
||||
EXPECT_EQ(manager_with_override->recommended_analog_level(),
|
||||
kMinMicLevelOverride);
|
||||
EXPECT_EQ(manager_with_override->recommended_analog_level(), kMinInputVolume);
|
||||
}
|
||||
|
||||
// Checks that, when the "WebRTC-Audio-AgcMinMicLevelExperiment" field trial is
|
||||
// Checks that, when the "WebRTC-Audio-Agc2-MinInputVolume" field trial is
|
||||
// specified with a value lower than the `clipped_level_min`, the behavior of
|
||||
// the analog gain controller is the same as that obtained when the field trial
|
||||
// is not specified.
|
||||
TEST(InputVolumeControllerTest,
|
||||
AgcMinMicLevelExperimentCompareMicLevelWithClipping) {
|
||||
TEST(InputVolumeControllerTest, MinInputVolumeCompareMicLevelWithClipping) {
|
||||
// Create and initialize two AGCs by specifying and leaving unspecified the
|
||||
// relevant field trial.
|
||||
const auto factory = []() {
|
||||
@ -961,12 +956,12 @@ TEST(InputVolumeControllerTest,
|
||||
std::unique_ptr<InputVolumeController> manager = factory();
|
||||
std::unique_ptr<InputVolumeController> manager_with_override;
|
||||
{
|
||||
constexpr int kMinMicLevelOverride = 20;
|
||||
constexpr int kMinInputVolume = 20;
|
||||
static_assert(kDefaultInputVolumeControllerConfig.clipped_level_min >=
|
||||
kMinMicLevelOverride,
|
||||
kMinInputVolume,
|
||||
"Use a lower override value.");
|
||||
test::ScopedFieldTrials field_trial(
|
||||
GetAgcMinInputVolumeFieldTrialEnabled(kMinMicLevelOverride));
|
||||
GetAgcMinInputVolumeFieldTrialEnabled(kMinInputVolume));
|
||||
manager_with_override = factory();
|
||||
}
|
||||
|
||||
@ -999,7 +994,7 @@ TEST(InputVolumeControllerTest,
|
||||
kDefaultInputVolumeControllerConfig.clipped_level_min);
|
||||
}
|
||||
|
||||
// Checks that, when the "WebRTC-Audio-AgcMinMicLevelExperiment" field trial is
|
||||
// Checks that, when the "WebRTC-Audio-Agc2-MinInputVolume" field trial is
|
||||
// specified with a value lower than the `clipped_level_min`, the behavior of
|
||||
// the analog gain controller is the same as that obtained when the field trial
|
||||
// is not specified.
|
||||
@ -1007,7 +1002,7 @@ TEST(InputVolumeControllerTest,
|
||||
// frames to APM config. The test passes but internally the gain update timing
|
||||
// differs.
|
||||
TEST(InputVolumeControllerTest,
|
||||
AgcMinMicLevelExperimentCompareMicLevelWithClippingWithRmsError) {
|
||||
MinInputVolumeCompareMicLevelWithClippingWithRmsError) {
|
||||
// Create and initialize two AGCs by specifying and leaving unspecified the
|
||||
// relevant field trial.
|
||||
const auto factory = []() {
|
||||
@ -1026,12 +1021,12 @@ TEST(InputVolumeControllerTest,
|
||||
std::unique_ptr<InputVolumeController> manager = factory();
|
||||
std::unique_ptr<InputVolumeController> manager_with_override;
|
||||
{
|
||||
constexpr int kMinMicLevelOverride = 20;
|
||||
constexpr int kMinInputVolume = 20;
|
||||
static_assert(kDefaultInputVolumeControllerConfig.clipped_level_min >=
|
||||
kMinMicLevelOverride,
|
||||
kMinInputVolume,
|
||||
"Use a lower override value.");
|
||||
test::ScopedFieldTrials field_trial(
|
||||
GetAgcMinInputVolumeFieldTrialEnabled(kMinMicLevelOverride));
|
||||
GetAgcMinInputVolumeFieldTrialEnabled(kMinInputVolume));
|
||||
manager_with_override = factory();
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user