/*
 *  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
