diff --git a/modules/audio_processing/agc/agc_manager_direct.cc b/modules/audio_processing/agc/agc_manager_direct.cc index df6f48a993..d932303247 100644 --- a/modules/audio_processing/agc/agc_manager_direct.cc +++ b/modules/audio_processing/agc/agc_manager_direct.cc @@ -53,6 +53,17 @@ constexpr int kSurplusCompressionGain = 6; // frames). constexpr int kClippingPredictorEvaluatorHistorySize = 500; +// Target speech level (dBFs) and speech probability threshold used to compute +// the RMS error override in `GetSpeechLevelErrorDb()`. These are only used for +// computing the error override and they are not passed to `agc_`. +// TODO(webrtc:7494): Move these to a config and pass in the ctor. +constexpr float kOverrideTargetSpeechLevelDbfs = -18.0f; +constexpr float kOverrideSpeechProbabilitySilenceThreshold = 0.5f; +// The minimum number of frames between `UpdateGain()` calls. +// TODO(webrtc:7494): Move this to a config and pass in the ctor with +// kOverrideWaitFrames = 100. Default value zero needed for the unit tests. +constexpr int kOverrideWaitFrames = 0; + using AnalogAgcConfig = AudioProcessing::Config::GainController1::AnalogGainController; @@ -173,6 +184,27 @@ void LogClippingMetrics(int clipping_rate) { /*bucket_count=*/50); } +// Computes the speech level error in dB. `speech_level_dbfs` is required to be +// in the range [-90.0f, 30.0f] and `speech_probability` in the range +// [0.0f, 1.0f]. +int GetSpeechLevelErrorDb(float speech_level_dbfs, float speech_probability) { + constexpr float kMinSpeechLevelDbfs = -90.0f; + constexpr float kMaxSpeechLevelDbfs = 30.0f; + RTC_DCHECK_GE(speech_level_dbfs, kMinSpeechLevelDbfs); + RTC_DCHECK_LE(speech_level_dbfs, kMaxSpeechLevelDbfs); + RTC_DCHECK_GE(speech_probability, 0.0f); + RTC_DCHECK_LE(speech_probability, 1.0f); + + if (speech_probability < kOverrideSpeechProbabilitySilenceThreshold) { + return 0; + } + + const float speech_level = rtc::SafeClamp( + speech_level_dbfs, kMinSpeechLevelDbfs, kMaxSpeechLevelDbfs); + + return std::round(kOverrideTargetSpeechLevelDbfs - speech_level); +} + } // namespace MonoAgc::MonoAgc(ApmDataDumper* data_dumper, @@ -201,9 +233,12 @@ void MonoAgc::Initialize() { compression_accumulator_ = compression_; capture_output_used_ = true; check_volume_on_next_process_ = true; + frames_since_update_gain_ = 0; + is_first_frame_ = true; } -void MonoAgc::Process(rtc::ArrayView audio) { +void MonoAgc::Process(rtc::ArrayView audio, + absl::optional rms_error_override) { new_compression_to_set_ = absl::nullopt; if (check_volume_on_next_process_) { @@ -215,15 +250,33 @@ void MonoAgc::Process(rtc::ArrayView audio) { agc_->Process(audio); - // Update gain if `agc_` has an RMS error estimate ready. + // Always check if `agc_` has a new error available. If yes, `agc_` gets + // reset. + // TODO(webrtc:7494) Replace the `agc_` call `GetRmsErrorDb()` with `Reset()` + // if an error override is used. int rms_error = 0; - if (agc_->GetRmsErrorDb(&rms_error)) { + bool update_gain = agc_->GetRmsErrorDb(&rms_error); + if (rms_error_override.has_value()) { + if (is_first_frame_ || frames_since_update_gain_ < kOverrideWaitFrames) { + update_gain = false; + } else { + rms_error = *rms_error_override; + update_gain = true; + } + } + + if (update_gain) { UpdateGain(rms_error); } if (!disable_digital_adaptive_) { UpdateCompressor(); } + + is_first_frame_ = false; + if (frames_since_update_gain_ < kOverrideWaitFrames) { + ++frames_since_update_gain_; + } } void MonoAgc::HandleClipping(int clipped_level_step) { @@ -242,6 +295,8 @@ void MonoAgc::HandleClipping(int clipped_level_step) { SetLevel(std::max(clipped_level_min_, level_ - clipped_level_step)); // Reset the AGCs for all channels since the level has changed. agc_->Reset(); + frames_since_update_gain_ = 0; + is_first_frame_ = false; } } @@ -276,7 +331,8 @@ void MonoAgc::SetLevel(int new_level) { // was manually adjusted. The compressor will still provide some of the // desired gain change. agc_->Reset(); - + frames_since_update_gain_ = 0; + is_first_frame_ = false; return; } @@ -344,6 +400,8 @@ int MonoAgc::CheckVolumeAndReset() { agc_->Reset(); level_ = level; startup_ = false; + frames_since_update_gain_ = 0; + is_first_frame_ = true; return 0; } @@ -356,6 +414,11 @@ int MonoAgc::CheckVolumeAndReset() { void MonoAgc::UpdateGain(int rms_error_db) { int rms_error = rms_error_db; + // Always reset the counter regardless of whether the gain is changed + // or not. This matches with the bahvior of `agc_` where the histogram is + // reset every time an RMS error is successfully read. + frames_since_update_gain_ = 0; + // The compressor will always add at least kMinCompressionGain. In effect, // this adjusts our target gain upward by the same amount and rms_error // needs to reflect that. @@ -646,6 +709,13 @@ void AgcManagerDirect::AnalyzePreProcess(const AudioBuffer& audio_buffer) { } void AgcManagerDirect::Process(const AudioBuffer& audio_buffer) { + Process(audio_buffer, /*speech_probability=*/absl::nullopt, + /*speech_level_dbfs=*/absl::nullopt); +} + +void AgcManagerDirect::Process(const AudioBuffer& audio_buffer, + absl::optional speech_probability, + absl::optional speech_level_dbfs) { AggregateChannelLevels(); if (!capture_output_used_) { @@ -653,12 +723,18 @@ void AgcManagerDirect::Process(const AudioBuffer& audio_buffer) { } const size_t num_frames_per_band = audio_buffer.num_frames_per_band(); + absl::optional rms_error_override = absl::nullopt; + if (speech_probability.has_value() && speech_level_dbfs.has_value()) { + rms_error_override = + GetSpeechLevelErrorDb(*speech_level_dbfs, *speech_probability); + } for (size_t ch = 0; ch < channel_agcs_.size(); ++ch) { std::array audio_data; int16_t* audio_use = audio_data.data(); FloatS16ToS16(audio_buffer.split_bands_const_f(ch)[0], num_frames_per_band, audio_use); - channel_agcs_[ch]->Process({audio_use, num_frames_per_band}); + channel_agcs_[ch]->Process({audio_use, num_frames_per_band}, + rms_error_override); new_compressions_to_set_[ch] = channel_agcs_[ch]->new_compression(); } diff --git a/modules/audio_processing/agc/agc_manager_direct.h b/modules/audio_processing/agc/agc_manager_direct.h index 1197812798..0a9d32440e 100644 --- a/modules/audio_processing/agc/agc_manager_direct.h +++ b/modules/audio_processing/agc/agc_manager_direct.h @@ -69,8 +69,18 @@ class AgcManagerDirect final { // prediction (if enabled). Must be called after `set_stream_analog_level()`. void AnalyzePreProcess(const AudioBuffer& audio_buffer); - // Processes `audio`. Chooses a digital compression gain and the new input - // volume to recommend. Must be called after `AnalyzePreProcess()`. + // Processes `audio_buffer`. Chooses a digital compression gain and the new + // input volume to recommend. Must be called after `AnalyzePreProcess()`. If + // `speech_probability` (range [0.0f, 1.0f]) and `speech_level_dbfs` (range + // [-90.f, 30.0f]) are given, uses them to override the estimated RMS error. + // TODO(webrtc:7494): This signature is needed for testing purposes, unify + // the signatures when the clean-up is done. + void Process(const AudioBuffer& audio_buffer, + absl::optional speech_probability, + absl::optional speech_level_dbfs); + + // Processes `audio_buffer`. Chooses a digital compression gain and the new + // input volume to recommend. Must be called after `AnalyzePreProcess()`. void Process(const AudioBuffer& audio_buffer); // TODO(bugs.webrtc.org/7494): Return recommended input volume and remove @@ -125,6 +135,10 @@ class AgcManagerDirect final { UsedClippingPredictionsProduceLowerAnalogLevels); FRIEND_TEST_ALL_PREFIXES(AgcManagerDirectParametrizedTest, UnusedClippingPredictionsProduceEqualAnalogLevels); + FRIEND_TEST_ALL_PREFIXES(AgcManagerDirectParametrizedTest, + EmptyRmsErrorOverrideHasNoEffect); + FRIEND_TEST_ALL_PREFIXES(AgcManagerDirectParametrizedTest, + NonEmptyRmsErrorOverrideHasEffect); // Ctor that creates a single channel AGC and by injecting `agc`. // `agc` will be owned by this class; hence, do not delete it. @@ -198,10 +212,13 @@ class MonoAgc { // `set_stream_analog_level()`. void HandleClipping(int clipped_level_step); - // Analyzes `audio`, updates the recommended input volume based on the - // estimated speech level and, if enabled, updates the (digital) compression - // gain to be applied by `agc_`. Must be called after `HandleClipping()`. - void Process(rtc::ArrayView audio); + // Analyzes `audio`, requests the RMS error from AGC, updates the recommended + // input volume based on the estimated speech level and, if enabled, updates + // the (digital) compression gain to be applied by `agc_`. Must be called + // after `HandleClipping()`. If `rms_error_override` has a value, RMS error + // from AGC is overridden by it. + void Process(rtc::ArrayView audio, + absl::optional rms_error_override); // Returns the recommended input volume. Must be called after `Process()`. int recommended_analog_level() const { return recommended_input_volume_; } @@ -257,6 +274,11 @@ class MonoAgc { absl::optional new_compression_to_set_; bool log_to_histograms_ = false; const int clipped_level_min_; + + // Frames since the last `UpdateGain()` call. + int frames_since_update_gain_ = 0; + // Set to true for the first frame after startup and reset, otherwise false. + bool is_first_frame_ = true; }; } // namespace webrtc diff --git a/modules/audio_processing/agc/agc_manager_direct_unittest.cc b/modules/audio_processing/agc/agc_manager_direct_unittest.cc index 6470a3c788..35959c859a 100644 --- a/modules/audio_processing/agc/agc_manager_direct_unittest.cc +++ b/modules/audio_processing/agc/agc_manager_direct_unittest.cc @@ -12,6 +12,7 @@ #include #include +#include #include #include "modules/audio_processing/agc/gain_control.h" @@ -42,6 +43,8 @@ constexpr int kMinMicLevel = 12; constexpr int kClippedLevelStep = 15; constexpr float kClippedRatioThreshold = 0.1f; constexpr int kClippedWaitFrames = 300; +constexpr float kHighSpeechProbability = 0.7f; +constexpr float kSpeechLevel = -25.0f; constexpr float kMinSample = std::numeric_limits::min(); constexpr float kMaxSample = std::numeric_limits::max(); @@ -192,10 +195,13 @@ void WriteAudioBufferSamples(float samples_value, // `AgcManagerDirectTestHelper::CallAgcSequence()` instead. void CallPreProcessAndProcess(int num_calls, const AudioBuffer& audio_buffer, + absl::optional speech_probability_override, + absl::optional speech_level_override, AgcManagerDirect& manager) { for (int n = 0; n < num_calls; ++n) { manager.AnalyzePreProcess(audio_buffer); - manager.Process(audio_buffer); + manager.Process(audio_buffer, speech_probability_override, + speech_level_override); } } @@ -250,6 +256,41 @@ class SpeechSamplesReader { } } + // Reads `num_frames` 10 ms frames from the beginning of the PCM file, applies + // `gain_db` and feeds the frames into `agc` by calling `AnalyzePreProcess()` + // and `Process()` for each frame. Reads the number of 10 ms frames available + // in the PCM file if `num_frames` is too large - i.e., does not loop. + // `speech_probability_override` and `speech_level_override` are passed to + // `Process()` where they are used to override the `agc` RMS error if they + // have a value. + void Feed(int num_frames, + int gain_db, + absl::optional speech_probability_override, + absl::optional speech_level_override, + AgcManagerDirect& agc) { + float gain = std::pow(10.0f, gain_db / 20.0f); // From dB to linear gain. + is_.seekg(0, is_.beg); // Start from the beginning of the PCM file. + + // Read and feed frames. + for (int i = 0; i < num_frames; ++i) { + is_.read(reinterpret_cast(buffer_.data()), buffer_num_bytes_); + if (is_.gcount() < buffer_num_bytes_) { + // EOF reached. Stop. + break; + } + // Apply gain and copy samples into `audio_buffer_`. + std::transform(buffer_.begin(), buffer_.end(), + audio_buffer_.channels()[0], [gain](int16_t v) -> float { + return rtc::SafeClamp(static_cast(v) * gain, + kMinSample, kMaxSample); + }); + + agc.AnalyzePreProcess(audio_buffer_); + agc.Process(audio_buffer_, speech_probability_override, + speech_level_override); + } + } + private: std::ifstream is_; AudioBuffer audio_buffer_; @@ -307,11 +348,16 @@ class AgcManagerDirectTestHelper { // - Sets the applied input volume; // - Uses `audio_buffer` to call `AnalyzePreProcess()` and `Process()`; // - Sets the digital compression gain, if specified, on the injected - // `mock_agc`. Returns the recommended input volume. - int CallAgcSequence(int applied_input_volume) { + // `mock_agc`. Returns the recommended input volume. The RMS error from + // AGC is replaced by an override value if `speech_probability_override` + // and `speech_level_override` have a value. + int CallAgcSequence(int applied_input_volume, + absl::optional speech_probability_override, + absl::optional speech_level_override) { manager.set_stream_analog_level(applied_input_volume); manager.AnalyzePreProcess(audio_buffer); - manager.Process(audio_buffer); + manager.Process(audio_buffer, speech_probability_override, + speech_level_override); absl::optional digital_gain = manager.GetDigitalComressionGain(); if (digital_gain) { mock_gain_control.set_compression_gain_db(*digital_gain); @@ -321,11 +367,16 @@ class AgcManagerDirectTestHelper { // Deprecated. // TODO(bugs.webrtc.org/7494): Let the caller write `audio_buffer` and use - // `CallAgcSequence()`. - void CallProcess(int num_calls) { + // `CallAgcSequence()`. The RMS error from AGC is replaced by an override + // value if `speech_probability_override` and `speech_level_override` have + // a value. + void CallProcess(int num_calls, + absl::optional speech_probability_override, + absl::optional speech_level_override) { for (int i = 0; i < num_calls; ++i) { EXPECT_CALL(*mock_agc, Process(_)).WillOnce(Return()); - manager.Process(audio_buffer); + manager.Process(audio_buffer, speech_probability_override, + speech_level_override); absl::optional new_digital_gain = manager.GetDigitalComressionGain(); if (new_digital_gain) { mock_gain_control.set_compression_gain_db(*new_digital_gain); @@ -383,24 +434,40 @@ class AgcManagerDirectTestHelper { }; class AgcManagerDirectParametrizedTest - : public ::testing::TestWithParam> { + : public ::testing::TestWithParam, bool>> { protected: AgcManagerDirectParametrizedTest() - : field_trials_(GetAgcMinMicLevelExperimentFieldTrial(GetParam())) {} + : field_trials_( + GetAgcMinMicLevelExperimentFieldTrial(std::get<0>(GetParam()))) {} - bool IsMinMicLevelOverridden() const { return GetParam().has_value(); } - int GetMinMicLevel() const { return GetParam().value_or(kMinMicLevel); } + bool IsMinMicLevelOverridden() const { + return std::get<0>(GetParam()).has_value(); + } + int GetMinMicLevel() const { + return std::get<0>(GetParam()).value_or(kMinMicLevel); + } + + bool IsRmsErrorOverridden() const { return std::get<1>(GetParam()); } + absl::optional GetOverrideOrEmpty(float value) const { + return IsRmsErrorOverridden() ? absl::optional(value) + : absl::nullopt; + } private: test::ScopedFieldTrials field_trials_; }; -INSTANTIATE_TEST_SUITE_P(, - AgcManagerDirectParametrizedTest, - testing::Values(absl::nullopt, 12, 20)); +INSTANTIATE_TEST_SUITE_P( + , + AgcManagerDirectParametrizedTest, + ::testing::Combine(testing::Values(absl::nullopt, 12, 20), + testing::Bool())); // Checks that when the analog controller is disabled, no downward adaptation // takes place. +// TODO(webrtc:7494): Revisit the test after moving the number of override wait +// frames to AMP config. The test passes but internally the gain update timing +// differs. TEST_P(AgcManagerDirectParametrizedTest, DisabledAnalogAgcDoesNotAdaptDownwards) { AgcManagerDirect manager_no_analog_agc(kNumChannels, @@ -426,16 +493,24 @@ TEST_P(AgcManagerDirectParametrizedTest, audio_buffer); manager_no_analog_agc.AnalyzePreProcess(audio_buffer); manager_with_analog_agc.AnalyzePreProcess(audio_buffer); - manager_no_analog_agc.Process(audio_buffer); - manager_with_analog_agc.Process(audio_buffer); + manager_no_analog_agc.Process(audio_buffer, + GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(-18.0f)); + manager_with_analog_agc.Process(audio_buffer, + GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(-18.0f)); // Feed clipping input to trigger a downward adapation of the analog level. WriteAudioBufferSamples(/*samples_value=*/0.0f, /*clipping_ratio=*/0.2f, audio_buffer); manager_no_analog_agc.AnalyzePreProcess(audio_buffer); manager_with_analog_agc.AnalyzePreProcess(audio_buffer); - manager_no_analog_agc.Process(audio_buffer); - manager_with_analog_agc.Process(audio_buffer); + manager_no_analog_agc.Process(audio_buffer, + GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(-10.0f)); + manager_with_analog_agc.Process(audio_buffer, + GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(-10.0f)); // Check that no adaptation occurs when the analog controller is disabled // and make sure that the test triggers a downward adaptation otherwise. @@ -445,6 +520,9 @@ TEST_P(AgcManagerDirectParametrizedTest, // Checks that when the analog controller is disabled, no upward adaptation // takes place. +// TODO(webrtc:7494): Revisit the test after moving the number of override wait +// frames to APM config. The test passes but internally the gain update timing +// differs. TEST_P(AgcManagerDirectParametrizedTest, DisabledAnalogAgcDoesNotAdaptUpwards) { AgcManagerDirect manager_no_analog_agc(kNumChannels, GetDisabledAnalogAgcConfig()); @@ -462,8 +540,10 @@ TEST_P(AgcManagerDirectParametrizedTest, DisabledAnalogAgcDoesNotAdaptUpwards) { constexpr int kNumFrames = 125; constexpr int kGainDb = -20; SpeechSamplesReader reader; - reader.Feed(kNumFrames, kGainDb, manager_no_analog_agc); - reader.Feed(kNumFrames, kGainDb, manager_with_analog_agc); + reader.Feed(kNumFrames, kGainDb, GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(-42.0f), manager_no_analog_agc); + reader.Feed(kNumFrames, kGainDb, GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(-42.0f), manager_with_analog_agc); // Check that no adaptation occurs when the analog controller is disabled // and make sure that the test triggers an upward adaptation otherwise. @@ -474,216 +554,281 @@ TEST_P(AgcManagerDirectParametrizedTest, DisabledAnalogAgcDoesNotAdaptUpwards) { TEST_P(AgcManagerDirectParametrizedTest, StartupMinVolumeConfigurationIsRespected) { AgcManagerDirectTestHelper helper; - helper.CallAgcSequence(kInitialInputVolume); + helper.CallAgcSequence(kInitialInputVolume, + GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(kSpeechLevel)); EXPECT_EQ(kInitialInputVolume, helper.manager.recommended_analog_level()); } TEST_P(AgcManagerDirectParametrizedTest, MicVolumeResponseToRmsError) { + const auto speech_probability_override = + GetOverrideOrEmpty(kHighSpeechProbability); + AgcManagerDirectTestHelper helper; - helper.CallAgcSequence(kInitialInputVolume); + helper.CallAgcSequence(kInitialInputVolume, speech_probability_override, + GetOverrideOrEmpty(kSpeechLevel)); // Compressor default; no residual error. EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(5), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-23.0f)); // Inside the compressor's window; no change of volume. EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(10), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-28.0f)); // Above the compressor's window; volume should be increased. EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(11), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-29.0f)); EXPECT_EQ(130, helper.manager.recommended_analog_level()); EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(20), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-38.0f)); EXPECT_EQ(168, helper.manager.recommended_analog_level()); // Inside the compressor's window; no change of volume. EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(5), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-23.0f)); EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(0), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-18.0f)); // Below the compressor's window; volume should be decreased. EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(-1), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-17.0f)); EXPECT_EQ(167, helper.manager.recommended_analog_level()); EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(-1), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-17.0f)); EXPECT_EQ(163, helper.manager.recommended_analog_level()); EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(-9), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-9.0f)); EXPECT_EQ(129, helper.manager.recommended_analog_level()); } TEST_P(AgcManagerDirectParametrizedTest, MicVolumeIsLimited) { + const auto speech_probability_override = + GetOverrideOrEmpty(kHighSpeechProbability); + AgcManagerDirectTestHelper helper; - helper.CallAgcSequence(kInitialInputVolume); + helper.CallAgcSequence(kInitialInputVolume, speech_probability_override, + GetOverrideOrEmpty(kSpeechLevel)); // Maximum upwards change is limited. EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(30), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-48.0f)); EXPECT_EQ(183, helper.manager.recommended_analog_level()); EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(30), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-48.0f)); EXPECT_EQ(243, helper.manager.recommended_analog_level()); // Won't go higher than the maximum. EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(30), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-48.0f)); EXPECT_EQ(255, helper.manager.recommended_analog_level()); EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(-1), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-17.0f)); EXPECT_EQ(254, helper.manager.recommended_analog_level()); // Maximum downwards change is limited. EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(-40), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(22.0f)); EXPECT_EQ(194, helper.manager.recommended_analog_level()); EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(-40), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(22.0f)); EXPECT_EQ(137, helper.manager.recommended_analog_level()); EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(-40), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(22.0f)); EXPECT_EQ(88, helper.manager.recommended_analog_level()); EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(-40), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(22.0f)); EXPECT_EQ(54, helper.manager.recommended_analog_level()); EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(-40), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(22.0f)); EXPECT_EQ(33, helper.manager.recommended_analog_level()); // Won't go lower than the minimum. EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(-40), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(22.0f)); EXPECT_EQ(std::max(18, GetMinMicLevel()), helper.manager.recommended_analog_level()); EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(-40), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(22.0f)); EXPECT_EQ(std::max(12, GetMinMicLevel()), helper.manager.recommended_analog_level()); } TEST_P(AgcManagerDirectParametrizedTest, CompressorStepsTowardsTarget) { + constexpr absl::optional kNoOverride = absl::nullopt; + const auto speech_probability_override = + GetOverrideOrEmpty(kHighSpeechProbability); + AgcManagerDirectTestHelper helper; - helper.CallAgcSequence(kInitialInputVolume); + helper.CallAgcSequence(kInitialInputVolume, speech_probability_override, + GetOverrideOrEmpty(kSpeechLevel)); // Compressor default; no call to set_compression_gain_db. EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(5), Return(true))) .WillRepeatedly(Return(false)); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0); - helper.CallProcess(/*num_calls=*/20); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-23.0f)); + EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0); + // The mock `GetRmsErrorDb()` returns false; mimic this by passing + // absl::nullopt as an override. + helper.CallProcess(/*num_calls=*/19, kNoOverride, kNoOverride); // Moves slowly upwards. EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(9), Return(true))) .WillRepeatedly(Return(false)); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0); - helper.CallProcess(/*num_calls=*/19); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-27.0f)); + EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0); + helper.CallProcess(/*num_calls=*/18, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(8)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0); - helper.CallProcess(/*num_calls=*/19); + helper.CallProcess(/*num_calls=*/19, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(9)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0); - helper.CallProcess(/*num_calls=*/20); + helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride); // Moves slowly downward, then reverses before reaching the original target. EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(5), Return(true))) .WillRepeatedly(Return(false)); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0); - helper.CallProcess(/*num_calls=*/19); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-23.0f)); + EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0); + helper.CallProcess(/*num_calls=*/18, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(8)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, kNoOverride, kNoOverride); EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(9), Return(true))) .WillRepeatedly(Return(false)); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0); - helper.CallProcess(/*num_calls=*/19); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-27.0f)); + EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0); + helper.CallProcess(/*num_calls=*/18, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(9)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0); - helper.CallProcess(/*num_calls=*/20); + helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride); } TEST_P(AgcManagerDirectParametrizedTest, CompressorErrorIsDeemphasized) { + constexpr absl::optional kNoOverride = absl::nullopt; + const auto speech_probability_override = + GetOverrideOrEmpty(kHighSpeechProbability); + AgcManagerDirectTestHelper helper; - helper.CallAgcSequence(kInitialInputVolume); + helper.CallAgcSequence(kInitialInputVolume, speech_probability_override, + GetOverrideOrEmpty(kSpeechLevel)); EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(10), Return(true))) .WillRepeatedly(Return(false)); - helper.CallProcess(/*num_calls=*/19); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-28.0f)); + // The mock `GetRmsErrorDb()` returns false; mimic this by passing + // absl::nullopt as an override. + helper.CallProcess(/*num_calls=*/18, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(8)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/20); + helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(9)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0); - helper.CallProcess(/*num_calls=*/20); + helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride); EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(0), Return(true))) .WillRepeatedly(Return(false)); - helper.CallProcess(/*num_calls=*/19); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-18.0f)); + helper.CallProcess(/*num_calls=*/18, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(8)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/20); + helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(7)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/20); + helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(6)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0); - helper.CallProcess(/*num_calls=*/20); + helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride); } TEST_P(AgcManagerDirectParametrizedTest, CompressorReachesMaximum) { + constexpr absl::optional kNoOverride = absl::nullopt; + const auto speech_probability_override = + GetOverrideOrEmpty(kHighSpeechProbability); + AgcManagerDirectTestHelper helper; - helper.CallAgcSequence(kInitialInputVolume); + helper.CallAgcSequence(kInitialInputVolume, speech_probability_override, + GetOverrideOrEmpty(kSpeechLevel)); EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(10), Return(true))) @@ -691,27 +836,36 @@ TEST_P(AgcManagerDirectParametrizedTest, CompressorReachesMaximum) { .WillOnce(DoAll(SetArgPointee<0>(10), Return(true))) .WillOnce(DoAll(SetArgPointee<0>(10), Return(true))) .WillRepeatedly(Return(false)); - helper.CallProcess(/*num_calls=*/19); + helper.CallProcess(/*num_calls=*/4, speech_probability_override, + GetOverrideOrEmpty(-28.0f)); + // The mock `GetRmsErrorDb()` returns false; mimic this by passing + // absl::nullopt as an override. + helper.CallProcess(/*num_calls=*/15, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(8)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/20); + helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(9)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/20); + helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(10)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/20); + helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(11)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/20); + helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(12)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, kNoOverride, kNoOverride); } TEST_P(AgcManagerDirectParametrizedTest, CompressorReachesMinimum) { + constexpr absl::optional kNoOverride = absl::nullopt; + const auto speech_probability_override = + GetOverrideOrEmpty(kHighSpeechProbability); + AgcManagerDirectTestHelper helper; - helper.CallAgcSequence(kInitialInputVolume); + helper.CallAgcSequence(kInitialInputVolume, speech_probability_override, + GetOverrideOrEmpty(kSpeechLevel)); EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(0), Return(true))) @@ -719,30 +873,39 @@ TEST_P(AgcManagerDirectParametrizedTest, CompressorReachesMinimum) { .WillOnce(DoAll(SetArgPointee<0>(0), Return(true))) .WillOnce(DoAll(SetArgPointee<0>(0), Return(true))) .WillRepeatedly(Return(false)); - helper.CallProcess(/*num_calls=*/19); + helper.CallProcess(/*num_calls=*/4, speech_probability_override, + GetOverrideOrEmpty(-18.0f)); + // The mock `GetRmsErrorDb()` returns false; mimic this by passing + // absl::nullopt as an override. + helper.CallProcess(/*num_calls=*/15, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(6)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/20); + helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(5)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/20); + helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(4)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/20); + helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(3)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/20); + helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(2)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, kNoOverride, kNoOverride); } TEST_P(AgcManagerDirectParametrizedTest, NoActionWhileMuted) { AgcManagerDirectTestHelper helper; - helper.CallAgcSequence(kInitialInputVolume); + helper.CallAgcSequence(kInitialInputVolume, + GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(kSpeechLevel)); helper.manager.HandleCaptureOutputUsedChange(false); - helper.manager.Process(helper.audio_buffer); + helper.manager.Process(helper.audio_buffer, + GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(kSpeechLevel)); + absl::optional new_digital_gain = helper.manager.GetDigitalComressionGain(); if (new_digital_gain) { @@ -752,7 +915,9 @@ TEST_P(AgcManagerDirectParametrizedTest, NoActionWhileMuted) { TEST_P(AgcManagerDirectParametrizedTest, UnmutingChecksVolumeWithoutRaising) { AgcManagerDirectTestHelper helper; - helper.CallAgcSequence(kInitialInputVolume); + helper.CallAgcSequence(kInitialInputVolume, + GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(kSpeechLevel)); helper.manager.HandleCaptureOutputUsedChange(false); helper.manager.HandleCaptureOutputUsedChange(true); @@ -763,13 +928,17 @@ TEST_P(AgcManagerDirectParametrizedTest, UnmutingChecksVolumeWithoutRaising) { // SetMicVolume should not be called. EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)).WillOnce(Return(false)); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, + GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(kSpeechLevel)); EXPECT_EQ(127, helper.manager.recommended_analog_level()); } TEST_P(AgcManagerDirectParametrizedTest, UnmutingRaisesTooLowVolume) { AgcManagerDirectTestHelper helper; - helper.CallAgcSequence(kInitialInputVolume); + helper.CallAgcSequence(kInitialInputVolume, + GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(kSpeechLevel)); helper.manager.HandleCaptureOutputUsedChange(false); helper.manager.HandleCaptureOutputUsedChange(true); @@ -779,14 +948,20 @@ TEST_P(AgcManagerDirectParametrizedTest, UnmutingRaisesTooLowVolume) { EXPECT_CALL(*helper.mock_agc, Reset()); EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)).WillOnce(Return(false)); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, + GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(kSpeechLevel)); EXPECT_EQ(GetMinMicLevel(), helper.manager.recommended_analog_level()); } TEST_P(AgcManagerDirectParametrizedTest, ManualLevelChangeResultsInNoSetMicCall) { + const auto speech_probability_override = + GetOverrideOrEmpty(kHighSpeechProbability); + AgcManagerDirectTestHelper helper; - helper.CallAgcSequence(kInitialInputVolume); + helper.CallAgcSequence(kInitialInputVolume, speech_probability_override, + GetOverrideOrEmpty(kSpeechLevel)); // Change outside of compressor's range, which would normally trigger a call // to `SetMicVolume()`. @@ -800,7 +975,8 @@ TEST_P(AgcManagerDirectParametrizedTest, // a manual volume change. ASSERT_NE(helper.manager.recommended_analog_level(), 154); helper.manager.set_stream_analog_level(154); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-29.0f)); EXPECT_EQ(154, helper.manager.recommended_analog_level()); // Do the same thing, except downwards now. @@ -808,30 +984,39 @@ TEST_P(AgcManagerDirectParametrizedTest, .WillOnce(DoAll(SetArgPointee<0>(-1), Return(true))); helper.manager.set_stream_analog_level(100); EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1)); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-17.0f)); EXPECT_EQ(100, helper.manager.recommended_analog_level()); // And finally verify the AGC continues working without a manual change. EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(-1), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-17.0f)); EXPECT_EQ(99, helper.manager.recommended_analog_level()); } TEST_P(AgcManagerDirectParametrizedTest, RecoveryAfterManualLevelChangeFromMax) { + const auto speech_probability_override = + GetOverrideOrEmpty(kHighSpeechProbability); + AgcManagerDirectTestHelper helper; - helper.CallAgcSequence(kInitialInputVolume); + helper.CallAgcSequence(kInitialInputVolume, speech_probability_override, + GetOverrideOrEmpty(kSpeechLevel)); // Force the mic up to max volume. Takes a few steps due to the residual // gain limitation. EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillRepeatedly(DoAll(SetArgPointee<0>(30), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-48.0f)); EXPECT_EQ(183, helper.manager.recommended_analog_level()); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-48.0f)); EXPECT_EQ(243, helper.manager.recommended_analog_level()); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-48.0f)); EXPECT_EQ(255, helper.manager.recommended_analog_level()); // Manual change does not result in SetMicVolume call. @@ -839,46 +1024,62 @@ TEST_P(AgcManagerDirectParametrizedTest, .WillOnce(DoAll(SetArgPointee<0>(-1), Return(true))); helper.manager.set_stream_analog_level(50); EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1)); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-17.0f)); EXPECT_EQ(50, helper.manager.recommended_analog_level()); // Continues working as usual afterwards. EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(20), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-38.0f)); + EXPECT_EQ(69, helper.manager.recommended_analog_level()); } // Checks that, when the min mic level override is not specified, AGC ramps up // towards the minimum mic level after the mic level is manually set below the // minimum gain to enforce. -TEST(AgcManagerDirectTest, RecoveryAfterManualLevelChangeBelowMin) { - AgcManagerDirectTestHelper helper; - helper.CallAgcSequence(kInitialInputVolume); +TEST_P(AgcManagerDirectParametrizedTest, + RecoveryAfterManualLevelChangeBelowMinWithoutMiMicLevelnOverride) { + if (IsMinMicLevelOverridden()) { + GTEST_SKIP() << "Skipped. Min mic level overridden."; + } - // Manual change below min, but strictly positive, otherwise - // AGC won't take any action. + const auto speech_probability_override = + GetOverrideOrEmpty(kHighSpeechProbability); + + AgcManagerDirectTestHelper helper; + helper.CallAgcSequence(kInitialInputVolume, speech_probability_override, + GetOverrideOrEmpty(kSpeechLevel)); + + // Manual change below min, but strictly positive, otherwise AGC won't take + // any action. EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(-1), Return(true))); helper.manager.set_stream_analog_level(1); EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1)); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-17.0f)); EXPECT_EQ(1, helper.manager.recommended_analog_level()); // Continues working as usual afterwards. EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(11), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-29.0f)); EXPECT_EQ(2, helper.manager.recommended_analog_level()); EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(30), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-48.0f)); EXPECT_EQ(11, helper.manager.recommended_analog_level()); EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(20), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-38.0f)); EXPECT_EQ(18, helper.manager.recommended_analog_level()); } @@ -891,8 +1092,12 @@ TEST_P(AgcManagerDirectParametrizedTest, GTEST_SKIP() << "Skipped. Min mic level not overridden."; } + const auto speech_probability_override = + GetOverrideOrEmpty(kHighSpeechProbability); + AgcManagerDirectTestHelper helper; - helper.CallAgcSequence(kInitialInputVolume); + helper.CallAgcSequence(kInitialInputVolume, speech_probability_override, + GetOverrideOrEmpty(kSpeechLevel)); // Manual change below min, but strictly positive, otherwise // AGC won't take any action. @@ -900,13 +1105,16 @@ TEST_P(AgcManagerDirectParametrizedTest, .WillOnce(DoAll(SetArgPointee<0>(-1), Return(true))); helper.manager.set_stream_analog_level(1); EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1)); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-17.0f)); EXPECT_EQ(GetMinMicLevel(), helper.manager.recommended_analog_level()); } TEST_P(AgcManagerDirectParametrizedTest, NoClippingHasNoImpact) { AgcManagerDirectTestHelper helper; - helper.CallAgcSequence(kInitialInputVolume); + helper.CallAgcSequence(kInitialInputVolume, + GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(kSpeechLevel)); helper.CallPreProc(/*num_calls=*/100, /*clipped_ratio=*/0); EXPECT_EQ(128, helper.manager.recommended_analog_level()); @@ -914,7 +1122,9 @@ TEST_P(AgcManagerDirectParametrizedTest, NoClippingHasNoImpact) { TEST_P(AgcManagerDirectParametrizedTest, ClippingUnderThresholdHasNoImpact) { AgcManagerDirectTestHelper helper; - helper.CallAgcSequence(kInitialInputVolume); + helper.CallAgcSequence(kInitialInputVolume, + GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(kSpeechLevel)); helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/0.099); EXPECT_EQ(128, helper.manager.recommended_analog_level()); @@ -922,7 +1132,9 @@ TEST_P(AgcManagerDirectParametrizedTest, ClippingUnderThresholdHasNoImpact) { TEST_P(AgcManagerDirectParametrizedTest, ClippingLowersVolume) { AgcManagerDirectTestHelper helper; - helper.CallAgcSequence(/*applied_input_volume=*/255); + helper.CallAgcSequence(/*applied_input_volume=*/255, + GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(kSpeechLevel)); EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1)); helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/0.2); @@ -931,7 +1143,9 @@ TEST_P(AgcManagerDirectParametrizedTest, ClippingLowersVolume) { TEST_P(AgcManagerDirectParametrizedTest, WaitingPeriodBetweenClippingChecks) { AgcManagerDirectTestHelper helper; - helper.CallAgcSequence(/*applied_input_volume=*/255); + helper.CallAgcSequence(/*applied_input_volume=*/255, + GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(kSpeechLevel)); EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1)); helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold); @@ -949,7 +1163,9 @@ TEST_P(AgcManagerDirectParametrizedTest, WaitingPeriodBetweenClippingChecks) { TEST_P(AgcManagerDirectParametrizedTest, ClippingLoweringIsLimited) { AgcManagerDirectTestHelper helper; - helper.CallAgcSequence(/*applied_input_volume=*/180); + helper.CallAgcSequence(/*applied_input_volume=*/180, + GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(kSpeechLevel)); EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1)); helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold); @@ -963,8 +1179,13 @@ TEST_P(AgcManagerDirectParametrizedTest, ClippingLoweringIsLimited) { TEST_P(AgcManagerDirectParametrizedTest, ClippingMaxIsRespectedWhenEqualToLevel) { + const auto speech_probability_override = + GetOverrideOrEmpty(kHighSpeechProbability); + AgcManagerDirectTestHelper helper; - helper.CallAgcSequence(/*applied_input_volume=*/255); + helper.CallAgcSequence(/*applied_input_volume=*/255, + speech_probability_override, + GetOverrideOrEmpty(kSpeechLevel)); EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1)); helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold); @@ -972,14 +1193,20 @@ TEST_P(AgcManagerDirectParametrizedTest, EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillRepeatedly(DoAll(SetArgPointee<0>(30), Return(true))); - helper.CallProcess(/*num_calls=*/10); + helper.CallProcess(/*num_calls=*/10, speech_probability_override, + GetOverrideOrEmpty(-48.0f)); EXPECT_EQ(240, helper.manager.recommended_analog_level()); } TEST_P(AgcManagerDirectParametrizedTest, ClippingMaxIsRespectedWhenHigherThanLevel) { + const auto speech_probability_override = + GetOverrideOrEmpty(kHighSpeechProbability); + AgcManagerDirectTestHelper helper; - helper.CallAgcSequence(/*applied_input_volume=*/200); + helper.CallAgcSequence(/*applied_input_volume=*/200, + speech_probability_override, + GetOverrideOrEmpty(kSpeechLevel)); EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1)); helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold); @@ -987,16 +1214,24 @@ TEST_P(AgcManagerDirectParametrizedTest, EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillRepeatedly(DoAll(SetArgPointee<0>(40), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-58.0f)); EXPECT_EQ(240, helper.manager.recommended_analog_level()); - helper.CallProcess(/*num_calls=*/10); + helper.CallProcess(/*num_calls=*/10, speech_probability_override, + GetOverrideOrEmpty(-58.0f)); EXPECT_EQ(240, helper.manager.recommended_analog_level()); } TEST_P(AgcManagerDirectParametrizedTest, MaxCompressionIsIncreasedAfterClipping) { + constexpr absl::optional kNoOverride = absl::nullopt; + const auto speech_probability_override = + GetOverrideOrEmpty(kHighSpeechProbability); + AgcManagerDirectTestHelper helper; - helper.CallAgcSequence(/*applied_input_volume=*/210); + helper.CallAgcSequence(/*applied_input_volume=*/210, + speech_probability_override, + GetOverrideOrEmpty(kSpeechLevel)); EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1)); helper.CallPreProc(/*num_calls=*/1, kAboveClippedThreshold); @@ -1009,25 +1244,29 @@ TEST_P(AgcManagerDirectParametrizedTest, .WillOnce(DoAll(SetArgPointee<0>(11), Return(true))) .WillOnce(DoAll(SetArgPointee<0>(11), Return(true))) .WillRepeatedly(Return(false)); - helper.CallProcess(/*num_calls=*/19); + helper.CallProcess(/*num_calls=*/5, speech_probability_override, + GetOverrideOrEmpty(-29.0f)); + // The mock `GetRmsErrorDb()` returns false; mimic this by passing + // absl::nullopt as an override. + helper.CallProcess(/*num_calls=*/14, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(8)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/20); + helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(9)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/20); + helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(10)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/20); + helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(11)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/20); + helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(12)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/20); + helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(13)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, kNoOverride, kNoOverride); // Continue clipping until we hit the maximum surplus compression. helper.CallPreProc(/*num_calls=*/300, @@ -1062,27 +1301,34 @@ TEST_P(AgcManagerDirectParametrizedTest, .WillOnce(DoAll(SetArgPointee<0>(16), Return(true))) .WillOnce(DoAll(SetArgPointee<0>(16), Return(true))) .WillRepeatedly(Return(false)); - helper.CallProcess(/*num_calls=*/19); + helper.CallProcess(/*num_calls=*/4, speech_probability_override, + GetOverrideOrEmpty(-34.0f)); + helper.CallProcess(/*num_calls=*/15, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(14)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/20); + helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(15)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/20); + helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(16)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/20); + helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(17)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/20); + helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride); EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(18)) .WillOnce(Return(0)); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, kNoOverride, kNoOverride); } TEST_P(AgcManagerDirectParametrizedTest, UserCanRaiseVolumeAfterClipping) { + const auto speech_probability_override = + GetOverrideOrEmpty(kHighSpeechProbability); + AgcManagerDirectTestHelper helper; - helper.CallAgcSequence(/*applied_input_volume=*/225); + helper.CallAgcSequence(/*applied_input_volume=*/225, + speech_probability_override, + GetOverrideOrEmpty(kSpeechLevel)); EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1)); helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold); @@ -1094,29 +1340,35 @@ TEST_P(AgcManagerDirectParametrizedTest, UserCanRaiseVolumeAfterClipping) { // User changed the volume. helper.manager.set_stream_analog_level(250); EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1)); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-32.0f)); EXPECT_EQ(250, helper.manager.recommended_analog_level()); // Move down... EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(-10), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-8.0f)); EXPECT_EQ(210, helper.manager.recommended_analog_level()); // And back up to the new max established by the user. EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(40), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-58.0f)); EXPECT_EQ(250, helper.manager.recommended_analog_level()); // Will not move above new maximum. EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillOnce(DoAll(SetArgPointee<0>(30), Return(true))); - helper.CallProcess(/*num_calls=*/1); + helper.CallProcess(/*num_calls=*/1, speech_probability_override, + GetOverrideOrEmpty(-48.0f)); EXPECT_EQ(250, helper.manager.recommended_analog_level()); } TEST_P(AgcManagerDirectParametrizedTest, ClippingDoesNotPullLowVolumeBackUp) { AgcManagerDirectTestHelper helper; - helper.CallAgcSequence(/*applied_input_volume=*/80); + helper.CallAgcSequence(/*applied_input_volume=*/80, + GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(kSpeechLevel)); EXPECT_CALL(*helper.mock_agc, Reset()).Times(0); int initial_volume = helper.manager.recommended_analog_level(); @@ -1126,18 +1378,24 @@ TEST_P(AgcManagerDirectParametrizedTest, ClippingDoesNotPullLowVolumeBackUp) { TEST_P(AgcManagerDirectParametrizedTest, TakesNoActionOnZeroMicVolume) { AgcManagerDirectTestHelper helper; - helper.CallAgcSequence(kInitialInputVolume); + helper.CallAgcSequence(kInitialInputVolume, + GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(kSpeechLevel)); EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) .WillRepeatedly(DoAll(SetArgPointee<0>(30), Return(true))); helper.manager.set_stream_analog_level(0); - helper.CallProcess(/*num_calls=*/10); + helper.CallProcess(/*num_calls=*/10, + GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(-48.0f)); EXPECT_EQ(0, helper.manager.recommended_analog_level()); } TEST_P(AgcManagerDirectParametrizedTest, ClippingDetectionLowersVolume) { AgcManagerDirectTestHelper helper; - helper.CallAgcSequence(/*applied_input_volume=*/255); + helper.CallAgcSequence(/*applied_input_volume=*/255, + GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(kSpeechLevel)); EXPECT_EQ(255, helper.manager.recommended_analog_level()); helper.CallPreProcForChangingAudio(/*num_calls=*/100, /*peak_ratio=*/0.99f); @@ -1149,7 +1407,9 @@ TEST_P(AgcManagerDirectParametrizedTest, ClippingDetectionLowersVolume) { TEST_P(AgcManagerDirectParametrizedTest, DisabledClippingPredictorDoesNotLowerVolume) { AgcManagerDirectTestHelper helper; - helper.CallAgcSequence(/*applied_input_volume=*/255); + helper.CallAgcSequence(/*applied_input_volume=*/255, + GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(kSpeechLevel)); EXPECT_FALSE(helper.manager.clipping_predictor_enabled()); EXPECT_EQ(255, helper.manager.recommended_analog_level()); @@ -1160,6 +1420,10 @@ TEST_P(AgcManagerDirectParametrizedTest, } TEST_P(AgcManagerDirectParametrizedTest, DisableDigitalDisablesDigital) { + if (IsRmsErrorOverridden()) { + GTEST_SKIP() << "Skipped. RMS error override does not affect the test."; + } + auto agc = std::unique_ptr(new ::testing::NiceMock()); MockGainControl mock_gain_control; EXPECT_CALL(mock_gain_control, set_mode(GainControl::kFixedDigital)); @@ -1273,8 +1537,12 @@ TEST(AgcManagerDirectTest, AgcMinMicLevelExperimentCheckMinLevelWithClipping) { // Simulate 4 seconds of clipping; it is expected to trigger a downward // adjustment of the analog gain. - CallPreProcessAndProcess(/*num_calls=*/400, audio_buffer, *manager); CallPreProcessAndProcess(/*num_calls=*/400, audio_buffer, + /*speech_probability_override=*/absl::nullopt, + /*speech_level_override=*/absl::nullopt, *manager); + CallPreProcessAndProcess(/*num_calls=*/400, audio_buffer, + /*speech_probability_override=*/absl::nullopt, + /*speech_level_override=*/absl::nullopt, *manager_with_override); // Make sure that an adaptation occurred. @@ -1290,6 +1558,65 @@ TEST(AgcManagerDirectTest, AgcMinMicLevelExperimentCheckMinLevelWithClipping) { kMinMicLevelOverride); } +// Checks that, when the "WebRTC-Audio-AgcMinMicLevelExperiment" 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 override is used. +// TODO(webrtc:7494): Revisit the test after moving the number of override wait +// frames to APM config. The test passes but internally the gain update timing +// differs. +TEST(AgcManagerDirectTest, + AgcMinMicLevelExperimentCheckMinLevelWithClippingWithRmsErrorOverride) { + constexpr int kMinMicLevelOverride = 250; + + // Create and initialize two AGCs by specifying and leaving unspecified the + // relevant field trial. + const auto factory = []() { + std::unique_ptr manager = + CreateAgcManagerDirect(kInitialInputVolume, kClippedLevelStep, + kClippedRatioThreshold, kClippedWaitFrames); + manager->Initialize(); + manager->set_stream_analog_level(kInitialInputVolume); + return manager; + }; + std::unique_ptr manager = factory(); + std::unique_ptr manager_with_override; + { + test::ScopedFieldTrials field_trial( + GetAgcMinMicLevelExperimentFieldTrialEnabled(kMinMicLevelOverride)); + manager_with_override = factory(); + } + + // Create a test input signal which containts 80% of clipped samples. + AudioBuffer audio_buffer(kSampleRateHz, 1, kSampleRateHz, 1, kSampleRateHz, + 1); + WriteAudioBufferSamples(/*samples_value=*/4000.0f, /*clipped_ratio=*/0.8f, + audio_buffer); + + // Simulate 4 seconds of clipping; it is expected to trigger a downward + // adjustment of the analog gain. + CallPreProcessAndProcess( + /*num_calls=*/400, audio_buffer, + /*speech_probability_override=*/0.7f, + /*speech_probability_level=*/-18.0f, *manager); + CallPreProcessAndProcess( + /*num_calls=*/400, audio_buffer, + /*speech_probability_override=*/absl::optional(0.7f), + /*speech_probability_level=*/absl::optional(-18.0f), + *manager_with_override); + + // Make sure that an adaptation occurred. + ASSERT_GT(manager->recommended_analog_level(), 0); + + // Check that the test signal triggers a larger downward adaptation for + // `manager`, which is allowed to reach a lower gain. + EXPECT_GT(manager_with_override->recommended_analog_level(), + 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); +} + // Checks that, when the "WebRTC-Audio-AgcMinMicLevelExperiment" 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 @@ -1333,8 +1660,12 @@ TEST(AgcManagerDirectTest, // Simulate 4 seconds of clipping; it is expected to trigger a downward // adjustment of the analog gain. - CallPreProcessAndProcess(/*num_calls=*/400, audio_buffer, *manager); CallPreProcessAndProcess(/*num_calls=*/400, audio_buffer, + /*speech_probability_override=*/absl::nullopt, + /*speech_level_override=*/absl::nullopt, *manager); + CallPreProcessAndProcess(/*num_calls=*/400, audio_buffer, + /*speech_probability_override=*/absl::nullopt, + /*speech_level_override=*/absl::nullopt, *manager_with_override); // Make sure that an adaptation occurred. @@ -1350,11 +1681,82 @@ TEST(AgcManagerDirectTest, kDefaultAnalogConfig.clipped_level_min); } +// Checks that, when the "WebRTC-Audio-AgcMinMicLevelExperiment" 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. +// TODO(webrtc:7494): Revisit the test after moving the number of override wait +// frames to APM config. The test passes but internally the gain update timing +// differs. +TEST(AgcManagerDirectTest, + AgcMinMicLevelExperimentCompareMicLevelWithClippingWithRmsErrorOverride) { + // Create and initialize two AGCs by specifying and leaving unspecified the + // relevant field trial. + const auto factory = []() { + // Use a large clipped level step to more quickly decrease the analog gain + // with clipping. + AnalogAgcConfig config = kDefaultAnalogConfig; + config.startup_min_volume = kInitialInputVolume; + config.enable_digital_adaptive = false; + config.clipped_level_step = 64; + config.clipped_ratio_threshold = kClippedRatioThreshold; + config.clipped_wait_frames = kClippedWaitFrames; + auto controller = + std::make_unique(/*num_capture_channels=*/1, config); + controller->Initialize(); + controller->set_stream_analog_level(kInitialInputVolume); + return controller; + }; + std::unique_ptr manager = factory(); + std::unique_ptr manager_with_override; + { + constexpr int kMinMicLevelOverride = 20; + static_assert( + kDefaultAnalogConfig.clipped_level_min >= kMinMicLevelOverride, + "Use a lower override value."); + test::ScopedFieldTrials field_trial( + GetAgcMinMicLevelExperimentFieldTrialEnabled(kMinMicLevelOverride)); + manager_with_override = factory(); + } + + // Create a test input signal which containts 80% of clipped samples. + AudioBuffer audio_buffer(kSampleRateHz, 1, kSampleRateHz, 1, kSampleRateHz, + 1); + WriteAudioBufferSamples(/*samples_value=*/4000.0f, /*clipped_ratio=*/0.8f, + audio_buffer); + + CallPreProcessAndProcess( + /*num_calls=*/400, audio_buffer, + /*speech_probability_override=*/absl::optional(0.7f), + /*speech_level_override=*/absl::optional(-18.0f), *manager); + CallPreProcessAndProcess( + /*num_calls=*/400, audio_buffer, + /*speech_probability_override=*/absl::optional(0.7f), + /*speech_level_override=*/absl::optional(-18.0f), + *manager_with_override); + + // Make sure that an adaptation occurred. + ASSERT_GT(manager->recommended_analog_level(), 0); + + // Check that the selected analog gain is the same for both controllers and + // that it equals the minimum level reached when clipping is handled. That is + // expected because the minimum microphone level override is less than the + // minimum level used when clipping is detected. + EXPECT_EQ(manager->recommended_analog_level(), + manager_with_override->recommended_analog_level()); + EXPECT_EQ(manager_with_override->recommended_analog_level(), + kDefaultAnalogConfig.clipped_level_min); +} + // TODO(bugs.webrtc.org/12774): Test the bahavior of `clipped_level_step`. // TODO(bugs.webrtc.org/12774): Test the bahavior of `clipped_ratio_threshold`. // TODO(bugs.webrtc.org/12774): Test the bahavior of `clipped_wait_frames`. // Verifies that configurable clipping parameters are initialized as intended. TEST_P(AgcManagerDirectParametrizedTest, ClippingParametersVerified) { + if (IsRmsErrorOverridden()) { + GTEST_SKIP() << "Skipped. RMS error override does not affect the test."; + } + std::unique_ptr manager = CreateAgcManagerDirect(kInitialInputVolume, kClippedLevelStep, kClippedRatioThreshold, kClippedWaitFrames); @@ -1375,6 +1777,10 @@ TEST_P(AgcManagerDirectParametrizedTest, ClippingParametersVerified) { TEST_P(AgcManagerDirectParametrizedTest, DisableClippingPredictorDisablesClippingPredictor) { + if (IsRmsErrorOverridden()) { + GTEST_SKIP() << "Skipped. RMS error override does not affect the test."; + } + // TODO(bugs.webrtc.org/12874): Use designated initializers once fixed. ClippingPredictorConfig config; config.enabled = false; @@ -1388,12 +1794,20 @@ TEST_P(AgcManagerDirectParametrizedTest, } TEST_P(AgcManagerDirectParametrizedTest, ClippingPredictorDisabledByDefault) { + if (IsRmsErrorOverridden()) { + GTEST_SKIP() << "Skipped. RMS error override does not affect the test."; + } + constexpr ClippingPredictorConfig kDefaultConfig; EXPECT_FALSE(kDefaultConfig.enabled); } TEST_P(AgcManagerDirectParametrizedTest, EnableClippingPredictorEnablesClippingPredictor) { + if (IsRmsErrorOverridden()) { + GTEST_SKIP() << "Skipped. RMS error override does not affect the test."; + } + // TODO(bugs.webrtc.org/12874): Use designated initializers once fixed. ClippingPredictorConfig config; config.enabled = true; @@ -1420,7 +1834,8 @@ TEST_P(AgcManagerDirectParametrizedTest, EXPECT_FALSE(manager.clipping_predictor_enabled()); EXPECT_FALSE(manager.use_clipping_predictor_step()); EXPECT_EQ(manager.recommended_analog_level(), 255); - manager.Process(audio_buffer); + manager.Process(audio_buffer, GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(kSpeechLevel)); CallPreProcessAudioBuffer(/*num_calls=*/10, /*peak_ratio=*/0.99f, manager); EXPECT_EQ(manager.recommended_analog_level(), 255); CallPreProcessAudioBuffer(/*num_calls=*/300, /*peak_ratio=*/0.99f, manager); @@ -1453,8 +1868,12 @@ TEST_P(AgcManagerDirectParametrizedTest, constexpr float kZeroPeakRatio = 0.0f; manager_with_prediction.set_stream_analog_level(kInitialLevel); manager_without_prediction.set_stream_analog_level(kInitialLevel); - manager_with_prediction.Process(audio_buffer); - manager_without_prediction.Process(audio_buffer); + manager_with_prediction.Process(audio_buffer, + GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(kSpeechLevel)); + manager_without_prediction.Process(audio_buffer, + GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(kSpeechLevel)); EXPECT_TRUE(manager_with_prediction.clipping_predictor_enabled()); EXPECT_FALSE(manager_without_prediction.clipping_predictor_enabled()); EXPECT_TRUE(manager_with_prediction.use_clipping_predictor_step()); @@ -1556,8 +1975,13 @@ TEST_P(AgcManagerDirectParametrizedTest, manager_without_prediction.Initialize(); manager_with_prediction.set_stream_analog_level(kInitialLevel); manager_without_prediction.set_stream_analog_level(kInitialLevel); - manager_with_prediction.Process(audio_buffer); - manager_without_prediction.Process(audio_buffer); + manager_with_prediction.Process(audio_buffer, + GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(kSpeechLevel)); + manager_without_prediction.Process(audio_buffer, + GetOverrideOrEmpty(kHighSpeechProbability), + GetOverrideOrEmpty(kSpeechLevel)); + EXPECT_TRUE(manager_with_prediction.clipping_predictor_enabled()); EXPECT_FALSE(manager_without_prediction.clipping_predictor_enabled()); EXPECT_FALSE(manager_with_prediction.use_clipping_predictor_step()); @@ -1622,4 +2046,90 @@ TEST_P(AgcManagerDirectParametrizedTest, manager_without_prediction.recommended_analog_level()); } +// Checks that passing an empty speech level and probability overrides to +// `Process()` has the same effect as passing no overrides. +TEST_P(AgcManagerDirectParametrizedTest, EmptyRmsErrorOverrideHasNoEffect) { + AgcManagerDirect manager_1(kNumChannels, GetAnalogAgcTestConfig()); + AgcManagerDirect manager_2(kNumChannels, GetAnalogAgcTestConfig()); + manager_1.Initialize(); + manager_2.Initialize(); + + constexpr int kAnalogLevel = 50; + manager_1.set_stream_analog_level(kAnalogLevel); + manager_2.set_stream_analog_level(kAnalogLevel); + + // Feed speech with low energy to trigger an upward adapation of the analog + // level. + constexpr int kNumFrames = 125; + constexpr int kGainDb = -20; + SpeechSamplesReader reader; + + // Check the initial input volume. + ASSERT_EQ(manager_1.recommended_analog_level(), kAnalogLevel); + ASSERT_EQ(manager_2.recommended_analog_level(), kAnalogLevel); + + reader.Feed(kNumFrames, kGainDb, absl::nullopt, absl::nullopt, manager_1); + reader.Feed(kNumFrames, kGainDb, manager_2); + + // Check that the states are the same and adaptation occurs. + EXPECT_EQ(manager_1.recommended_analog_level(), + manager_2.recommended_analog_level()); + ASSERT_GT(manager_1.recommended_analog_level(), kAnalogLevel); + EXPECT_EQ(manager_1.voice_probability(), manager_2.voice_probability()); + EXPECT_EQ(manager_1.frames_since_clipped_, manager_2.frames_since_clipped_); + + // Check that the states of the channel AGCs are the same. + EXPECT_EQ(manager_1.num_channels(), manager_2.num_channels()); + for (int i = 0; i < manager_1.num_channels(); ++i) { + EXPECT_EQ(manager_1.channel_agcs_[i]->recommended_analog_level(), + manager_2.channel_agcs_[i]->recommended_analog_level()); + EXPECT_EQ(manager_1.channel_agcs_[i]->voice_probability(), + manager_2.channel_agcs_[i]->voice_probability()); + } +} + +// Checks that passing a non-empty speech level and probability overrides to +// `Process()` has an effect. +TEST_P(AgcManagerDirectParametrizedTest, NonEmptyRmsErrorOverrideHasEffect) { + AgcManagerDirect manager_1(kNumChannels, GetAnalogAgcTestConfig()); + AgcManagerDirect manager_2(kNumChannels, GetAnalogAgcTestConfig()); + manager_1.Initialize(); + manager_2.Initialize(); + + constexpr int kAnalogLevel = 50; + manager_1.set_stream_analog_level(kAnalogLevel); + manager_2.set_stream_analog_level(kAnalogLevel); + + // Feed speech with low energy to trigger an upward adapation of the analog + // level. + constexpr int kNumFrames = 125; + constexpr int kGainDb = -20; + SpeechSamplesReader reader; + + // Check the initial input volume. + ASSERT_EQ(manager_1.recommended_analog_level(), kAnalogLevel); + ASSERT_EQ(manager_2.recommended_analog_level(), kAnalogLevel); + + reader.Feed(kNumFrames, kGainDb, + absl::optional(kHighSpeechProbability), + absl::optional(kSpeechLevel), manager_1); + reader.Feed(kNumFrames, kGainDb, manager_2); + + // Check that different adaptation occurs. The voice probability estimate from + // AGC is not affected. + ASSERT_GT(manager_1.recommended_analog_level(), kAnalogLevel); + ASSERT_GT(manager_2.recommended_analog_level(), kAnalogLevel); + ASSERT_NE(manager_1.recommended_analog_level(), + manager_2.recommended_analog_level()); + EXPECT_EQ(manager_1.voice_probability(), manager_2.voice_probability()); + + EXPECT_EQ(manager_1.num_channels(), manager_2.num_channels()); + for (int i = 0; i < manager_1.num_channels(); ++i) { + EXPECT_NE(manager_1.channel_agcs_[i]->recommended_analog_level(), + manager_2.channel_agcs_[i]->recommended_analog_level()); + EXPECT_EQ(manager_1.channel_agcs_[i]->voice_probability(), + manager_2.channel_agcs_[i]->voice_probability()); + } +} + } // namespace webrtc