APM: Add a field trial for input volume controller

Add a field trial WebRTC-Audio-InputVolumeControllerExperiment and
a mechanism to adjust the config accordingly. Pass the additional
input volume controller config to GainController2.

Bug: webrtc:7494
Change-Id: I3dd624df1f4774cb533417747627995e1f60aa68
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/284101
Reviewed-by: Alessio Bazzica <alessiob@webrtc.org>
Reviewed-by: Per Ã…hgren <peah@webrtc.org>
Reviewed-by: Sam Zackrisson <saza@webrtc.org>
Commit-Queue: Hanna Silen <silen@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#38780}
diff --git a/modules/audio_processing/BUILD.gn b/modules/audio_processing/BUILD.gn
index 8edf6fe..e1f6877 100644
--- a/modules/audio_processing/BUILD.gn
+++ b/modules/audio_processing/BUILD.gn
@@ -193,6 +193,7 @@
     "../../rtc_base:sanitizer",
     "../../rtc_base:swap_queue",
     "../../rtc_base:timeutils",
+    "../../rtc_base/experiments:field_trial_parser",
     "../../rtc_base/synchronization:mutex",
     "../../rtc_base/system:rtc_export",
     "../../system_wrappers",
diff --git a/modules/audio_processing/audio_processing_impl.cc b/modules/audio_processing/audio_processing_impl.cc
index 96193fb..52f2fcb 100644
--- a/modules/audio_processing/audio_processing_impl.cc
+++ b/modules/audio_processing/audio_processing_impl.cc
@@ -31,6 +31,7 @@
 #include "modules/audio_processing/logging/apm_data_dumper.h"
 #include "modules/audio_processing/optionally_built_submodule_creators.h"
 #include "rtc_base/checks.h"
+#include "rtc_base/experiments/field_trial_parser.h"
 #include "rtc_base/logging.h"
 #include "rtc_base/time_utils.h"
 #include "rtc_base/trace_event.h"
@@ -144,8 +145,6 @@
                        audio.channels_const()[0] + audio.num_frames());
 }
 
-constexpr int kUnspecifiedDataDumpInputVolume = -100;
-
 // Options for gracefully handling processing errors.
 enum class FormatErrorOutputOption {
   kOutputExactCopyOfInput,
@@ -326,6 +325,125 @@
   return error_code;
 }
 
+const absl::optional<InputVolumeController::Config>
+GetInputVolumeControllerConfigOverride() {
+  constexpr char kInputVolumeControllerFieldTrial[] =
+      "WebRTC-Audio-InputVolumeControllerExperiment";
+
+  if (!field_trial::IsEnabled(kInputVolumeControllerFieldTrial)) {
+    return absl::nullopt;
+  }
+
+  constexpr InputVolumeController::Config kDefaultConfig;
+
+  FieldTrialFlag enabled("Enabled", false);
+  FieldTrialConstrained<int> clipped_level_min(
+      "clipped_level_min", kDefaultConfig.clipped_level_min, 0, 255);
+  FieldTrialConstrained<int> clipped_level_step(
+      "clipped_level_step", kDefaultConfig.clipped_level_step, 0, 255);
+  FieldTrialConstrained<double> clipped_ratio_threshold(
+      "clipped_ratio_threshold", kDefaultConfig.clipped_ratio_threshold, 0, 1);
+  FieldTrialConstrained<int> clipped_wait_frames(
+      "clipped_wait_frames", kDefaultConfig.clipped_wait_frames, 0,
+      absl::nullopt);
+  FieldTrialParameter<bool> enable_clipping_predictor(
+      "enable_clipping_predictor", kDefaultConfig.enable_clipping_predictor);
+  FieldTrialConstrained<int> target_range_max_dbfs(
+      "target_range_max_dbfs", kDefaultConfig.target_range_max_dbfs, -90, 30);
+  FieldTrialConstrained<int> target_range_min_dbfs(
+      "target_range_min_dbfs", kDefaultConfig.target_range_min_dbfs, -90, 30);
+  FieldTrialConstrained<int> update_input_volume_wait_frames(
+      "update_input_volume_wait_frames",
+      kDefaultConfig.update_input_volume_wait_frames, 0, absl::nullopt);
+  FieldTrialConstrained<double> speech_probability_threshold(
+      "speech_probability_threshold",
+      kDefaultConfig.speech_probability_threshold, 0, 1);
+  FieldTrialConstrained<double> speech_ratio_threshold(
+      "speech_ratio_threshold", kDefaultConfig.speech_ratio_threshold, 0, 1);
+
+  // Field-trial based override for the input volume controller config.
+  const std::string field_trial_name =
+      field_trial::FindFullName(kInputVolumeControllerFieldTrial);
+
+  ParseFieldTrial({&enabled, &clipped_level_min, &clipped_level_step,
+                   &clipped_ratio_threshold, &clipped_wait_frames,
+                   &enable_clipping_predictor, &target_range_max_dbfs,
+                   &target_range_min_dbfs, &update_input_volume_wait_frames,
+                   &speech_probability_threshold, &speech_ratio_threshold},
+                  field_trial_name);
+
+  // Checked already by `IsEnabled()` before parsing, therefore always true.
+  RTC_DCHECK(enabled);
+
+  return InputVolumeController::Config{
+      .clipped_level_min = static_cast<int>(clipped_level_min.Get()),
+      .clipped_level_step = static_cast<int>(clipped_level_step.Get()),
+      .clipped_ratio_threshold =
+          static_cast<float>(clipped_ratio_threshold.Get()),
+      .clipped_wait_frames = static_cast<int>(clipped_wait_frames.Get()),
+      .enable_clipping_predictor =
+          static_cast<bool>(enable_clipping_predictor.Get()),
+      .target_range_max_dbfs = static_cast<int>(target_range_max_dbfs.Get()),
+      .target_range_min_dbfs = static_cast<int>(target_range_min_dbfs.Get()),
+      .update_input_volume_wait_frames =
+          static_cast<int>(update_input_volume_wait_frames.Get()),
+      .speech_probability_threshold =
+          static_cast<float>(speech_probability_threshold.Get()),
+      .speech_ratio_threshold =
+          static_cast<float>(speech_ratio_threshold.Get()),
+  };
+}
+
+// Switches all gain control to AGC2 if experimenting with input volume
+// controller.
+const AudioProcessing::Config AdjustConfig(
+    const AudioProcessing::Config& config,
+    const absl::optional<InputVolumeController::Config>&
+        input_volume_controller_config_override) {
+  const bool analog_agc_enabled =
+      config.gain_controller1.enabled &&
+      (config.gain_controller1.mode ==
+           AudioProcessing::Config::GainController1::kAdaptiveAnalog ||
+       config.gain_controller1.analog_gain_controller.enabled);
+
+  // Do not update the config if none of the analog AGCs is active
+  // regardless of the input volume controller override.
+  if (!analog_agc_enabled ||
+      !input_volume_controller_config_override.has_value()) {
+    return config;
+  }
+
+  const bool hybrid_agc_config_detected =
+      config.gain_controller1.enabled &&
+      config.gain_controller1.analog_gain_controller.enabled &&
+      !config.gain_controller1.analog_gain_controller.enable_digital_adaptive &&
+      config.gain_controller2.enabled &&
+      config.gain_controller2.adaptive_digital.enabled;
+
+  const bool full_agc1_config_detected =
+      config.gain_controller1.enabled &&
+      config.gain_controller1.analog_gain_controller.enabled &&
+      config.gain_controller1.analog_gain_controller.enable_digital_adaptive &&
+      !config.gain_controller2.enabled;
+
+  if (hybrid_agc_config_detected == full_agc1_config_detected ||
+      config.gain_controller2.input_volume_controller.enabled) {
+    RTC_LOG(LS_ERROR) << "Unexpected AGC config: Config not adjusted.";
+    return config;
+  }
+
+  AudioProcessing::Config adjusted_config = config;
+  adjusted_config.gain_controller1.enabled = false;
+  adjusted_config.gain_controller1.analog_gain_controller.enabled = false;
+  adjusted_config.gain_controller2.enabled = true;
+  adjusted_config.gain_controller2.adaptive_digital.enabled = true;
+  adjusted_config.gain_controller2.input_volume_controller.enabled = true;
+
+  return adjusted_config;
+}
+
+constexpr int kUnspecifiedDataDumpInputVolume = -100;
+
 }  // namespace
 
 // Throughout webrtc, it's assumed that success is represented by zero.
@@ -448,6 +566,8 @@
     : data_dumper_(new ApmDataDumper(instance_count_.fetch_add(1) + 1)),
       use_setup_specific_default_aec3_config_(
           UseSetupSpecificDefaultAec3Congfig()),
+      input_volume_controller_config_override_(
+          GetInputVolumeControllerConfigOverride()),
       use_denormal_disabler_(
           !field_trial::IsEnabled("WebRTC-ApmDenormalDisablerKillSwitch")),
       transient_suppressor_vad_mode_(GetTransientSuppressorVadMode()),
@@ -456,7 +576,7 @@
       capture_runtime_settings_enqueuer_(&capture_runtime_settings_),
       render_runtime_settings_enqueuer_(&render_runtime_settings_),
       echo_control_factory_(std::move(echo_control_factory)),
-      config_(config),
+      config_(AdjustConfig(config, input_volume_controller_config_override_)),
       submodule_states_(!!capture_post_processor,
                         !!render_pre_processor,
                         !!capture_analyzer),
@@ -490,6 +610,8 @@
     RTC_LOG(LS_INFO) << "Denormal disabler unsupported";
   }
 
+  RTC_LOG(LS_INFO) << "AudioProcessing: " << config_.ToString();
+
   // Mark Echo Controller enabled if a factory is injected.
   capture_nonlocked_.echo_controller_enabled =
       static_cast<bool>(echo_control_factory_);
@@ -681,46 +803,57 @@
 }
 
 void AudioProcessingImpl::ApplyConfig(const AudioProcessing::Config& config) {
-  RTC_LOG(LS_INFO) << "AudioProcessing::ApplyConfig: " << config.ToString();
-
   // Run in a single-threaded manner when applying the settings.
   MutexLock lock_render(&mutex_render_);
   MutexLock lock_capture(&mutex_capture_);
 
+  // TODO(bugs.webrtc.org/7494): Replace `adjusted_config` with `config` after
+  // "WebRTC-Audio-InputVolumeControllerExperiment" field trial is removed.
+  const auto adjusted_config =
+      AdjustConfig(config, input_volume_controller_config_override_);
+
+  RTC_LOG(LS_INFO) << "AudioProcessing::ApplyConfig: "
+                   << adjusted_config.ToString();
+
   const bool pipeline_config_changed =
       config_.pipeline.multi_channel_render !=
-          config.pipeline.multi_channel_render ||
+          adjusted_config.pipeline.multi_channel_render ||
       config_.pipeline.multi_channel_capture !=
-          config.pipeline.multi_channel_capture ||
+          adjusted_config.pipeline.multi_channel_capture ||
       config_.pipeline.maximum_internal_processing_rate !=
-          config.pipeline.maximum_internal_processing_rate;
+          adjusted_config.pipeline.maximum_internal_processing_rate;
 
   const bool aec_config_changed =
-      config_.echo_canceller.enabled != config.echo_canceller.enabled ||
-      config_.echo_canceller.mobile_mode != config.echo_canceller.mobile_mode;
+      config_.echo_canceller.enabled !=
+          adjusted_config.echo_canceller.enabled ||
+      config_.echo_canceller.mobile_mode !=
+          adjusted_config.echo_canceller.mobile_mode;
 
   const bool agc1_config_changed =
-      config_.gain_controller1 != config.gain_controller1;
+      config_.gain_controller1 != adjusted_config.gain_controller1;
 
   const bool agc2_config_changed =
-      config_.gain_controller2 != config.gain_controller2;
+      config_.gain_controller2 != adjusted_config.gain_controller2;
 
   const bool ns_config_changed =
-      config_.noise_suppression.enabled != config.noise_suppression.enabled ||
-      config_.noise_suppression.level != config.noise_suppression.level;
+      config_.noise_suppression.enabled !=
+          adjusted_config.noise_suppression.enabled ||
+      config_.noise_suppression.level !=
+          adjusted_config.noise_suppression.level;
 
   const bool ts_config_changed = config_.transient_suppression.enabled !=
-                                 config.transient_suppression.enabled;
+                                 adjusted_config.transient_suppression.enabled;
 
   const bool pre_amplifier_config_changed =
-      config_.pre_amplifier.enabled != config.pre_amplifier.enabled ||
+      config_.pre_amplifier.enabled != adjusted_config.pre_amplifier.enabled ||
       config_.pre_amplifier.fixed_gain_factor !=
-          config.pre_amplifier.fixed_gain_factor;
+          adjusted_config.pre_amplifier.fixed_gain_factor;
 
   const bool gain_adjustment_config_changed =
-      config_.capture_level_adjustment != config.capture_level_adjustment;
+      config_.capture_level_adjustment !=
+      adjusted_config.capture_level_adjustment;
 
-  config_ = config;
+  config_ = adjusted_config;
 
   if (aec_config_changed) {
     InitializeEchoController();
@@ -2123,8 +2256,10 @@
     const bool use_internal_vad =
         transient_suppressor_vad_mode_ != TransientSuppressor::VadMode::kRnnVad;
     submodules_.gain_controller2 = std::make_unique<GainController2>(
-        config_.gain_controller2, proc_fullband_sample_rate_hz(),
-        num_input_channels(), use_internal_vad);
+        config_.gain_controller2,
+        input_volume_controller_config_override_.value_or(
+            InputVolumeController::Config{}),
+        proc_fullband_sample_rate_hz(), num_input_channels(), use_internal_vad);
     submodules_.gain_controller2->SetCaptureOutputUsed(
         capture_.capture_output_used);
   }
diff --git a/modules/audio_processing/audio_processing_impl.h b/modules/audio_processing/audio_processing_impl.h
index 191a3ee..9a30c8b 100644
--- a/modules/audio_processing/audio_processing_impl.h
+++ b/modules/audio_processing/audio_processing_impl.h
@@ -160,6 +160,9 @@
                            ReinitializeTransientSuppressor);
   FRIEND_TEST_ALL_PREFIXES(ApmWithSubmodulesExcludedTest,
                            BitexactWithDisabledModules);
+  FRIEND_TEST_ALL_PREFIXES(
+      AudioProcessingImplInputVolumeControllerExperimentParametrizedTest,
+      ConfigAdjustedWhenExperimentEnabled);
 
   void set_stream_analog_level_locked(int level)
       RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_);
@@ -188,6 +191,12 @@
   static std::atomic<int> instance_count_;
   const bool use_setup_specific_default_aec3_config_;
 
+  // TODO(bugs.webrtc.org/7494): Remove the the config when the field trial is
+  // removed. "WebRTC-Audio-InputVolumeControllerExperiment" field trial
+  // override for the input volume controller config.
+  const absl::optional<InputVolumeController::Config>
+      input_volume_controller_config_override_;
+
   const bool use_denormal_disabler_;
 
   const TransientSuppressor::VadMode transient_suppressor_vad_mode_;
diff --git a/modules/audio_processing/audio_processing_impl_unittest.cc b/modules/audio_processing/audio_processing_impl_unittest.cc
index fea7a8c..ea61dae 100644
--- a/modules/audio_processing/audio_processing_impl_unittest.cc
+++ b/modules/audio_processing/audio_processing_impl_unittest.cc
@@ -1188,4 +1188,282 @@
   EXPECT_EQ(ProcessInputVolume(*apm, kOneFrame, /*initial_volume=*/135), 135);
 }
 
+TEST(AudioProcessingImplInputVolumeControllerExperimentTest,
+     ConfigAdjustedWhenExperimentEnabledAndAgc1AnalogEnabled) {
+  webrtc::test::ScopedFieldTrials field_trials(
+      "WebRTC-Audio-InputVolumeControllerExperiment/"
+      "Enabled,"
+      "enable_clipping_predictor:true,"
+      "clipped_level_min:20,"
+      "clipped_level_step:30,"
+      "clipped_ratio_threshold:0.4,"
+      "clipped_wait_frames:50,"
+      "target_range_max_dbfs:-6,"
+      "target_range_min_dbfs:-70,"
+      "update_input_volume_wait_frames:80,"
+      "speech_probability_threshold:0.9,"
+      "speech_ratio_threshold:1.0/");
+
+  AudioProcessingBuilderForTesting apm_builder;
+
+  // Set a config with analog AGC1 enabled.
+  AudioProcessing::Config config;
+  config.gain_controller1.enabled = true;
+  config.gain_controller1.analog_gain_controller.enabled = true;
+  config.gain_controller1.analog_gain_controller.enable_digital_adaptive = true;
+  config.gain_controller2.enabled = false;
+  config.gain_controller1.mode =
+      AudioProcessing::Config::GainController1::kAdaptiveAnalog;
+
+  EXPECT_FALSE(config.gain_controller2.input_volume_controller.enabled);
+
+  apm_builder.SetConfig(config);
+
+  auto apm = apm_builder.Create();
+  auto adjusted_config = apm->GetConfig();
+
+  // Expect the config to be adjusted.
+  EXPECT_FALSE(adjusted_config.gain_controller1.enabled);
+  EXPECT_FALSE(adjusted_config.gain_controller1.analog_gain_controller.enabled);
+  EXPECT_TRUE(adjusted_config.gain_controller2.enabled);
+  EXPECT_TRUE(adjusted_config.gain_controller2.adaptive_digital.enabled);
+  EXPECT_TRUE(adjusted_config.gain_controller2.input_volume_controller.enabled);
+
+  // Change config back and compare.
+  adjusted_config.gain_controller1.enabled = config.gain_controller1.enabled;
+  adjusted_config.gain_controller1.analog_gain_controller.enabled =
+      config.gain_controller1.analog_gain_controller.enabled;
+  adjusted_config.gain_controller2.enabled = config.gain_controller2.enabled;
+  adjusted_config.gain_controller2.adaptive_digital.enabled =
+      config.gain_controller2.adaptive_digital.enabled;
+  adjusted_config.gain_controller2.input_volume_controller.enabled =
+      config.gain_controller2.input_volume_controller.enabled;
+
+  EXPECT_THAT(adjusted_config.ToString(), ::testing::StrEq(config.ToString()));
+}
+
+TEST(AudioProcessingImplInputVolumeControllerExperimentTest,
+     ConfigAdjustedWhenExperimentEnabledAndHybridAgcEnabled) {
+  webrtc::test::ScopedFieldTrials field_trials(
+      "WebRTC-Audio-InputVolumeControllerExperiment/"
+      "Enabled,"
+      "enable_clipping_predictor:true,"
+      "clipped_level_min:20,"
+      "clipped_level_step:30,"
+      "clipped_ratio_threshold:0.4,"
+      "clipped_wait_frames:50,"
+      "target_range_max_dbfs:-6,"
+      "target_range_min_dbfs:-70,"
+      "update_input_volume_wait_frames:80,"
+      "speech_probability_threshold:0.9,"
+      "speech_ratio_threshold:1.0/");
+
+  AudioProcessingBuilderForTesting apm_builder;
+
+  // Set a config with hybrid AGC enabled.
+  AudioProcessing::Config config;
+  config.gain_controller1.enabled = true;
+  config.gain_controller1.analog_gain_controller.enabled = true;
+  config.gain_controller1.analog_gain_controller.enable_digital_adaptive =
+      false;
+  config.gain_controller2.enabled = true;
+  config.gain_controller2.adaptive_digital.enabled = true;
+  config.gain_controller1.mode =
+      AudioProcessing::Config::GainController1::kAdaptiveAnalog;
+
+  EXPECT_FALSE(config.gain_controller2.input_volume_controller.enabled);
+
+  apm_builder.SetConfig(config);
+
+  auto apm = apm_builder.Create();
+  auto adjusted_config = apm->GetConfig();
+
+  // Expect the config to be adjusted.
+  EXPECT_FALSE(adjusted_config.gain_controller1.enabled);
+  EXPECT_FALSE(adjusted_config.gain_controller1.analog_gain_controller.enabled);
+  EXPECT_TRUE(adjusted_config.gain_controller2.enabled);
+  EXPECT_TRUE(adjusted_config.gain_controller2.adaptive_digital.enabled);
+  EXPECT_TRUE(adjusted_config.gain_controller2.input_volume_controller.enabled);
+
+  // Change config back and compare.
+  adjusted_config.gain_controller1.enabled = config.gain_controller1.enabled;
+  adjusted_config.gain_controller1.analog_gain_controller.enabled =
+      config.gain_controller1.analog_gain_controller.enabled;
+  adjusted_config.gain_controller2.enabled = config.gain_controller2.enabled;
+  adjusted_config.gain_controller2.adaptive_digital.enabled =
+      config.gain_controller2.adaptive_digital.enabled;
+  adjusted_config.gain_controller2.input_volume_controller.enabled =
+      config.gain_controller2.input_volume_controller.enabled;
+
+  EXPECT_THAT(adjusted_config.ToString(), ::testing::StrEq(config.ToString()));
+}
+
+TEST(AudioProcessingImplInputVolumeControllerExperimentTest,
+     ConfigNotAdjustedWhenExperimentEnabledAndAgc1AnalogNotEnabled) {
+  webrtc::test::ScopedFieldTrials field_trials(
+      "WebRTC-Audio-InputVolumeControllerExperiment/"
+      "Enabled,"
+      "enable_clipping_predictor:true,"
+      "clipped_level_min:20,"
+      "clipped_level_step:30,"
+      "clipped_ratio_threshold:0.4,"
+      "clipped_wait_frames:50,"
+      "target_range_max_dbfs:-6,"
+      "target_range_min_dbfs:-70,"
+      "update_input_volume_wait_frames:80,"
+      "speech_probability_threshold:0.9,"
+      "speech_ratio_threshold:1.0/");
+
+  AudioProcessingBuilderForTesting apm_builder;
+
+  // Set a config with analog AGC1 not enabled.
+  AudioProcessing::Config config;
+  config.gain_controller1.enabled = false;
+  config.gain_controller1.analog_gain_controller.enabled = true;
+  config.gain_controller1.analog_gain_controller.enable_digital_adaptive = true;
+  config.gain_controller2.enabled = false;
+  config.gain_controller1.mode =
+      AudioProcessing::Config::GainController1::kAdaptiveAnalog;
+
+  EXPECT_FALSE(config.gain_controller2.input_volume_controller.enabled);
+
+  apm_builder.SetConfig(config);
+
+  auto apm = apm_builder.Create();
+  auto adjusted_config = apm->GetConfig();
+
+  EXPECT_EQ(config.gain_controller1.enabled,
+            adjusted_config.gain_controller1.enabled);
+  EXPECT_EQ(config.gain_controller1.analog_gain_controller.enabled,
+            adjusted_config.gain_controller1.analog_gain_controller.enabled);
+  EXPECT_EQ(config.gain_controller2.enabled,
+            adjusted_config.gain_controller2.enabled);
+  EXPECT_EQ(config.gain_controller2.adaptive_digital.enabled,
+            adjusted_config.gain_controller2.adaptive_digital.enabled);
+  EXPECT_FALSE(
+      adjusted_config.gain_controller2.input_volume_controller.enabled);
+
+  EXPECT_THAT(adjusted_config.ToString(), ::testing::StrEq(config.ToString()));
+}
+
+TEST(AudioProcessingImplInputVolumeControllerExperimentTest,
+     ConfigNotAdjustedWhenExperimentEnabledAndHybridAgcNotEnabled) {
+  webrtc::test::ScopedFieldTrials field_trials(
+      "WebRTC-Audio-InputVolumeControllerExperiment/"
+      "Enabled,"
+      "enable_clipping_predictor:true,"
+      "clipped_level_min:20,"
+      "clipped_level_step:30,"
+      "clipped_ratio_threshold:0.4,"
+      "clipped_wait_frames:50,"
+      "target_range_max_dbfs:-6,"
+      "target_range_min_dbfs:-70,"
+      "update_input_volume_wait_frames:80,"
+      "speech_probability_threshold:0.9,"
+      "speech_ratio_threshold:1.0/");
+
+  AudioProcessingBuilderForTesting apm_builder;
+
+  // Set a config with hybrid AGC analog not enabled.
+  AudioProcessing::Config config;
+  config.gain_controller1.enabled = false;
+  config.gain_controller1.analog_gain_controller.enabled = true;
+  config.gain_controller1.analog_gain_controller.enable_digital_adaptive =
+      false;
+  config.gain_controller2.enabled = true;
+  config.gain_controller2.adaptive_digital.enabled = true;
+  config.gain_controller1.mode =
+      AudioProcessing::Config::GainController1::kAdaptiveAnalog;
+
+  EXPECT_FALSE(config.gain_controller2.input_volume_controller.enabled);
+
+  apm_builder.SetConfig(config);
+
+  auto apm = apm_builder.Create();
+  auto adjusted_config = apm->GetConfig();
+
+  EXPECT_EQ(config.gain_controller1.enabled,
+            adjusted_config.gain_controller1.enabled);
+  EXPECT_EQ(config.gain_controller1.analog_gain_controller.enabled,
+            adjusted_config.gain_controller1.analog_gain_controller.enabled);
+  EXPECT_EQ(config.gain_controller2.enabled,
+            adjusted_config.gain_controller2.enabled);
+  EXPECT_EQ(config.gain_controller2.adaptive_digital.enabled,
+            adjusted_config.gain_controller2.adaptive_digital.enabled);
+  EXPECT_FALSE(
+      adjusted_config.gain_controller2.input_volume_controller.enabled);
+
+  EXPECT_THAT(adjusted_config.ToString(), ::testing::StrEq(config.ToString()));
+}
+
+TEST(AudioProcessingImplInputVolumeControllerExperimentTest,
+     ConfigNotAdjustedWhenExperimentNotEnabledAndAgc1AnalogEnabled) {
+  AudioProcessingBuilderForTesting apm_builder;
+
+  // Set a config with analog AGC1 analog enabled.
+  AudioProcessing::Config config;
+  config.gain_controller1.enabled = true;
+  config.gain_controller1.analog_gain_controller.enabled = true;
+  config.gain_controller1.analog_gain_controller.enable_digital_adaptive = true;
+  config.gain_controller2.enabled = false;
+  config.gain_controller1.mode =
+      AudioProcessing::Config::GainController1::kAdaptiveAnalog;
+
+  EXPECT_FALSE(config.gain_controller2.input_volume_controller.enabled);
+
+  apm_builder.SetConfig(config);
+
+  auto apm = apm_builder.Create();
+  auto adjusted_config = apm->GetConfig();
+
+  EXPECT_EQ(config.gain_controller1.enabled,
+            adjusted_config.gain_controller1.enabled);
+  EXPECT_EQ(config.gain_controller1.analog_gain_controller.enabled,
+            adjusted_config.gain_controller1.analog_gain_controller.enabled);
+  EXPECT_EQ(config.gain_controller2.enabled,
+            adjusted_config.gain_controller2.enabled);
+  EXPECT_EQ(config.gain_controller2.adaptive_digital.enabled,
+            adjusted_config.gain_controller2.adaptive_digital.enabled);
+  EXPECT_FALSE(
+      adjusted_config.gain_controller2.input_volume_controller.enabled);
+
+  EXPECT_THAT(adjusted_config.ToString(), ::testing::StrEq(config.ToString()));
+}
+
+TEST(AudioProcessingImplInputVolumeControllerExperimentTest,
+     ConfigNotAdjustedWhenExperimentNotEnabledAndHybridAgcEnabled) {
+  AudioProcessingBuilderForTesting apm_builder;
+
+  // Set a config with hybrid AGC enabled.
+  AudioProcessing::Config config;
+  config.gain_controller1.enabled = true;
+  config.gain_controller1.analog_gain_controller.enabled = true;
+  config.gain_controller1.analog_gain_controller.enable_digital_adaptive =
+      false;
+  config.gain_controller2.enabled = true;
+  config.gain_controller2.adaptive_digital.enabled = true;
+  config.gain_controller1.mode =
+      AudioProcessing::Config::GainController1::kAdaptiveAnalog;
+
+  EXPECT_FALSE(config.gain_controller2.input_volume_controller.enabled);
+
+  apm_builder.SetConfig(config);
+
+  auto apm = apm_builder.Create();
+  auto adjusted_config = apm->GetConfig();
+
+  EXPECT_EQ(config.gain_controller1.enabled,
+            adjusted_config.gain_controller1.enabled);
+  EXPECT_EQ(config.gain_controller1.analog_gain_controller.enabled,
+            adjusted_config.gain_controller1.analog_gain_controller.enabled);
+  EXPECT_EQ(config.gain_controller2.enabled,
+            adjusted_config.gain_controller2.enabled);
+  EXPECT_EQ(config.gain_controller2.adaptive_digital.enabled,
+            adjusted_config.gain_controller2.adaptive_digital.enabled);
+  EXPECT_FALSE(
+      adjusted_config.gain_controller2.input_volume_controller.enabled);
+
+  EXPECT_THAT(adjusted_config.ToString(), ::testing::StrEq(config.ToString()));
+}
+
 }  // namespace webrtc
diff --git a/modules/audio_processing/gain_controller2.cc b/modules/audio_processing/gain_controller2.cc
index 70b598d..2a9c862 100644
--- a/modules/audio_processing/gain_controller2.cc
+++ b/modules/audio_processing/gain_controller2.cc
@@ -27,6 +27,7 @@
 namespace {
 
 using Agc2Config = AudioProcessing::Config::GainController2;
+using InputVolumeControllerConfig = InputVolumeController::Config;
 
 constexpr int kLogLimiterStatsPeriodMs = 30'000;
 constexpr int kFrameLengthMs = 10;
@@ -64,10 +65,10 @@
 // Creates an input volume controller if `enabled` is true.
 std::unique_ptr<InputVolumeController> CreateInputVolumeController(
     bool enabled,
+    const InputVolumeControllerConfig& config,
     int num_channels) {
   if (enabled) {
-    return std::make_unique<InputVolumeController>(
-        num_channels, InputVolumeController::Config());
+    return std::make_unique<InputVolumeController>(num_channels, config);
   }
   return nullptr;
 }
@@ -76,10 +77,12 @@
 
 std::atomic<int> GainController2::instance_count_(0);
 
-GainController2::GainController2(const Agc2Config& config,
-                                 int sample_rate_hz,
-                                 int num_channels,
-                                 bool use_internal_vad)
+GainController2::GainController2(
+    const Agc2Config& config,
+    const InputVolumeControllerConfig& input_volume_controller_config,
+    int sample_rate_hz,
+    int num_channels,
+    bool use_internal_vad)
     : cpu_features_(GetAllowedCpuFeatures()),
       data_dumper_(instance_count_.fetch_add(1) + 1),
       fixed_gain_applier_(
@@ -92,6 +95,7 @@
                                           &data_dumper_)),
       input_volume_controller_(
           CreateInputVolumeController(config.input_volume_controller.enabled,
+                                      input_volume_controller_config,
                                       num_channels)),
       limiter_(sample_rate_hz, &data_dumper_, /*histogram_name_prefix=*/"Agc2"),
       calls_since_last_limiter_log_(0) {
diff --git a/modules/audio_processing/gain_controller2.h b/modules/audio_processing/gain_controller2.h
index 3341cd2..0d41eaa 100644
--- a/modules/audio_processing/gain_controller2.h
+++ b/modules/audio_processing/gain_controller2.h
@@ -34,10 +34,12 @@
  public:
   // Ctor. If `use_internal_vad` is true, an internal voice activity
   // detector is used for digital adaptive gain.
-  GainController2(const AudioProcessing::Config::GainController2& config,
-                  int sample_rate_hz,
-                  int num_channels,
-                  bool use_internal_vad);
+  GainController2(
+      const AudioProcessing::Config::GainController2& config,
+      const InputVolumeController::Config& input_volume_controller_config,
+      int sample_rate_hz,
+      int num_channels,
+      bool use_internal_vad);
   GainController2(const GainController2&) = delete;
   GainController2& operator=(const GainController2&) = delete;
   ~GainController2();
diff --git a/modules/audio_processing/gain_controller2_unittest.cc b/modules/audio_processing/gain_controller2_unittest.cc
index 7fb0c26..f7e5db2 100644
--- a/modules/audio_processing/gain_controller2_unittest.cc
+++ b/modules/audio_processing/gain_controller2_unittest.cc
@@ -33,6 +33,7 @@
 using ::testing::Optional;
 
 using Agc2Config = AudioProcessing::Config::GainController2;
+using InputVolumeControllerConfig = InputVolumeController::Config;
 
 // Sets all the samples in `ab` to `value`.
 void SetAudioBufferSamples(float value, AudioBuffer& ab) {
@@ -73,11 +74,25 @@
   config.adaptive_digital.enabled = false;
   config.fixed_digital.gain_db = fixed_gain_db;
   EXPECT_TRUE(GainController2::Validate(config));
-  return std::make_unique<GainController2>(config, sample_rate_hz,
-                                           /*num_channels=*/1,
-                                           /*use_internal_vad=*/true);
+  return std::make_unique<GainController2>(
+      config, InputVolumeControllerConfig{}, sample_rate_hz,
+      /*num_channels=*/1,
+      /*use_internal_vad=*/true);
 }
 
+constexpr InputVolumeControllerConfig kTestInputVolumeControllerConfig{
+    .clipped_level_min = 20,
+    .clipped_level_step = 30,
+    .clipped_ratio_threshold = 0.4,
+    .clipped_wait_frames = 50,
+    .enable_clipping_predictor = true,
+    .target_range_max_dbfs = -6,
+    .target_range_min_dbfs = -70,
+    .update_input_volume_wait_frames = 100,
+    .speech_probability_threshold = 0.9,
+    .speech_ratio_threshold = 1,
+};
+
 }  // namespace
 
 TEST(GainController2, CheckDefaultConfig) {
@@ -160,9 +175,41 @@
   Agc2Config config;
   config.input_volume_controller.enabled = false;
 
-  auto gain_controller =
-      std::make_unique<GainController2>(config, kSampleRateHz, kNumChannels,
-                                        /*use_internal_vad=*/true);
+  auto gain_controller = std::make_unique<GainController2>(
+      config, InputVolumeControllerConfig{}, kSampleRateHz, kNumChannels,
+      /*use_internal_vad=*/true);
+
+  EXPECT_FALSE(gain_controller->GetRecommendedInputVolume().has_value());
+
+  // Run AGC for a signal with no clipping or detected speech.
+  RunAgc2WithConstantInput(*gain_controller, kLowInputLevel, kNumFrames,
+                           kSampleRateHz, kNumChannels, kInitialInputVolume);
+
+  EXPECT_FALSE(gain_controller->GetRecommendedInputVolume().has_value());
+
+  // Run AGC for a signal with clipping.
+  RunAgc2WithConstantInput(*gain_controller, kHighInputLevel, kNumFrames,
+                           kSampleRateHz, kNumChannels, kInitialInputVolume);
+
+  EXPECT_FALSE(gain_controller->GetRecommendedInputVolume().has_value());
+}
+
+TEST(
+    GainController2,
+    CheckGetRecommendedInputVolumeWhenInputVolumeControllerNotEnabledAndSpecificConfigUsed) {
+  constexpr float kHighInputLevel = 32767.0f;
+  constexpr float kLowInputLevel = 1000.0f;
+  constexpr int kInitialInputVolume = 100;
+  constexpr int kNumChannels = 2;
+  constexpr int kNumFrames = 5;
+  constexpr int kSampleRateHz = 16000;
+
+  Agc2Config config;
+  config.input_volume_controller.enabled = false;
+
+  auto gain_controller = std::make_unique<GainController2>(
+      config, kTestInputVolumeControllerConfig, kSampleRateHz, kNumChannels,
+      /*use_internal_vad=*/true);
 
   EXPECT_FALSE(gain_controller->GetRecommendedInputVolume().has_value());
 
@@ -192,9 +239,42 @@
   config.input_volume_controller.enabled = true;
   config.adaptive_digital.enabled = true;
 
-  auto gain_controller =
-      std::make_unique<GainController2>(config, kSampleRateHz, kNumChannels,
-                                        /*use_internal_vad=*/true);
+  auto gain_controller = std::make_unique<GainController2>(
+      config, InputVolumeControllerConfig{}, kSampleRateHz, kNumChannels,
+      /*use_internal_vad=*/true);
+
+  EXPECT_TRUE(gain_controller->GetRecommendedInputVolume().has_value());
+
+  // Run AGC for a signal with no clipping or detected speech.
+  RunAgc2WithConstantInput(*gain_controller, kLowInputLevel, kNumFrames,
+                           kSampleRateHz, kNumChannels, kInitialInputVolume);
+
+  EXPECT_TRUE(gain_controller->GetRecommendedInputVolume().has_value());
+
+  // Run AGC for a signal with clipping.
+  RunAgc2WithConstantInput(*gain_controller, kHighInputLevel, kNumFrames,
+                           kSampleRateHz, kNumChannels, kInitialInputVolume);
+
+  EXPECT_TRUE(gain_controller->GetRecommendedInputVolume().has_value());
+}
+
+TEST(
+    GainController2,
+    CheckGetRecommendedInputVolumeWhenInputVolumeControllerEnabledAndSpecificConfigUsed) {
+  constexpr float kHighInputLevel = 32767.0f;
+  constexpr float kLowInputLevel = 1000.0f;
+  constexpr int kInitialInputVolume = 100;
+  constexpr int kNumChannels = 2;
+  constexpr int kNumFrames = 5;
+  constexpr int kSampleRateHz = 16000;
+
+  Agc2Config config;
+  config.input_volume_controller.enabled = true;
+  config.adaptive_digital.enabled = true;
+
+  auto gain_controller = std::make_unique<GainController2>(
+      config, kTestInputVolumeControllerConfig, kSampleRateHz, kNumChannels,
+      /*use_internal_vad=*/true);
 
   EXPECT_TRUE(gain_controller->GetRecommendedInputVolume().has_value());
 
@@ -214,7 +294,8 @@
 // Checks that the default config is applied.
 TEST(GainController2, ApplyDefaultConfig) {
   auto gain_controller2 = std::make_unique<GainController2>(
-      Agc2Config{}, /*sample_rate_hz=*/16000, /*num_channels=*/2,
+      Agc2Config{}, InputVolumeControllerConfig{},
+      /*sample_rate_hz=*/16000, /*num_channels=*/2,
       /*use_internal_vad=*/true);
   EXPECT_TRUE(gain_controller2.get());
 }
@@ -330,7 +411,8 @@
   Agc2Config config;
   config.fixed_digital.gain_db = 0.0f;
   config.adaptive_digital.enabled = true;
-  GainController2 agc2(config, kSampleRateHz, kStereo,
+  GainController2 agc2(config, /*input_volume_controller_config=*/{},
+                       kSampleRateHz, kStereo,
                        /*use_internal_vad=*/true);
 
   test::InputAudioFile input_file(
@@ -385,9 +467,11 @@
   Agc2Config config;
   config.fixed_digital.gain_db = 0.0f;
   config.adaptive_digital.enabled = true;
-  GainController2 agc2(config, kSampleRateHz, kStereo,
+  GainController2 agc2(config, /*input_volume_controller_config=*/{},
+                       kSampleRateHz, kStereo,
                        /*use_internal_vad=*/true);
-  GainController2 agc2_reference(config, kSampleRateHz, kStereo,
+  GainController2 agc2_reference(config, /*input_volume_controller_config=*/{},
+                                 kSampleRateHz, kStereo,
                                  /*use_internal_vad=*/true);
 
   test::InputAudioFile input_file(
@@ -452,9 +536,11 @@
   Agc2Config config;
   config.fixed_digital.gain_db = 0.0f;
   config.adaptive_digital.enabled = true;
-  GainController2 agc2(config, kSampleRateHz, kStereo,
+  GainController2 agc2(config, /*input_volume_controller_config=*/{},
+                       kSampleRateHz, kStereo,
                        /*use_internal_vad=*/false);
-  GainController2 agc2_reference(config, kSampleRateHz, kStereo,
+  GainController2 agc2_reference(config, /*input_volume_controller_config=*/{},
+                                 kSampleRateHz, kStereo,
                                  /*use_internal_vad=*/true);
 
   test::InputAudioFile input_file(
@@ -521,9 +607,11 @@
   Agc2Config config;
   config.fixed_digital.gain_db = 0.0f;
   config.adaptive_digital.enabled = true;
-  GainController2 agc2(config, kSampleRateHz, kStereo,
+  GainController2 agc2(config, /*input_volume_controller_config=*/{},
+                       kSampleRateHz, kStereo,
                        /*use_internal_vad=*/false);
-  GainController2 agc2_reference(config, kSampleRateHz, kStereo,
+  GainController2 agc2_reference(config, /*input_volume_controller_config=*/{},
+                                 kSampleRateHz, kStereo,
                                  /*use_internal_vad=*/true);
   VoiceActivityDetectorWrapper vad(config.adaptive_digital.vad_reset_period_ms,
                                    GetAvailableCpuFeatures(), kSampleRateHz);