|  | /* | 
|  | *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. | 
|  | * | 
|  | *  Use of this source code is governed by a BSD-style license | 
|  | *  that can be found in the LICENSE file in the root of the source | 
|  | *  tree. An additional intellectual property rights grant can be found | 
|  | *  in the file PATENTS.  All contributing project authors may | 
|  | *  be found in the AUTHORS file in the root of the source tree. | 
|  | */ | 
|  |  | 
|  | #include "modules/audio_processing/agc/agc_manager_direct.h" | 
|  |  | 
|  | #include <fstream> | 
|  | #include <limits> | 
|  | #include <tuple> | 
|  | #include <vector> | 
|  |  | 
|  | #include "modules/audio_processing/agc/gain_control.h" | 
|  | #include "modules/audio_processing/agc/mock_agc.h" | 
|  | #include "modules/audio_processing/include/mock_audio_processing.h" | 
|  | #include "rtc_base/numerics/safe_minmax.h" | 
|  | #include "rtc_base/strings/string_builder.h" | 
|  | #include "test/field_trial.h" | 
|  | #include "test/gmock.h" | 
|  | #include "test/gtest.h" | 
|  | #include "test/testsupport/file_utils.h" | 
|  |  | 
|  | using ::testing::_; | 
|  | using ::testing::AtLeast; | 
|  | using ::testing::DoAll; | 
|  | using ::testing::Return; | 
|  | using ::testing::SetArgPointee; | 
|  |  | 
|  | namespace webrtc { | 
|  | namespace { | 
|  |  | 
|  | constexpr int kSampleRateHz = 32000; | 
|  | constexpr int kNumChannels = 1; | 
|  | constexpr int kInitialInputVolume = 128; | 
|  | constexpr int kClippedMin = 165;  // Arbitrary, but different from the default. | 
|  | constexpr float kAboveClippedThreshold = 0.2f; | 
|  | constexpr int kMinMicLevel = 12; | 
|  | constexpr int kClippedLevelStep = 15; | 
|  | constexpr float kClippedRatioThreshold = 0.1f; | 
|  | constexpr int kClippedWaitFrames = 300; | 
|  | constexpr float kLowSpeechProbability = 0.1f; | 
|  | constexpr float kHighSpeechProbability = 0.7f; | 
|  | constexpr float kSpeechLevelDbfs = -25.0f; | 
|  |  | 
|  | constexpr float kMinSample = std::numeric_limits<int16_t>::min(); | 
|  | constexpr float kMaxSample = std::numeric_limits<int16_t>::max(); | 
|  |  | 
|  | using AnalogAgcConfig = | 
|  | AudioProcessing::Config::GainController1::AnalogGainController; | 
|  | using ClippingPredictorConfig = AudioProcessing::Config::GainController1:: | 
|  | AnalogGainController::ClippingPredictor; | 
|  | constexpr AnalogAgcConfig kDefaultAnalogConfig{}; | 
|  |  | 
|  | class MockGainControl : public GainControl { | 
|  | public: | 
|  | virtual ~MockGainControl() {} | 
|  | MOCK_METHOD(int, set_stream_analog_level, (int level), (override)); | 
|  | MOCK_METHOD(int, stream_analog_level, (), (const, override)); | 
|  | MOCK_METHOD(int, set_mode, (Mode mode), (override)); | 
|  | MOCK_METHOD(Mode, mode, (), (const, override)); | 
|  | MOCK_METHOD(int, set_target_level_dbfs, (int level), (override)); | 
|  | MOCK_METHOD(int, target_level_dbfs, (), (const, override)); | 
|  | MOCK_METHOD(int, set_compression_gain_db, (int gain), (override)); | 
|  | MOCK_METHOD(int, compression_gain_db, (), (const, override)); | 
|  | MOCK_METHOD(int, enable_limiter, (bool enable), (override)); | 
|  | MOCK_METHOD(bool, is_limiter_enabled, (), (const, override)); | 
|  | MOCK_METHOD(int, | 
|  | set_analog_level_limits, | 
|  | (int minimum, int maximum), | 
|  | (override)); | 
|  | MOCK_METHOD(int, analog_level_minimum, (), (const, override)); | 
|  | MOCK_METHOD(int, analog_level_maximum, (), (const, override)); | 
|  | MOCK_METHOD(bool, stream_is_saturated, (), (const, override)); | 
|  | }; | 
|  |  | 
|  | // TODO(bugs.webrtc.org/12874): Remove and use designated initializers once | 
|  | // fixed. | 
|  | std::unique_ptr<AgcManagerDirect> CreateAgcManagerDirect( | 
|  | int startup_min_volume, | 
|  | int clipped_level_step, | 
|  | float clipped_ratio_threshold, | 
|  | int clipped_wait_frames, | 
|  | const ClippingPredictorConfig& clipping_predictor_config = | 
|  | kDefaultAnalogConfig.clipping_predictor) { | 
|  | AnalogAgcConfig config; | 
|  | config.startup_min_volume = startup_min_volume; | 
|  | config.clipped_level_min = kClippedMin; | 
|  | config.enable_digital_adaptive = false; | 
|  | config.clipped_level_step = clipped_level_step; | 
|  | config.clipped_ratio_threshold = clipped_ratio_threshold; | 
|  | config.clipped_wait_frames = clipped_wait_frames; | 
|  | config.clipping_predictor = clipping_predictor_config; | 
|  | return std::make_unique<AgcManagerDirect>(/*num_capture_channels=*/1, config); | 
|  | } | 
|  |  | 
|  | // Deprecated. | 
|  | // TODO(bugs.webrtc.org/7494): Delete this helper, use | 
|  | // `AgcManagerDirectTestHelper::CallAgcSequence()` instead. | 
|  | // Calls `AnalyzePreProcess()` on `manager` `num_calls` times. `peak_ratio` is a | 
|  | // value in [0, 1] which determines the amplitude of the samples (1 maps to full | 
|  | // scale). The first half of the calls is made on frames which are half filled | 
|  | // with zeros in order to simulate a signal with different crest factors. | 
|  | void CallPreProcessAudioBuffer(int num_calls, | 
|  | float peak_ratio, | 
|  | AgcManagerDirect& manager) { | 
|  | RTC_DCHECK_LE(peak_ratio, 1.0f); | 
|  | AudioBuffer audio_buffer(kSampleRateHz, kNumChannels, kSampleRateHz, | 
|  | kNumChannels, kSampleRateHz, kNumChannels); | 
|  | const int num_channels = audio_buffer.num_channels(); | 
|  | const int num_frames = audio_buffer.num_frames(); | 
|  |  | 
|  | // Make half of the calls with half zeroed frames. | 
|  | for (int ch = 0; ch < num_channels; ++ch) { | 
|  | // 50% of the samples in one frame are zero. | 
|  | for (int i = 0; i < num_frames; i += 2) { | 
|  | audio_buffer.channels()[ch][i] = peak_ratio * 32767.0f; | 
|  | audio_buffer.channels()[ch][i + 1] = 0.0f; | 
|  | } | 
|  | } | 
|  | for (int n = 0; n < num_calls / 2; ++n) { | 
|  | manager.AnalyzePreProcess(audio_buffer); | 
|  | } | 
|  |  | 
|  | // Make the remaining half of the calls with frames whose samples are all set. | 
|  | for (int ch = 0; ch < num_channels; ++ch) { | 
|  | for (int i = 0; i < num_frames; ++i) { | 
|  | audio_buffer.channels()[ch][i] = peak_ratio * 32767.0f; | 
|  | } | 
|  | } | 
|  | for (int n = 0; n < num_calls - num_calls / 2; ++n) { | 
|  | manager.AnalyzePreProcess(audio_buffer); | 
|  | } | 
|  | } | 
|  |  | 
|  | constexpr char kMinMicLevelFieldTrial[] = | 
|  | "WebRTC-Audio-2ndAgcMinMicLevelExperiment"; | 
|  |  | 
|  | std::string GetAgcMinMicLevelExperimentFieldTrial(const std::string& value) { | 
|  | char field_trial_buffer[64]; | 
|  | rtc::SimpleStringBuilder builder(field_trial_buffer); | 
|  | builder << kMinMicLevelFieldTrial << "/" << value << "/"; | 
|  | return builder.str(); | 
|  | } | 
|  |  | 
|  | std::string GetAgcMinMicLevelExperimentFieldTrialEnabled( | 
|  | int enabled_value, | 
|  | const std::string& suffix = "") { | 
|  | RTC_DCHECK_GE(enabled_value, 0); | 
|  | RTC_DCHECK_LE(enabled_value, 255); | 
|  | char field_trial_buffer[64]; | 
|  | rtc::SimpleStringBuilder builder(field_trial_buffer); | 
|  | builder << kMinMicLevelFieldTrial << "/Enabled-" << enabled_value << suffix | 
|  | << "/"; | 
|  | return builder.str(); | 
|  | } | 
|  |  | 
|  | std::string GetAgcMinMicLevelExperimentFieldTrial( | 
|  | absl::optional<int> min_mic_level) { | 
|  | if (min_mic_level.has_value()) { | 
|  | return GetAgcMinMicLevelExperimentFieldTrialEnabled(*min_mic_level); | 
|  | } | 
|  | return GetAgcMinMicLevelExperimentFieldTrial("Disabled"); | 
|  | } | 
|  |  | 
|  | // (Over)writes `samples_value` for the samples in `audio_buffer`. | 
|  | // When `clipped_ratio`, a value in [0, 1], is greater than 0, the corresponding | 
|  | // fraction of the frame is set to a full scale value to simulate clipping. | 
|  | void WriteAudioBufferSamples(float samples_value, | 
|  | float clipped_ratio, | 
|  | AudioBuffer& audio_buffer) { | 
|  | RTC_DCHECK_GE(samples_value, kMinSample); | 
|  | RTC_DCHECK_LE(samples_value, kMaxSample); | 
|  | RTC_DCHECK_GE(clipped_ratio, 0.0f); | 
|  | RTC_DCHECK_LE(clipped_ratio, 1.0f); | 
|  | int num_channels = audio_buffer.num_channels(); | 
|  | int num_samples = audio_buffer.num_frames(); | 
|  | int num_clipping_samples = clipped_ratio * num_samples; | 
|  | for (int ch = 0; ch < num_channels; ++ch) { | 
|  | int i = 0; | 
|  | for (; i < num_clipping_samples; ++i) { | 
|  | audio_buffer.channels()[ch][i] = 32767.0f; | 
|  | } | 
|  | for (; i < num_samples; ++i) { | 
|  | audio_buffer.channels()[ch][i] = samples_value; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Deprecated. | 
|  | // TODO(bugs.webrtc.org/7494): Delete this helper, use | 
|  | // `AgcManagerDirectTestHelper::CallAgcSequence()` instead. | 
|  | void CallPreProcessAndProcess(int num_calls, | 
|  | const AudioBuffer& audio_buffer, | 
|  | absl::optional<float> speech_probability_override, | 
|  | absl::optional<float> speech_level_override, | 
|  | AgcManagerDirect& manager) { | 
|  | for (int n = 0; n < num_calls; ++n) { | 
|  | manager.AnalyzePreProcess(audio_buffer); | 
|  | manager.Process(audio_buffer, speech_probability_override, | 
|  | speech_level_override); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Reads a given number of 10 ms chunks from a PCM file and feeds them to | 
|  | // `AgcManagerDirect`. | 
|  | class SpeechSamplesReader { | 
|  | private: | 
|  | // Recording properties. | 
|  | static constexpr int kPcmSampleRateHz = 16000; | 
|  | static constexpr int kPcmNumChannels = 1; | 
|  | static constexpr int kPcmBytesPerSamples = sizeof(int16_t); | 
|  |  | 
|  | public: | 
|  | SpeechSamplesReader() | 
|  | : is_(test::ResourcePath("audio_processing/agc/agc_audio", "pcm"), | 
|  | std::ios::binary | std::ios::ate), | 
|  | audio_buffer_(kPcmSampleRateHz, | 
|  | kPcmNumChannels, | 
|  | kPcmSampleRateHz, | 
|  | kPcmNumChannels, | 
|  | kPcmSampleRateHz, | 
|  | kPcmNumChannels), | 
|  | buffer_(audio_buffer_.num_frames()), | 
|  | buffer_num_bytes_(buffer_.size() * kPcmBytesPerSamples) { | 
|  | RTC_CHECK(is_); | 
|  | } | 
|  |  | 
|  | // 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. | 
|  | void Feed(int num_frames, int gain_db, 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<char*>(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<float>(v) * gain, | 
|  | kMinSample, kMaxSample); | 
|  | }); | 
|  |  | 
|  | agc.AnalyzePreProcess(audio_buffer_); | 
|  | agc.Process(audio_buffer_); | 
|  | } | 
|  | } | 
|  |  | 
|  | // 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<float> speech_probability_override, | 
|  | absl::optional<float> 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<char*>(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<float>(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_; | 
|  | std::vector<int16_t> buffer_; | 
|  | const std::streamsize buffer_num_bytes_; | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | // TODO(bugs.webrtc.org/12874): Use constexpr struct with designated | 
|  | // initializers once fixed. | 
|  | constexpr AnalogAgcConfig GetAnalogAgcTestConfig() { | 
|  | AnalogAgcConfig config; | 
|  | config.enabled = true; | 
|  | config.startup_min_volume = kInitialInputVolume; | 
|  | config.clipped_level_min = kClippedMin; | 
|  | config.enable_digital_adaptive = true; | 
|  | config.clipped_level_step = kClippedLevelStep; | 
|  | config.clipped_ratio_threshold = kClippedRatioThreshold; | 
|  | config.clipped_wait_frames = kClippedWaitFrames; | 
|  | config.clipping_predictor = kDefaultAnalogConfig.clipping_predictor; | 
|  | return config; | 
|  | } | 
|  |  | 
|  | constexpr AnalogAgcConfig GetDisabledAnalogAgcConfig() { | 
|  | AnalogAgcConfig config = GetAnalogAgcTestConfig(); | 
|  | config.enabled = false; | 
|  | return config; | 
|  | } | 
|  |  | 
|  | // Helper class that provides an `AgcManagerDirect` instance with an injected | 
|  | // `Agc` mock, an `AudioBuffer` instance and `CallAgcSequence()`, a helper | 
|  | // method that runs the `AgcManagerDirect` instance on the `AudioBuffer` one by | 
|  | // sticking to the API contract. | 
|  | class AgcManagerDirectTestHelper { | 
|  | public: | 
|  | // Ctor. Initializes `audio_buffer` with zeros. | 
|  | AgcManagerDirectTestHelper() | 
|  | : audio_buffer(kSampleRateHz, | 
|  | kNumChannels, | 
|  | kSampleRateHz, | 
|  | kNumChannels, | 
|  | kSampleRateHz, | 
|  | kNumChannels), | 
|  | mock_agc(new ::testing::NiceMock<MockAgc>()), | 
|  | manager(GetAnalogAgcTestConfig(), mock_agc) { | 
|  | manager.Initialize(); | 
|  | manager.SetupDigitalGainControl(mock_gain_control); | 
|  | WriteAudioBufferSamples(/*samples_value=*/0.0f, /*clipped_ratio=*/0.0f, | 
|  | audio_buffer); | 
|  | } | 
|  |  | 
|  | // Calls the sequence of `AgcManagerDirect` methods according to the API | 
|  | // contract, namely: | 
|  | // - 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. 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<float> speech_probability_override, | 
|  | absl::optional<float> speech_level_override) { | 
|  | manager.set_stream_analog_level(applied_input_volume); | 
|  | manager.AnalyzePreProcess(audio_buffer); | 
|  | manager.Process(audio_buffer, speech_probability_override, | 
|  | speech_level_override); | 
|  | absl::optional<int> digital_gain = manager.GetDigitalComressionGain(); | 
|  | if (digital_gain) { | 
|  | mock_gain_control.set_compression_gain_db(*digital_gain); | 
|  | } | 
|  | return manager.recommended_analog_level(); | 
|  | } | 
|  |  | 
|  | // Deprecated. | 
|  | // TODO(bugs.webrtc.org/7494): Let the caller write `audio_buffer` and use | 
|  | // `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<float> speech_probability_override, | 
|  | absl::optional<float> speech_level_override) { | 
|  | for (int i = 0; i < num_calls; ++i) { | 
|  | EXPECT_CALL(*mock_agc, Process(_)).WillOnce(Return()); | 
|  | manager.Process(audio_buffer, speech_probability_override, | 
|  | speech_level_override); | 
|  | absl::optional<int> new_digital_gain = manager.GetDigitalComressionGain(); | 
|  | if (new_digital_gain) { | 
|  | mock_gain_control.set_compression_gain_db(*new_digital_gain); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Deprecated. | 
|  | // TODO(bugs.webrtc.org/7494): Let the caller write `audio_buffer` and use | 
|  | // `CallAgcSequence()`. | 
|  | void CallPreProc(int num_calls, float clipped_ratio) { | 
|  | RTC_DCHECK_GE(clipped_ratio, 0.0f); | 
|  | RTC_DCHECK_LE(clipped_ratio, 1.0f); | 
|  | WriteAudioBufferSamples(/*samples_value=*/0.0f, clipped_ratio, | 
|  | audio_buffer); | 
|  | for (int i = 0; i < num_calls; ++i) { | 
|  | manager.AnalyzePreProcess(audio_buffer); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Deprecated. | 
|  | // TODO(bugs.webrtc.org/7494): Let the caller write `audio_buffer` and use | 
|  | // `CallAgcSequence()`. | 
|  | void CallPreProcForChangingAudio(int num_calls, float peak_ratio) { | 
|  | RTC_DCHECK_GE(peak_ratio, 0.0f); | 
|  | RTC_DCHECK_LE(peak_ratio, 1.0f); | 
|  | const float samples_value = peak_ratio * 32767.0f; | 
|  |  | 
|  | // Make half of the calls on a frame where the samples alternate | 
|  | // `sample_values` and zeros. | 
|  | WriteAudioBufferSamples(samples_value, /*clipped_ratio=*/0.0f, | 
|  | audio_buffer); | 
|  | for (size_t ch = 0; ch < audio_buffer.num_channels(); ++ch) { | 
|  | for (size_t k = 1; k < audio_buffer.num_frames(); k += 2) { | 
|  | audio_buffer.channels()[ch][k] = 0.0f; | 
|  | } | 
|  | } | 
|  | for (int i = 0; i < num_calls / 2; ++i) { | 
|  | manager.AnalyzePreProcess(audio_buffer); | 
|  | } | 
|  |  | 
|  | // Make half of thecalls on a frame where all the samples equal | 
|  | // `sample_values`. | 
|  | WriteAudioBufferSamples(samples_value, /*clipped_ratio=*/0.0f, | 
|  | audio_buffer); | 
|  | for (int i = 0; i < num_calls - num_calls / 2; ++i) { | 
|  | manager.AnalyzePreProcess(audio_buffer); | 
|  | } | 
|  | } | 
|  |  | 
|  | AudioBuffer audio_buffer; | 
|  | MockAgc* mock_agc; | 
|  | AgcManagerDirect manager; | 
|  | MockGainControl mock_gain_control; | 
|  | }; | 
|  |  | 
|  | class AgcManagerDirectParametrizedTest | 
|  | : public ::testing::TestWithParam<std::tuple<absl::optional<int>, bool>> { | 
|  | protected: | 
|  | AgcManagerDirectParametrizedTest() | 
|  | : field_trials_( | 
|  | GetAgcMinMicLevelExperimentFieldTrial(std::get<0>(GetParam()))) {} | 
|  |  | 
|  | 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<float> GetOverrideOrEmpty(float value) const { | 
|  | return IsRmsErrorOverridden() ? absl::optional<float>(value) | 
|  | : absl::nullopt; | 
|  | } | 
|  |  | 
|  | private: | 
|  | test::ScopedFieldTrials field_trials_; | 
|  | }; | 
|  |  | 
|  | 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, | 
|  | GetDisabledAnalogAgcConfig()); | 
|  | manager_no_analog_agc.Initialize(); | 
|  | AgcManagerDirect manager_with_analog_agc(kNumChannels, | 
|  | GetAnalogAgcTestConfig()); | 
|  | manager_with_analog_agc.Initialize(); | 
|  |  | 
|  | AudioBuffer audio_buffer(kSampleRateHz, kNumChannels, kSampleRateHz, | 
|  | kNumChannels, kSampleRateHz, kNumChannels); | 
|  |  | 
|  | constexpr int kAnalogLevel = 250; | 
|  | static_assert(kAnalogLevel > kInitialInputVolume, "Increase `kAnalogLevel`."); | 
|  | manager_no_analog_agc.set_stream_analog_level(kAnalogLevel); | 
|  | manager_with_analog_agc.set_stream_analog_level(kAnalogLevel); | 
|  |  | 
|  | // Make a first call with input that doesn't clip in order to let the | 
|  | // controller read the input volume. That is needed because clipping input | 
|  | // causes the controller to stay in idle state for | 
|  | // `AnalogAgcConfig::clipped_wait_frames` frames. | 
|  | WriteAudioBufferSamples(/*samples_value=*/0.0f, /*clipping_ratio=*/0.0f, | 
|  | audio_buffer); | 
|  | manager_no_analog_agc.AnalyzePreProcess(audio_buffer); | 
|  | manager_with_analog_agc.AnalyzePreProcess(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, | 
|  | 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. | 
|  | EXPECT_EQ(manager_no_analog_agc.recommended_analog_level(), kAnalogLevel); | 
|  | ASSERT_LT(manager_with_analog_agc.recommended_analog_level(), kAnalogLevel); | 
|  | } | 
|  |  | 
|  | // 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()); | 
|  | manager_no_analog_agc.Initialize(); | 
|  | AgcManagerDirect manager_with_analog_agc(kNumChannels, | 
|  | GetAnalogAgcTestConfig()); | 
|  | manager_with_analog_agc.Initialize(); | 
|  |  | 
|  | constexpr int kAnalogLevel = kInitialInputVolume; | 
|  | manager_no_analog_agc.set_stream_analog_level(kAnalogLevel); | 
|  | manager_with_analog_agc.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; | 
|  | 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. | 
|  | EXPECT_EQ(manager_no_analog_agc.recommended_analog_level(), kAnalogLevel); | 
|  | ASSERT_GT(manager_with_analog_agc.recommended_analog_level(), kAnalogLevel); | 
|  | } | 
|  |  | 
|  | TEST_P(AgcManagerDirectParametrizedTest, | 
|  | StartupMinVolumeConfigurationIsRespected) { | 
|  | AgcManagerDirectTestHelper helper; | 
|  | helper.CallAgcSequence(kInitialInputVolume, | 
|  | GetOverrideOrEmpty(kHighSpeechProbability), | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  | EXPECT_EQ(kInitialInputVolume, helper.manager.recommended_analog_level()); | 
|  | } | 
|  |  | 
|  | TEST_P(AgcManagerDirectParametrizedTest, MicVolumeResponseToRmsError) { | 
|  | const auto speech_probability_override = | 
|  | GetOverrideOrEmpty(kHighSpeechProbability); | 
|  |  | 
|  | AgcManagerDirectTestHelper helper; | 
|  | helper.CallAgcSequence(kInitialInputVolume, speech_probability_override, | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | // Compressor default; no residual error. | 
|  | EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) | 
|  | .WillOnce(DoAll(SetArgPointee<0>(5), Return(true))); | 
|  | 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, 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, 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, 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, speech_probability_override, | 
|  | GetOverrideOrEmpty(-23.0f)); | 
|  | EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) | 
|  | .WillOnce(DoAll(SetArgPointee<0>(0), Return(true))); | 
|  | 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, 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, 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, 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, speech_probability_override, | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | // Maximum upwards change is limited. | 
|  | EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) | 
|  | .WillOnce(DoAll(SetArgPointee<0>(30), Return(true))); | 
|  | 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, speech_probability_override, | 
|  | GetOverrideOrEmpty(22.0f)); | 
|  | EXPECT_EQ(std::max(12, GetMinMicLevel()), | 
|  | helper.manager.recommended_analog_level()); | 
|  | } | 
|  |  | 
|  | TEST_P(AgcManagerDirectParametrizedTest, CompressorStepsTowardsTarget) { | 
|  | constexpr absl::optional<float> kNoOverride = absl::nullopt; | 
|  | const auto speech_probability_override = | 
|  | GetOverrideOrEmpty(kHighSpeechProbability); | 
|  |  | 
|  | AgcManagerDirectTestHelper helper; | 
|  | helper.CallAgcSequence(kInitialInputVolume, speech_probability_override, | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | // 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=*/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=*/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, kNoOverride, kNoOverride); | 
|  |  | 
|  | EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0); | 
|  | 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, kNoOverride, kNoOverride); | 
|  |  | 
|  | EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0); | 
|  | 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=*/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, 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=*/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, kNoOverride, kNoOverride); | 
|  |  | 
|  | EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0); | 
|  | helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride); | 
|  | } | 
|  |  | 
|  | TEST_P(AgcManagerDirectParametrizedTest, CompressorErrorIsDeemphasized) { | 
|  | constexpr absl::optional<float> kNoOverride = absl::nullopt; | 
|  | const auto speech_probability_override = | 
|  | GetOverrideOrEmpty(kHighSpeechProbability); | 
|  |  | 
|  | AgcManagerDirectTestHelper helper; | 
|  | helper.CallAgcSequence(kInitialInputVolume, speech_probability_override, | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) | 
|  | .WillOnce(DoAll(SetArgPointee<0>(10), Return(true))) | 
|  | .WillRepeatedly(Return(false)); | 
|  | 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, kNoOverride, kNoOverride); | 
|  | EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(9)) | 
|  | .WillOnce(Return(0)); | 
|  | helper.CallProcess(/*num_calls=*/1, kNoOverride, kNoOverride); | 
|  | EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0); | 
|  | 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=*/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, kNoOverride, kNoOverride); | 
|  | EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(7)) | 
|  | .WillOnce(Return(0)); | 
|  | 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, kNoOverride, kNoOverride); | 
|  | EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(_)).Times(0); | 
|  | helper.CallProcess(/*num_calls=*/20, kNoOverride, kNoOverride); | 
|  | } | 
|  |  | 
|  | TEST_P(AgcManagerDirectParametrizedTest, CompressorReachesMaximum) { | 
|  | constexpr absl::optional<float> kNoOverride = absl::nullopt; | 
|  | const auto speech_probability_override = | 
|  | GetOverrideOrEmpty(kHighSpeechProbability); | 
|  |  | 
|  | AgcManagerDirectTestHelper helper; | 
|  | helper.CallAgcSequence(kInitialInputVolume, speech_probability_override, | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) | 
|  | .WillOnce(DoAll(SetArgPointee<0>(10), Return(true))) | 
|  | .WillOnce(DoAll(SetArgPointee<0>(10), Return(true))) | 
|  | .WillOnce(DoAll(SetArgPointee<0>(10), Return(true))) | 
|  | .WillOnce(DoAll(SetArgPointee<0>(10), Return(true))) | 
|  | .WillRepeatedly(Return(false)); | 
|  | 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, kNoOverride, kNoOverride); | 
|  | EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(9)) | 
|  | .WillOnce(Return(0)); | 
|  | 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, kNoOverride, kNoOverride); | 
|  | EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(11)) | 
|  | .WillOnce(Return(0)); | 
|  | 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, kNoOverride, kNoOverride); | 
|  | } | 
|  |  | 
|  | TEST_P(AgcManagerDirectParametrizedTest, CompressorReachesMinimum) { | 
|  | constexpr absl::optional<float> kNoOverride = absl::nullopt; | 
|  | const auto speech_probability_override = | 
|  | GetOverrideOrEmpty(kHighSpeechProbability); | 
|  |  | 
|  | AgcManagerDirectTestHelper helper; | 
|  | helper.CallAgcSequence(kInitialInputVolume, speech_probability_override, | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) | 
|  | .WillOnce(DoAll(SetArgPointee<0>(0), Return(true))) | 
|  | .WillOnce(DoAll(SetArgPointee<0>(0), Return(true))) | 
|  | .WillOnce(DoAll(SetArgPointee<0>(0), Return(true))) | 
|  | .WillOnce(DoAll(SetArgPointee<0>(0), Return(true))) | 
|  | .WillRepeatedly(Return(false)); | 
|  | 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, kNoOverride, kNoOverride); | 
|  | EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(5)) | 
|  | .WillOnce(Return(0)); | 
|  | 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, kNoOverride, kNoOverride); | 
|  | EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(3)) | 
|  | .WillOnce(Return(0)); | 
|  | 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, kNoOverride, kNoOverride); | 
|  | } | 
|  |  | 
|  | TEST_P(AgcManagerDirectParametrizedTest, NoActionWhileMuted) { | 
|  | AgcManagerDirectTestHelper helper; | 
|  | helper.CallAgcSequence(kInitialInputVolume, | 
|  | GetOverrideOrEmpty(kHighSpeechProbability), | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | helper.manager.HandleCaptureOutputUsedChange(false); | 
|  | helper.manager.Process(helper.audio_buffer, | 
|  | GetOverrideOrEmpty(kHighSpeechProbability), | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | absl::optional<int> new_digital_gain = | 
|  | helper.manager.GetDigitalComressionGain(); | 
|  | if (new_digital_gain) { | 
|  | helper.mock_gain_control.set_compression_gain_db(*new_digital_gain); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_P(AgcManagerDirectParametrizedTest, UnmutingChecksVolumeWithoutRaising) { | 
|  | AgcManagerDirectTestHelper helper; | 
|  | helper.CallAgcSequence(kInitialInputVolume, | 
|  | GetOverrideOrEmpty(kHighSpeechProbability), | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | helper.manager.HandleCaptureOutputUsedChange(false); | 
|  | helper.manager.HandleCaptureOutputUsedChange(true); | 
|  |  | 
|  | constexpr int kInputVolume = 127; | 
|  | helper.manager.set_stream_analog_level(kInputVolume); | 
|  | EXPECT_CALL(*helper.mock_agc, Reset()); | 
|  |  | 
|  | // SetMicVolume should not be called. | 
|  | EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)).WillOnce(Return(false)); | 
|  | helper.CallProcess(/*num_calls=*/1, | 
|  | GetOverrideOrEmpty(kHighSpeechProbability), | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  | EXPECT_EQ(127, helper.manager.recommended_analog_level()); | 
|  | } | 
|  |  | 
|  | TEST_P(AgcManagerDirectParametrizedTest, UnmutingRaisesTooLowVolume) { | 
|  | AgcManagerDirectTestHelper helper; | 
|  | helper.CallAgcSequence(kInitialInputVolume, | 
|  | GetOverrideOrEmpty(kHighSpeechProbability), | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | helper.manager.HandleCaptureOutputUsedChange(false); | 
|  | helper.manager.HandleCaptureOutputUsedChange(true); | 
|  |  | 
|  | constexpr int kInputVolume = 11; | 
|  | helper.manager.set_stream_analog_level(kInputVolume); | 
|  | EXPECT_CALL(*helper.mock_agc, Reset()); | 
|  |  | 
|  | EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)).WillOnce(Return(false)); | 
|  | helper.CallProcess(/*num_calls=*/1, | 
|  | GetOverrideOrEmpty(kHighSpeechProbability), | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  | EXPECT_EQ(GetMinMicLevel(), helper.manager.recommended_analog_level()); | 
|  | } | 
|  |  | 
|  | TEST_P(AgcManagerDirectParametrizedTest, | 
|  | ManualLevelChangeResultsInNoSetMicCall) { | 
|  | const auto speech_probability_override = | 
|  | GetOverrideOrEmpty(kHighSpeechProbability); | 
|  |  | 
|  | AgcManagerDirectTestHelper helper; | 
|  | helper.CallAgcSequence(kInitialInputVolume, speech_probability_override, | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | // Change outside of compressor's range, which would normally trigger a call | 
|  | // to `SetMicVolume()`. | 
|  | EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) | 
|  | .WillOnce(DoAll(SetArgPointee<0>(11), Return(true))); | 
|  |  | 
|  | // When the analog volume changes, the gain controller is reset. | 
|  | EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1)); | 
|  |  | 
|  | // GetMicVolume returns a value outside of the quantization slack, indicating | 
|  | // a manual volume change. | 
|  | ASSERT_NE(helper.manager.recommended_analog_level(), 154); | 
|  | helper.manager.set_stream_analog_level(154); | 
|  | 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. | 
|  | EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) | 
|  | .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, 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, 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, speech_probability_override, | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | // 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, speech_probability_override, | 
|  | GetOverrideOrEmpty(-48.0f)); | 
|  | EXPECT_EQ(183, helper.manager.recommended_analog_level()); | 
|  | helper.CallProcess(/*num_calls=*/1, speech_probability_override, | 
|  | GetOverrideOrEmpty(-48.0f)); | 
|  | EXPECT_EQ(243, helper.manager.recommended_analog_level()); | 
|  | 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. | 
|  | EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) | 
|  | .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, 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, 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_P(AgcManagerDirectParametrizedTest, | 
|  | RecoveryAfterManualLevelChangeBelowMinWithoutMiMicLevelnOverride) { | 
|  | if (IsMinMicLevelOverridden()) { | 
|  | GTEST_SKIP() << "Skipped. Min mic level overridden."; | 
|  | } | 
|  |  | 
|  | const auto speech_probability_override = | 
|  | GetOverrideOrEmpty(kHighSpeechProbability); | 
|  |  | 
|  | AgcManagerDirectTestHelper helper; | 
|  | helper.CallAgcSequence(kInitialInputVolume, speech_probability_override, | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | // 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, 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, 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, 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, speech_probability_override, | 
|  | GetOverrideOrEmpty(-38.0f)); | 
|  | EXPECT_EQ(18, helper.manager.recommended_analog_level()); | 
|  | } | 
|  |  | 
|  | // Checks that, when the min mic level override is specified, AGC immediately | 
|  | // applies the minimum mic level after the mic level is manually set below the | 
|  | // minimum gain to enforce. | 
|  | TEST_P(AgcManagerDirectParametrizedTest, | 
|  | RecoveryAfterManualLevelChangeBelowMin) { | 
|  | if (!IsMinMicLevelOverridden()) { | 
|  | GTEST_SKIP() << "Skipped. Min mic level not overridden."; | 
|  | } | 
|  |  | 
|  | const auto speech_probability_override = | 
|  | GetOverrideOrEmpty(kHighSpeechProbability); | 
|  |  | 
|  | AgcManagerDirectTestHelper helper; | 
|  | helper.CallAgcSequence(kInitialInputVolume, speech_probability_override, | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | // 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, speech_probability_override, | 
|  | GetOverrideOrEmpty(-17.0f)); | 
|  | EXPECT_EQ(GetMinMicLevel(), helper.manager.recommended_analog_level()); | 
|  | } | 
|  |  | 
|  | TEST_P(AgcManagerDirectParametrizedTest, NoClippingHasNoImpact) { | 
|  | AgcManagerDirectTestHelper helper; | 
|  | helper.CallAgcSequence(kInitialInputVolume, | 
|  | GetOverrideOrEmpty(kHighSpeechProbability), | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | helper.CallPreProc(/*num_calls=*/100, /*clipped_ratio=*/0); | 
|  | EXPECT_EQ(128, helper.manager.recommended_analog_level()); | 
|  | } | 
|  |  | 
|  | TEST_P(AgcManagerDirectParametrizedTest, ClippingUnderThresholdHasNoImpact) { | 
|  | AgcManagerDirectTestHelper helper; | 
|  | helper.CallAgcSequence(kInitialInputVolume, | 
|  | GetOverrideOrEmpty(kHighSpeechProbability), | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/0.099); | 
|  | EXPECT_EQ(128, helper.manager.recommended_analog_level()); | 
|  | } | 
|  |  | 
|  | TEST_P(AgcManagerDirectParametrizedTest, ClippingLowersVolume) { | 
|  | AgcManagerDirectTestHelper helper; | 
|  | helper.CallAgcSequence(/*applied_input_volume=*/255, | 
|  | GetOverrideOrEmpty(kHighSpeechProbability), | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1)); | 
|  | helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/0.2); | 
|  | EXPECT_EQ(240, helper.manager.recommended_analog_level()); | 
|  | } | 
|  |  | 
|  | TEST_P(AgcManagerDirectParametrizedTest, WaitingPeriodBetweenClippingChecks) { | 
|  | AgcManagerDirectTestHelper helper; | 
|  | helper.CallAgcSequence(/*applied_input_volume=*/255, | 
|  | GetOverrideOrEmpty(kHighSpeechProbability), | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1)); | 
|  | helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold); | 
|  | EXPECT_EQ(240, helper.manager.recommended_analog_level()); | 
|  |  | 
|  | EXPECT_CALL(*helper.mock_agc, Reset()).Times(0); | 
|  | helper.CallPreProc(/*num_calls=*/300, | 
|  | /*clipped_ratio=*/kAboveClippedThreshold); | 
|  | EXPECT_EQ(240, helper.manager.recommended_analog_level()); | 
|  |  | 
|  | EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1)); | 
|  | helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold); | 
|  | EXPECT_EQ(225, helper.manager.recommended_analog_level()); | 
|  | } | 
|  |  | 
|  | TEST_P(AgcManagerDirectParametrizedTest, ClippingLoweringIsLimited) { | 
|  | AgcManagerDirectTestHelper helper; | 
|  | helper.CallAgcSequence(/*applied_input_volume=*/180, | 
|  | GetOverrideOrEmpty(kHighSpeechProbability), | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1)); | 
|  | helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold); | 
|  | EXPECT_EQ(kClippedMin, helper.manager.recommended_analog_level()); | 
|  |  | 
|  | EXPECT_CALL(*helper.mock_agc, Reset()).Times(0); | 
|  | helper.CallPreProc(/*num_calls=*/1000, | 
|  | /*clipped_ratio=*/kAboveClippedThreshold); | 
|  | EXPECT_EQ(kClippedMin, helper.manager.recommended_analog_level()); | 
|  | } | 
|  |  | 
|  | TEST_P(AgcManagerDirectParametrizedTest, | 
|  | ClippingMaxIsRespectedWhenEqualToLevel) { | 
|  | const auto speech_probability_override = | 
|  | GetOverrideOrEmpty(kHighSpeechProbability); | 
|  |  | 
|  | AgcManagerDirectTestHelper helper; | 
|  | helper.CallAgcSequence(/*applied_input_volume=*/255, | 
|  | speech_probability_override, | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1)); | 
|  | helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold); | 
|  | EXPECT_EQ(240, helper.manager.recommended_analog_level()); | 
|  |  | 
|  | EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) | 
|  | .WillRepeatedly(DoAll(SetArgPointee<0>(30), Return(true))); | 
|  | 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, | 
|  | speech_probability_override, | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1)); | 
|  | helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold); | 
|  | EXPECT_EQ(185, helper.manager.recommended_analog_level()); | 
|  |  | 
|  | EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) | 
|  | .WillRepeatedly(DoAll(SetArgPointee<0>(40), Return(true))); | 
|  | helper.CallProcess(/*num_calls=*/1, speech_probability_override, | 
|  | GetOverrideOrEmpty(-58.0f)); | 
|  | EXPECT_EQ(240, helper.manager.recommended_analog_level()); | 
|  | 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<float> kNoOverride = absl::nullopt; | 
|  | const auto speech_probability_override = | 
|  | GetOverrideOrEmpty(kHighSpeechProbability); | 
|  |  | 
|  | AgcManagerDirectTestHelper helper; | 
|  | helper.CallAgcSequence(/*applied_input_volume=*/210, | 
|  | speech_probability_override, | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1)); | 
|  | helper.CallPreProc(/*num_calls=*/1, kAboveClippedThreshold); | 
|  | EXPECT_EQ(195, helper.manager.recommended_analog_level()); | 
|  |  | 
|  | EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) | 
|  | .WillOnce(DoAll(SetArgPointee<0>(11), Return(true))) | 
|  | .WillOnce(DoAll(SetArgPointee<0>(11), Return(true))) | 
|  | .WillOnce(DoAll(SetArgPointee<0>(11), Return(true))) | 
|  | .WillOnce(DoAll(SetArgPointee<0>(11), Return(true))) | 
|  | .WillOnce(DoAll(SetArgPointee<0>(11), Return(true))) | 
|  | .WillRepeatedly(Return(false)); | 
|  | 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, kNoOverride, kNoOverride); | 
|  | EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(9)) | 
|  | .WillOnce(Return(0)); | 
|  | 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, kNoOverride, kNoOverride); | 
|  | EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(11)) | 
|  | .WillOnce(Return(0)); | 
|  | 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, kNoOverride, kNoOverride); | 
|  | EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(13)) | 
|  | .WillOnce(Return(0)); | 
|  | helper.CallProcess(/*num_calls=*/1, kNoOverride, kNoOverride); | 
|  |  | 
|  | // Continue clipping until we hit the maximum surplus compression. | 
|  | helper.CallPreProc(/*num_calls=*/300, | 
|  | /*clipped_ratio=*/kAboveClippedThreshold); | 
|  | EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1)); | 
|  | helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold); | 
|  | EXPECT_EQ(180, helper.manager.recommended_analog_level()); | 
|  |  | 
|  | helper.CallPreProc(/*num_calls=*/300, | 
|  | /*clipped_ratio=*/kAboveClippedThreshold); | 
|  | EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1)); | 
|  | helper.CallPreProc(1, kAboveClippedThreshold); | 
|  | EXPECT_EQ(kClippedMin, helper.manager.recommended_analog_level()); | 
|  |  | 
|  | // Current level is now at the minimum, but the maximum allowed level still | 
|  | // has more to decrease. | 
|  | helper.CallPreProc(/*num_calls=*/300, | 
|  | /*clipped_ratio=*/kAboveClippedThreshold); | 
|  | helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold); | 
|  |  | 
|  | helper.CallPreProc(/*num_calls=*/300, | 
|  | /*clipped_ratio=*/kAboveClippedThreshold); | 
|  | helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold); | 
|  |  | 
|  | helper.CallPreProc(/*num_calls=*/300, | 
|  | /*clipped_ratio=*/kAboveClippedThreshold); | 
|  | helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold); | 
|  |  | 
|  | EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) | 
|  | .WillOnce(DoAll(SetArgPointee<0>(16), Return(true))) | 
|  | .WillOnce(DoAll(SetArgPointee<0>(16), Return(true))) | 
|  | .WillOnce(DoAll(SetArgPointee<0>(16), Return(true))) | 
|  | .WillOnce(DoAll(SetArgPointee<0>(16), Return(true))) | 
|  | .WillRepeatedly(Return(false)); | 
|  | 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, kNoOverride, kNoOverride); | 
|  | EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(15)) | 
|  | .WillOnce(Return(0)); | 
|  | 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, kNoOverride, kNoOverride); | 
|  | EXPECT_CALL(helper.mock_gain_control, set_compression_gain_db(17)) | 
|  | .WillOnce(Return(0)); | 
|  | 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, kNoOverride, kNoOverride); | 
|  | } | 
|  |  | 
|  | TEST_P(AgcManagerDirectParametrizedTest, UserCanRaiseVolumeAfterClipping) { | 
|  | const auto speech_probability_override = | 
|  | GetOverrideOrEmpty(kHighSpeechProbability); | 
|  |  | 
|  | AgcManagerDirectTestHelper helper; | 
|  | helper.CallAgcSequence(/*applied_input_volume=*/225, | 
|  | speech_probability_override, | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | EXPECT_CALL(*helper.mock_agc, Reset()).Times(AtLeast(1)); | 
|  | helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold); | 
|  | EXPECT_EQ(210, helper.manager.recommended_analog_level()); | 
|  |  | 
|  | // High enough error to trigger a volume check. | 
|  | EXPECT_CALL(*helper.mock_agc, GetRmsErrorDb(_)) | 
|  | .WillOnce(DoAll(SetArgPointee<0>(14), Return(true))); | 
|  | // 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, 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, 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, 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, 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, | 
|  | GetOverrideOrEmpty(kHighSpeechProbability), | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | EXPECT_CALL(*helper.mock_agc, Reset()).Times(0); | 
|  | int initial_volume = helper.manager.recommended_analog_level(); | 
|  | helper.CallPreProc(/*num_calls=*/1, /*clipped_ratio=*/kAboveClippedThreshold); | 
|  | EXPECT_EQ(initial_volume, helper.manager.recommended_analog_level()); | 
|  | } | 
|  |  | 
|  | TEST_P(AgcManagerDirectParametrizedTest, TakesNoActionOnZeroMicVolume) { | 
|  | AgcManagerDirectTestHelper helper; | 
|  | helper.CallAgcSequence(kInitialInputVolume, | 
|  | GetOverrideOrEmpty(kHighSpeechProbability), | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | 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, | 
|  | 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, | 
|  | GetOverrideOrEmpty(kHighSpeechProbability), | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | EXPECT_EQ(255, helper.manager.recommended_analog_level()); | 
|  | helper.CallPreProcForChangingAudio(/*num_calls=*/100, /*peak_ratio=*/0.99f); | 
|  | EXPECT_EQ(255, helper.manager.recommended_analog_level()); | 
|  | helper.CallPreProcForChangingAudio(/*num_calls=*/100, /*peak_ratio=*/1.0f); | 
|  | EXPECT_EQ(240, helper.manager.recommended_analog_level()); | 
|  | } | 
|  |  | 
|  | TEST_P(AgcManagerDirectParametrizedTest, | 
|  | DisabledClippingPredictorDoesNotLowerVolume) { | 
|  | AgcManagerDirectTestHelper helper; | 
|  | helper.CallAgcSequence(/*applied_input_volume=*/255, | 
|  | GetOverrideOrEmpty(kHighSpeechProbability), | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | EXPECT_FALSE(helper.manager.clipping_predictor_enabled()); | 
|  | EXPECT_EQ(255, helper.manager.recommended_analog_level()); | 
|  | helper.CallPreProcForChangingAudio(/*num_calls=*/100, /*peak_ratio=*/0.99f); | 
|  | EXPECT_EQ(255, helper.manager.recommended_analog_level()); | 
|  | helper.CallPreProcForChangingAudio(/*num_calls=*/100, /*peak_ratio=*/0.99f); | 
|  | EXPECT_EQ(255, helper.manager.recommended_analog_level()); | 
|  | } | 
|  |  | 
|  | TEST_P(AgcManagerDirectParametrizedTest, DisableDigitalDisablesDigital) { | 
|  | if (IsRmsErrorOverridden()) { | 
|  | GTEST_SKIP() << "Skipped. RMS error override does not affect the test."; | 
|  | } | 
|  |  | 
|  | auto agc = std::unique_ptr<Agc>(new ::testing::NiceMock<MockAgc>()); | 
|  | MockGainControl mock_gain_control; | 
|  | EXPECT_CALL(mock_gain_control, set_mode(GainControl::kFixedDigital)); | 
|  | EXPECT_CALL(mock_gain_control, set_target_level_dbfs(0)); | 
|  | EXPECT_CALL(mock_gain_control, set_compression_gain_db(0)); | 
|  | EXPECT_CALL(mock_gain_control, enable_limiter(false)); | 
|  |  | 
|  | AnalogAgcConfig config; | 
|  | config.enable_digital_adaptive = false; | 
|  | auto manager = std::make_unique<AgcManagerDirect>(kNumChannels, config); | 
|  | manager->Initialize(); | 
|  | manager->SetupDigitalGainControl(mock_gain_control); | 
|  | } | 
|  |  | 
|  | TEST(AgcManagerDirectTest, AgcMinMicLevelExperimentDefault) { | 
|  | std::unique_ptr<AgcManagerDirect> manager = | 
|  | CreateAgcManagerDirect(kInitialInputVolume, kClippedLevelStep, | 
|  | kClippedRatioThreshold, kClippedWaitFrames); | 
|  | EXPECT_EQ(manager->channel_agcs_[0]->min_mic_level(), kMinMicLevel); | 
|  | } | 
|  |  | 
|  | TEST(AgcManagerDirectTest, AgcMinMicLevelExperimentDisabled) { | 
|  | for (const std::string& field_trial_suffix : {"", "_20220210"}) { | 
|  | test::ScopedFieldTrials field_trial( | 
|  | GetAgcMinMicLevelExperimentFieldTrial("Disabled" + field_trial_suffix)); | 
|  | std::unique_ptr<AgcManagerDirect> manager = | 
|  | CreateAgcManagerDirect(kInitialInputVolume, kClippedLevelStep, | 
|  | kClippedRatioThreshold, kClippedWaitFrames); | 
|  | EXPECT_EQ(manager->channel_agcs_[0]->min_mic_level(), kMinMicLevel); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Checks that a field-trial parameter outside of the valid range [0,255] is | 
|  | // ignored. | 
|  | TEST(AgcManagerDirectTest, AgcMinMicLevelExperimentOutOfRangeAbove) { | 
|  | test::ScopedFieldTrials field_trial( | 
|  | GetAgcMinMicLevelExperimentFieldTrial("Enabled-256")); | 
|  | std::unique_ptr<AgcManagerDirect> manager = | 
|  | CreateAgcManagerDirect(kInitialInputVolume, kClippedLevelStep, | 
|  | kClippedRatioThreshold, kClippedWaitFrames); | 
|  | EXPECT_EQ(manager->channel_agcs_[0]->min_mic_level(), kMinMicLevel); | 
|  | } | 
|  |  | 
|  | // Checks that a field-trial parameter outside of the valid range [0,255] is | 
|  | // ignored. | 
|  | TEST(AgcManagerDirectTest, AgcMinMicLevelExperimentOutOfRangeBelow) { | 
|  | test::ScopedFieldTrials field_trial( | 
|  | GetAgcMinMicLevelExperimentFieldTrial("Enabled--1")); | 
|  | std::unique_ptr<AgcManagerDirect> manager = | 
|  | CreateAgcManagerDirect(kInitialInputVolume, kClippedLevelStep, | 
|  | kClippedRatioThreshold, kClippedWaitFrames); | 
|  | EXPECT_EQ(manager->channel_agcs_[0]->min_mic_level(), kMinMicLevel); | 
|  | } | 
|  |  | 
|  | // 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(AgcManagerDirectTest, AgcMinMicLevelExperimentEnabled50) { | 
|  | constexpr int kMinMicLevelOverride = 50; | 
|  | for (const std::string& field_trial_suffix : {"", "_20220210"}) { | 
|  | SCOPED_TRACE(field_trial_suffix); | 
|  | test::ScopedFieldTrials field_trial( | 
|  | GetAgcMinMicLevelExperimentFieldTrialEnabled(kMinMicLevelOverride, | 
|  | field_trial_suffix)); | 
|  | std::unique_ptr<AgcManagerDirect> manager = | 
|  | CreateAgcManagerDirect(kInitialInputVolume, kClippedLevelStep, | 
|  | kClippedRatioThreshold, kClippedWaitFrames); | 
|  | EXPECT_EQ(manager->channel_agcs_[0]->min_mic_level(), 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. | 
|  | TEST(AgcManagerDirectTest, AgcMinMicLevelExperimentCheckMinLevelWithClipping) { | 
|  | constexpr int kMinMicLevelOverride = 250; | 
|  |  | 
|  | // Create and initialize two AGCs by specifying and leaving unspecified the | 
|  | // relevant field trial. | 
|  | const auto factory = []() { | 
|  | std::unique_ptr<AgcManagerDirect> manager = | 
|  | CreateAgcManagerDirect(kInitialInputVolume, kClippedLevelStep, | 
|  | kClippedRatioThreshold, kClippedWaitFrames); | 
|  | manager->Initialize(); | 
|  | manager->set_stream_analog_level(kInitialInputVolume); | 
|  | return manager; | 
|  | }; | 
|  | std::unique_ptr<AgcManagerDirect> manager = factory(); | 
|  | std::unique_ptr<AgcManagerDirect> 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=*/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. | 
|  | 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 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<AgcManagerDirect> manager = | 
|  | CreateAgcManagerDirect(kInitialInputVolume, kClippedLevelStep, | 
|  | kClippedRatioThreshold, kClippedWaitFrames); | 
|  | manager->Initialize(); | 
|  | manager->set_stream_analog_level(kInitialInputVolume); | 
|  | return manager; | 
|  | }; | 
|  | std::unique_ptr<AgcManagerDirect> manager = factory(); | 
|  | std::unique_ptr<AgcManagerDirect> 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<float>(0.7f), | 
|  | /*speech_probability_level=*/absl::optional<float>(-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 | 
|  | // is not specified. | 
|  | TEST(AgcManagerDirectTest, | 
|  | AgcMinMicLevelExperimentCompareMicLevelWithClipping) { | 
|  | // 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<AgcManagerDirect>(/*num_capture_channels=*/1, config); | 
|  | controller->Initialize(); | 
|  | controller->set_stream_analog_level(kInitialInputVolume); | 
|  | return controller; | 
|  | }; | 
|  | std::unique_ptr<AgcManagerDirect> manager = factory(); | 
|  | std::unique_ptr<AgcManagerDirect> 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); | 
|  |  | 
|  | // 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=*/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. | 
|  | 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); | 
|  | } | 
|  |  | 
|  | // 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<AgcManagerDirect>(/*num_capture_channels=*/1, config); | 
|  | controller->Initialize(); | 
|  | controller->set_stream_analog_level(kInitialInputVolume); | 
|  | return controller; | 
|  | }; | 
|  | std::unique_ptr<AgcManagerDirect> manager = factory(); | 
|  | std::unique_ptr<AgcManagerDirect> 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<float>(0.7f), | 
|  | /*speech_level_override=*/absl::optional<float>(-18.0f), *manager); | 
|  | CallPreProcessAndProcess( | 
|  | /*num_calls=*/400, audio_buffer, | 
|  | /*speech_probability_override=*/absl::optional<float>(0.7f), | 
|  | /*speech_level_override=*/absl::optional<float>(-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<AgcManagerDirect> manager = | 
|  | CreateAgcManagerDirect(kInitialInputVolume, kClippedLevelStep, | 
|  | kClippedRatioThreshold, kClippedWaitFrames); | 
|  | manager->Initialize(); | 
|  | EXPECT_EQ(manager->clipped_level_step_, kClippedLevelStep); | 
|  | EXPECT_EQ(manager->clipped_ratio_threshold_, kClippedRatioThreshold); | 
|  | EXPECT_EQ(manager->clipped_wait_frames_, kClippedWaitFrames); | 
|  | std::unique_ptr<AgcManagerDirect> manager_custom = | 
|  | CreateAgcManagerDirect(kInitialInputVolume, | 
|  | /*clipped_level_step=*/10, | 
|  | /*clipped_ratio_threshold=*/0.2f, | 
|  | /*clipped_wait_frames=*/50); | 
|  | manager_custom->Initialize(); | 
|  | EXPECT_EQ(manager_custom->clipped_level_step_, 10); | 
|  | EXPECT_EQ(manager_custom->clipped_ratio_threshold_, 0.2f); | 
|  | EXPECT_EQ(manager_custom->clipped_wait_frames_, 50); | 
|  | } | 
|  |  | 
|  | 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; | 
|  |  | 
|  | std::unique_ptr<AgcManagerDirect> manager = CreateAgcManagerDirect( | 
|  | kInitialInputVolume, kClippedLevelStep, kClippedRatioThreshold, | 
|  | kClippedWaitFrames, config); | 
|  | manager->Initialize(); | 
|  | EXPECT_FALSE(manager->clipping_predictor_enabled()); | 
|  | EXPECT_FALSE(manager->use_clipping_predictor_step()); | 
|  | } | 
|  |  | 
|  | 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; | 
|  | config.use_predicted_step = true; | 
|  |  | 
|  | std::unique_ptr<AgcManagerDirect> manager = CreateAgcManagerDirect( | 
|  | kInitialInputVolume, kClippedLevelStep, kClippedRatioThreshold, | 
|  | kClippedWaitFrames, config); | 
|  | manager->Initialize(); | 
|  | EXPECT_TRUE(manager->clipping_predictor_enabled()); | 
|  | EXPECT_TRUE(manager->use_clipping_predictor_step()); | 
|  | } | 
|  |  | 
|  | TEST_P(AgcManagerDirectParametrizedTest, | 
|  | DisableClippingPredictorDoesNotLowerVolume) { | 
|  | AudioBuffer audio_buffer(kSampleRateHz, kNumChannels, kSampleRateHz, | 
|  | kNumChannels, kSampleRateHz, kNumChannels); | 
|  |  | 
|  | AnalogAgcConfig config = GetAnalogAgcTestConfig(); | 
|  | config.clipping_predictor.enabled = false; | 
|  | AgcManagerDirect manager(config, new ::testing::NiceMock<MockAgc>()); | 
|  | manager.Initialize(); | 
|  | manager.set_stream_analog_level(/*level=*/255); | 
|  | EXPECT_FALSE(manager.clipping_predictor_enabled()); | 
|  | EXPECT_FALSE(manager.use_clipping_predictor_step()); | 
|  | EXPECT_EQ(manager.recommended_analog_level(), 255); | 
|  | manager.Process(audio_buffer, GetOverrideOrEmpty(kHighSpeechProbability), | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  | 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); | 
|  | EXPECT_EQ(manager.recommended_analog_level(), 255); | 
|  | CallPreProcessAudioBuffer(/*num_calls=*/10, /*peak_ratio=*/0.99f, manager); | 
|  | EXPECT_EQ(manager.recommended_analog_level(), 255); | 
|  | } | 
|  |  | 
|  | TEST_P(AgcManagerDirectParametrizedTest, | 
|  | UsedClippingPredictionsProduceLowerAnalogLevels) { | 
|  | AudioBuffer audio_buffer(kSampleRateHz, kNumChannels, kSampleRateHz, | 
|  | kNumChannels, kSampleRateHz, kNumChannels); | 
|  |  | 
|  | AnalogAgcConfig config_with_prediction = GetAnalogAgcTestConfig(); | 
|  | config_with_prediction.clipping_predictor.enabled = true; | 
|  | config_with_prediction.clipping_predictor.use_predicted_step = true; | 
|  | AnalogAgcConfig config_without_prediction = GetAnalogAgcTestConfig(); | 
|  | config_without_prediction.clipping_predictor.enabled = false; | 
|  | AgcManagerDirect manager_with_prediction(config_with_prediction, | 
|  | new ::testing::NiceMock<MockAgc>()); | 
|  | AgcManagerDirect manager_without_prediction( | 
|  | config_without_prediction, new ::testing::NiceMock<MockAgc>()); | 
|  |  | 
|  | manager_with_prediction.Initialize(); | 
|  | manager_without_prediction.Initialize(); | 
|  |  | 
|  | constexpr int kInitialLevel = 255; | 
|  | constexpr float kClippingPeakRatio = 1.0f; | 
|  | constexpr float kCloseToClippingPeakRatio = 0.99f; | 
|  | 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, | 
|  | GetOverrideOrEmpty(kHighSpeechProbability), | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  | manager_without_prediction.Process(audio_buffer, | 
|  | GetOverrideOrEmpty(kHighSpeechProbability), | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  | 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()); | 
|  | EXPECT_EQ(manager_with_prediction.recommended_analog_level(), kInitialLevel); | 
|  | EXPECT_EQ(manager_without_prediction.recommended_analog_level(), | 
|  | kInitialLevel); | 
|  |  | 
|  | // Expect a change in the analog level when the prediction step is used. | 
|  | CallPreProcessAudioBuffer(/*num_calls=*/10, kCloseToClippingPeakRatio, | 
|  | manager_with_prediction); | 
|  | CallPreProcessAudioBuffer(/*num_calls=*/10, kCloseToClippingPeakRatio, | 
|  | manager_without_prediction); | 
|  | EXPECT_EQ(manager_with_prediction.recommended_analog_level(), | 
|  | kInitialLevel - kClippedLevelStep); | 
|  | EXPECT_EQ(manager_without_prediction.recommended_analog_level(), | 
|  | kInitialLevel); | 
|  |  | 
|  | // Expect no change during waiting. | 
|  | CallPreProcessAudioBuffer(kClippedWaitFrames, kCloseToClippingPeakRatio, | 
|  | manager_with_prediction); | 
|  | CallPreProcessAudioBuffer(kClippedWaitFrames, kCloseToClippingPeakRatio, | 
|  | manager_without_prediction); | 
|  | EXPECT_EQ(manager_with_prediction.recommended_analog_level(), | 
|  | kInitialLevel - kClippedLevelStep); | 
|  | EXPECT_EQ(manager_without_prediction.recommended_analog_level(), | 
|  | kInitialLevel); | 
|  |  | 
|  | // Expect a change when the prediction step is used. | 
|  | CallPreProcessAudioBuffer(/*num_calls=*/10, kCloseToClippingPeakRatio, | 
|  | manager_with_prediction); | 
|  | CallPreProcessAudioBuffer(/*num_calls=*/10, kCloseToClippingPeakRatio, | 
|  | manager_without_prediction); | 
|  | EXPECT_EQ(manager_with_prediction.recommended_analog_level(), | 
|  | kInitialLevel - 2 * kClippedLevelStep); | 
|  | EXPECT_EQ(manager_without_prediction.recommended_analog_level(), | 
|  | kInitialLevel); | 
|  |  | 
|  | // Expect no change when clipping is not detected or predicted. | 
|  | CallPreProcessAudioBuffer(2 * kClippedWaitFrames, kZeroPeakRatio, | 
|  | manager_with_prediction); | 
|  | CallPreProcessAudioBuffer(2 * kClippedWaitFrames, kZeroPeakRatio, | 
|  | manager_without_prediction); | 
|  | EXPECT_EQ(manager_with_prediction.recommended_analog_level(), | 
|  | kInitialLevel - 2 * kClippedLevelStep); | 
|  | EXPECT_EQ(manager_without_prediction.recommended_analog_level(), | 
|  | kInitialLevel); | 
|  |  | 
|  | // Expect a change for clipping frames. | 
|  | CallPreProcessAudioBuffer(/*num_calls=*/1, kClippingPeakRatio, | 
|  | manager_with_prediction); | 
|  | CallPreProcessAudioBuffer(/*num_calls=*/1, kClippingPeakRatio, | 
|  | manager_without_prediction); | 
|  | EXPECT_EQ(manager_with_prediction.recommended_analog_level(), | 
|  | kInitialLevel - 3 * kClippedLevelStep); | 
|  | EXPECT_EQ(manager_without_prediction.recommended_analog_level(), | 
|  | kInitialLevel - kClippedLevelStep); | 
|  |  | 
|  | // Expect no change during waiting. | 
|  | CallPreProcessAudioBuffer(kClippedWaitFrames, kClippingPeakRatio, | 
|  | manager_with_prediction); | 
|  | CallPreProcessAudioBuffer(kClippedWaitFrames, kClippingPeakRatio, | 
|  | manager_without_prediction); | 
|  | EXPECT_EQ(manager_with_prediction.recommended_analog_level(), | 
|  | kInitialLevel - 3 * kClippedLevelStep); | 
|  | EXPECT_EQ(manager_without_prediction.recommended_analog_level(), | 
|  | kInitialLevel - kClippedLevelStep); | 
|  |  | 
|  | // Expect a change for clipping frames. | 
|  | CallPreProcessAudioBuffer(/*num_calls=*/1, kClippingPeakRatio, | 
|  | manager_with_prediction); | 
|  | CallPreProcessAudioBuffer(/*num_calls=*/1, kClippingPeakRatio, | 
|  | manager_without_prediction); | 
|  | EXPECT_EQ(manager_with_prediction.recommended_analog_level(), | 
|  | kInitialLevel - 4 * kClippedLevelStep); | 
|  | EXPECT_EQ(manager_without_prediction.recommended_analog_level(), | 
|  | kInitialLevel - 2 * kClippedLevelStep); | 
|  | } | 
|  |  | 
|  | TEST_P(AgcManagerDirectParametrizedTest, | 
|  | UnusedClippingPredictionsProduceEqualAnalogLevels) { | 
|  | AudioBuffer audio_buffer(kSampleRateHz, kNumChannels, kSampleRateHz, | 
|  | kNumChannels, kSampleRateHz, kNumChannels); | 
|  |  | 
|  | AnalogAgcConfig config_with_prediction = GetAnalogAgcTestConfig(); | 
|  | config_with_prediction.clipping_predictor.enabled = true; | 
|  | config_with_prediction.clipping_predictor.use_predicted_step = false; | 
|  | AnalogAgcConfig config_without_prediction = GetAnalogAgcTestConfig(); | 
|  | config_without_prediction.clipping_predictor.enabled = false; | 
|  | AgcManagerDirect manager_with_prediction(config_with_prediction, | 
|  | new ::testing::NiceMock<MockAgc>()); | 
|  | AgcManagerDirect manager_without_prediction( | 
|  | config_without_prediction, new ::testing::NiceMock<MockAgc>()); | 
|  |  | 
|  | constexpr int kInitialLevel = 255; | 
|  | constexpr float kClippingPeakRatio = 1.0f; | 
|  | constexpr float kCloseToClippingPeakRatio = 0.99f; | 
|  | constexpr float kZeroPeakRatio = 0.0f; | 
|  | manager_with_prediction.Initialize(); | 
|  | 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, | 
|  | GetOverrideOrEmpty(kHighSpeechProbability), | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  | manager_without_prediction.Process(audio_buffer, | 
|  | GetOverrideOrEmpty(kHighSpeechProbability), | 
|  | GetOverrideOrEmpty(kSpeechLevelDbfs)); | 
|  |  | 
|  | 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()); | 
|  | EXPECT_EQ(manager_with_prediction.recommended_analog_level(), kInitialLevel); | 
|  | EXPECT_EQ(manager_without_prediction.recommended_analog_level(), | 
|  | kInitialLevel); | 
|  |  | 
|  | // Expect no change in the analog level for non-clipping frames. | 
|  | CallPreProcessAudioBuffer(/*num_calls=*/10, kCloseToClippingPeakRatio, | 
|  | manager_with_prediction); | 
|  | CallPreProcessAudioBuffer(/*num_calls=*/10, kCloseToClippingPeakRatio, | 
|  | manager_without_prediction); | 
|  | EXPECT_EQ(manager_with_prediction.recommended_analog_level(), | 
|  | manager_without_prediction.recommended_analog_level()); | 
|  |  | 
|  | // Expect no change for non-clipping frames. | 
|  | CallPreProcessAudioBuffer(kClippedWaitFrames, kCloseToClippingPeakRatio, | 
|  | manager_with_prediction); | 
|  | CallPreProcessAudioBuffer(kClippedWaitFrames, kCloseToClippingPeakRatio, | 
|  | manager_without_prediction); | 
|  | EXPECT_EQ(manager_with_prediction.recommended_analog_level(), | 
|  | manager_without_prediction.recommended_analog_level()); | 
|  |  | 
|  | // Expect no change for non-clipping frames. | 
|  | CallPreProcessAudioBuffer(/*num_calls=*/10, kCloseToClippingPeakRatio, | 
|  | manager_with_prediction); | 
|  | CallPreProcessAudioBuffer(/*num_calls=*/10, kCloseToClippingPeakRatio, | 
|  | manager_without_prediction); | 
|  | EXPECT_EQ(manager_with_prediction.recommended_analog_level(), | 
|  | manager_without_prediction.recommended_analog_level()); | 
|  |  | 
|  | // Expect no change when clipping is not detected or predicted. | 
|  | CallPreProcessAudioBuffer(2 * kClippedWaitFrames, kZeroPeakRatio, | 
|  | manager_with_prediction); | 
|  | CallPreProcessAudioBuffer(2 * kClippedWaitFrames, kZeroPeakRatio, | 
|  | manager_without_prediction); | 
|  | EXPECT_EQ(manager_with_prediction.recommended_analog_level(), | 
|  | manager_without_prediction.recommended_analog_level()); | 
|  |  | 
|  | // Expect a change for clipping frames. | 
|  | CallPreProcessAudioBuffer(/*num_calls=*/1, kClippingPeakRatio, | 
|  | manager_with_prediction); | 
|  | CallPreProcessAudioBuffer(/*num_calls=*/1, kClippingPeakRatio, | 
|  | manager_without_prediction); | 
|  | EXPECT_EQ(manager_with_prediction.recommended_analog_level(), | 
|  | manager_without_prediction.recommended_analog_level()); | 
|  |  | 
|  | // Expect no change during waiting. | 
|  | CallPreProcessAudioBuffer(kClippedWaitFrames, kClippingPeakRatio, | 
|  | manager_with_prediction); | 
|  | CallPreProcessAudioBuffer(kClippedWaitFrames, kClippingPeakRatio, | 
|  | manager_without_prediction); | 
|  | EXPECT_EQ(manager_with_prediction.recommended_analog_level(), | 
|  | manager_without_prediction.recommended_analog_level()); | 
|  |  | 
|  | // Expect a change for clipping frames. | 
|  | CallPreProcessAudioBuffer(/*num_calls=*/1, kClippingPeakRatio, | 
|  | manager_with_prediction); | 
|  | CallPreProcessAudioBuffer(/*num_calls=*/1, kClippingPeakRatio, | 
|  | manager_without_prediction); | 
|  | EXPECT_EQ(manager_with_prediction.recommended_analog_level(), | 
|  | 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 kInputVolume = 128; | 
|  | manager_1.set_stream_analog_level(kInputVolume); | 
|  | manager_2.set_stream_analog_level(kInputVolume); | 
|  |  | 
|  | // Feed speech with low energy to trigger an upward adapation of the input | 
|  | // volume. | 
|  | constexpr int kNumFrames = 125; | 
|  | constexpr int kGainDb = -20; | 
|  | SpeechSamplesReader reader; | 
|  |  | 
|  | // Make sure that the feeding samples triggers an adaptation when no override | 
|  | // is specified. | 
|  | reader.Feed(kNumFrames, kGainDb, manager_1); | 
|  | ASSERT_GT(manager_1.recommended_analog_level(), kInputVolume); | 
|  |  | 
|  | // Expect that feeding samples triggers an adaptation when the speech | 
|  | // probability and speech level overrides are specified. | 
|  | reader.Feed(kNumFrames, kGainDb, | 
|  | /*speech_probability_override=*/kHighSpeechProbability, | 
|  | /*speech_level_override=*/-45.0f, manager_2); | 
|  | EXPECT_GT(manager_2.recommended_analog_level(), kInputVolume); | 
|  |  | 
|  | // The voice probability override does not affect the `voice_probability()` | 
|  | // getter. | 
|  | EXPECT_EQ(manager_1.voice_probability(), manager_2.voice_probability()); | 
|  | } | 
|  |  | 
|  | class AgcManagerDirectChannelSampleRateTest | 
|  | : public ::testing::TestWithParam<std::tuple<int, int>> { | 
|  | protected: | 
|  | int GetNumChannels() const { return std::get<0>(GetParam()); } | 
|  | int GetSampleRateHz() const { return std::get<1>(GetParam()); } | 
|  | }; | 
|  |  | 
|  | TEST_P(AgcManagerDirectChannelSampleRateTest, CheckIsAlive) { | 
|  | const int num_channels = GetNumChannels(); | 
|  | const int sample_rate_hz = GetSampleRateHz(); | 
|  |  | 
|  | constexpr AnalogAgcConfig kConfig{.enabled = true, | 
|  | .clipping_predictor{.enabled = true}}; | 
|  | AgcManagerDirect manager(num_channels, kConfig); | 
|  | manager.Initialize(); | 
|  | AudioBuffer buffer(sample_rate_hz, num_channels, sample_rate_hz, num_channels, | 
|  | sample_rate_hz, num_channels); | 
|  |  | 
|  | constexpr int kStartupVolume = 100; | 
|  | int applied_initial_volume = kStartupVolume; | 
|  |  | 
|  | // Trigger a downward adaptation with clipping. | 
|  | WriteAudioBufferSamples(/*samples_value=*/0.0f, /*clipped_ratio=*/0.5f, | 
|  | buffer); | 
|  | const int initial_volume1 = applied_initial_volume; | 
|  | for (int i = 0; i < 400; ++i) { | 
|  | manager.set_stream_analog_level(applied_initial_volume); | 
|  | manager.AnalyzePreProcess(buffer); | 
|  | manager.Process(buffer, kLowSpeechProbability, | 
|  | /*speech_level_dbfs=*/-20.0f); | 
|  | applied_initial_volume = manager.recommended_analog_level(); | 
|  | } | 
|  | ASSERT_LT(manager.recommended_analog_level(), initial_volume1); | 
|  |  | 
|  | // Fill in audio that does not clip. | 
|  | WriteAudioBufferSamples(/*samples_value=*/1234.5f, /*clipped_ratio=*/0.0f, | 
|  | buffer); | 
|  |  | 
|  | // Trigger an upward adaptation. | 
|  | const int initial_volume2 = manager.recommended_analog_level(); | 
|  | for (int i = 0; i < kConfig.clipped_wait_frames; ++i) { | 
|  | manager.set_stream_analog_level(applied_initial_volume); | 
|  | manager.AnalyzePreProcess(buffer); | 
|  | manager.Process(buffer, kHighSpeechProbability, | 
|  | /*speech_level_dbfs=*/-65.0f); | 
|  | applied_initial_volume = manager.recommended_analog_level(); | 
|  | } | 
|  | EXPECT_GT(manager.recommended_analog_level(), initial_volume2); | 
|  |  | 
|  | // Trigger a downward adaptation. | 
|  | const int initial_volume = manager.recommended_analog_level(); | 
|  | for (int i = 0; i < 100; ++i) { | 
|  | manager.set_stream_analog_level(applied_initial_volume); | 
|  | manager.AnalyzePreProcess(buffer); | 
|  | manager.Process(buffer, kHighSpeechProbability, | 
|  | /*speech_level_dbfs=*/-5.0f); | 
|  | applied_initial_volume = manager.recommended_analog_level(); | 
|  | } | 
|  | EXPECT_LT(manager.recommended_analog_level(), initial_volume); | 
|  | } | 
|  |  | 
|  | INSTANTIATE_TEST_SUITE_P( | 
|  | , | 
|  | AgcManagerDirectChannelSampleRateTest, | 
|  | ::testing::Combine(::testing::Values(1, 2, 3, 6), | 
|  | ::testing::Values(8000, 16000, 32000, 48000))); | 
|  |  | 
|  | }  // namespace webrtc |