Add refined handling of the internal scaling of the audio in APM

This CL adds functionality that allows adjusting the audio levels
internally in APM. The main purpose of the functionality is to allow
APM to optionally be moved to an integration that does not provide an
analog gain to control, and the implementation of this has been
tailored specifically to meet the requirements for that.

More specifically, this CL does
-Add a new variant of the pre-amplifier gain that is intended to replace
 the pre-amplifier gain (but at the moment can coexist with that). The
 main differences with the pre-amplifier gain is that an attenuating
 gain is allowed, the gain is applied jointly with any emulated analog
 gain, and that its packaging fits better with the post gain.
-Add an emulation of an analog microphone gain. The emulation is
 designed to match the analog mic gain functionality in Chrome OS (which
 is digital) but should be usable also on other platforms.
-Add a post-gain which is applied after all processing has been applied.
 The purpose of this gain is for it to work well with the integration
 in ChromeOS, and be used to compensate for the offset that there is
 applied on some USB audio devices.


Bug: b/177830918
Change-Id: I0f312996e4088c9bd242a713a703eaaeb17f188a
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/209707
Commit-Queue: Per Åhgren <peah@webrtc.org>
Reviewed-by: Gustaf Ullberg <gustaf@webrtc.org>
Reviewed-by: Alessio Bazzica <alessiob@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#33466}
diff --git a/modules/audio_processing/audio_processing_unittest.cc b/modules/audio_processing/audio_processing_unittest.cc
index 4c879c7..3d562df 100644
--- a/modules/audio_processing/audio_processing_unittest.cc
+++ b/modules/audio_processing/audio_processing_unittest.cc
@@ -913,6 +913,131 @@
   EXPECT_EQ(config.pre_amplifier.fixed_gain_factor, 1.5f);
 }
 
+// This test a simple test that ensures that the emulated analog mic gain
+// functionality runs without crashing.
+TEST_F(ApmTest, AnalogMicGainEmulation) {
+  // Fill the audio frame with a sawtooth pattern.
+  rtc::ArrayView<int16_t> frame_data = GetMutableFrameData(&frame_);
+  const size_t samples_per_channel = frame_.samples_per_channel;
+  for (size_t i = 0; i < samples_per_channel; i++) {
+    for (size_t ch = 0; ch < frame_.num_channels; ++ch) {
+      frame_data[i + ch * samples_per_channel] = 100 * ((i % 3) - 1);
+    }
+  }
+  // Cache the frame in tmp_frame.
+  Int16FrameData tmp_frame;
+  tmp_frame.CopyFrom(frame_);
+
+  // Enable the analog gain emulation.
+  AudioProcessing::Config config = apm_->GetConfig();
+  config.capture_level_adjustment.enabled = true;
+  config.capture_level_adjustment.analog_mic_gain_emulation.enabled = true;
+  config.capture_level_adjustment.analog_mic_gain_emulation.initial_level = 21;
+  config.gain_controller1.enabled = true;
+  config.gain_controller1.mode =
+      AudioProcessing::Config::GainController1::Mode::kAdaptiveAnalog;
+  config.gain_controller1.analog_gain_controller.enabled = true;
+  apm_->ApplyConfig(config);
+
+  // Process a number of frames to ensure that the code runs without crashes.
+  for (int i = 0; i < 20; ++i) {
+    frame_.CopyFrom(tmp_frame);
+    EXPECT_EQ(apm_->kNoError, ProcessStreamChooser(kIntFormat));
+  }
+}
+
+// This test repeatedly reconfigures the capture level adjustment functionality
+// in APM, processes a number of frames, and checks that output signal has the
+// right level.
+TEST_F(ApmTest, CaptureLevelAdjustment) {
+  // Fill the audio frame with a sawtooth pattern.
+  rtc::ArrayView<int16_t> frame_data = GetMutableFrameData(&frame_);
+  const size_t samples_per_channel = frame_.samples_per_channel;
+  for (size_t i = 0; i < samples_per_channel; i++) {
+    for (size_t ch = 0; ch < frame_.num_channels; ++ch) {
+      frame_data[i + ch * samples_per_channel] = 100 * ((i % 3) - 1);
+    }
+  }
+  // Cache the frame in tmp_frame.
+  Int16FrameData tmp_frame;
+  tmp_frame.CopyFrom(frame_);
+
+  auto compute_power = [](const Int16FrameData& frame) {
+    rtc::ArrayView<const int16_t> data = GetFrameData(frame);
+    return std::accumulate(data.begin(), data.end(), 0.0f,
+                           [](float a, float b) { return a + b * b; }) /
+           data.size() / 32768 / 32768;
+  };
+
+  const float input_power = compute_power(tmp_frame);
+  // Double-check that the input data is large compared to the error kEpsilon.
+  constexpr float kEpsilon = 1e-20f;
+  RTC_DCHECK_GE(input_power, 10 * kEpsilon);
+
+  // 1. Enable pre-amp with 0 dB gain.
+  AudioProcessing::Config config = apm_->GetConfig();
+  config.capture_level_adjustment.enabled = true;
+  config.capture_level_adjustment.pre_gain_factor = 0.5f;
+  config.capture_level_adjustment.post_gain_factor = 4.f;
+  const float expected_output_power1 =
+      config.capture_level_adjustment.pre_gain_factor *
+      config.capture_level_adjustment.pre_gain_factor *
+      config.capture_level_adjustment.post_gain_factor *
+      config.capture_level_adjustment.post_gain_factor * input_power;
+  apm_->ApplyConfig(config);
+
+  for (int i = 0; i < 20; ++i) {
+    frame_.CopyFrom(tmp_frame);
+    EXPECT_EQ(apm_->kNoError, ProcessStreamChooser(kIntFormat));
+  }
+  float output_power = compute_power(frame_);
+  EXPECT_NEAR(output_power, expected_output_power1, kEpsilon);
+  config = apm_->GetConfig();
+  EXPECT_EQ(config.capture_level_adjustment.pre_gain_factor, 0.5f);
+  EXPECT_EQ(config.capture_level_adjustment.post_gain_factor, 4.f);
+
+  // 2. Change pre-amp gain via ApplyConfig.
+  config.capture_level_adjustment.pre_gain_factor = 1.0f;
+  config.capture_level_adjustment.post_gain_factor = 2.f;
+  const float expected_output_power2 =
+      config.capture_level_adjustment.pre_gain_factor *
+      config.capture_level_adjustment.pre_gain_factor *
+      config.capture_level_adjustment.post_gain_factor *
+      config.capture_level_adjustment.post_gain_factor * input_power;
+  apm_->ApplyConfig(config);
+
+  for (int i = 0; i < 20; ++i) {
+    frame_.CopyFrom(tmp_frame);
+    EXPECT_EQ(apm_->kNoError, ProcessStreamChooser(kIntFormat));
+  }
+  output_power = compute_power(frame_);
+  EXPECT_NEAR(output_power, expected_output_power2, kEpsilon);
+  config = apm_->GetConfig();
+  EXPECT_EQ(config.capture_level_adjustment.pre_gain_factor, 1.0f);
+  EXPECT_EQ(config.capture_level_adjustment.post_gain_factor, 2.f);
+
+  // 3. Change pre-amp gain via a RuntimeSetting.
+  constexpr float kPreGain3 = 0.5f;
+  constexpr float kPostGain3 = 3.f;
+  const float expected_output_power3 =
+      kPreGain3 * kPreGain3 * kPostGain3 * kPostGain3 * input_power;
+
+  apm_->SetRuntimeSetting(
+      AudioProcessing::RuntimeSetting::CreateCapturePreGain(kPreGain3));
+  apm_->SetRuntimeSetting(
+      AudioProcessing::RuntimeSetting::CreateCapturePostGain(kPostGain3));
+
+  for (int i = 0; i < 20; ++i) {
+    frame_.CopyFrom(tmp_frame);
+    EXPECT_EQ(apm_->kNoError, ProcessStreamChooser(kIntFormat));
+  }
+  output_power = compute_power(frame_);
+  EXPECT_NEAR(output_power, expected_output_power3, kEpsilon);
+  config = apm_->GetConfig();
+  EXPECT_EQ(config.capture_level_adjustment.pre_gain_factor, 0.5f);
+  EXPECT_EQ(config.capture_level_adjustment.post_gain_factor, 3.f);
+}
+
 TEST_F(ApmTest, GainControl) {
   AudioProcessing::Config config = apm_->GetConfig();
   config.gain_controller1.enabled = false;
@@ -2428,36 +2553,6 @@
   EXPECT_EQ(AudioProcessing::RuntimeSetting::Type::kNotSpecified, s.type());
 }
 
-TEST(RuntimeSettingDeathTest, TestCapturePreGain) {
-  using Type = AudioProcessing::RuntimeSetting::Type;
-  {
-    auto s = AudioProcessing::RuntimeSetting::CreateCapturePreGain(1.25f);
-    EXPECT_EQ(Type::kCapturePreGain, s.type());
-    float v;
-    s.GetFloat(&v);
-    EXPECT_EQ(1.25f, v);
-  }
-
-#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
-  EXPECT_DEATH(AudioProcessing::RuntimeSetting::CreateCapturePreGain(0.1f), "");
-#endif
-}
-
-TEST(RuntimeSettingDeathTest, TestCaptureFixedPostGain) {
-  using Type = AudioProcessing::RuntimeSetting::Type;
-  {
-    auto s = AudioProcessing::RuntimeSetting::CreateCaptureFixedPostGain(1.25f);
-    EXPECT_EQ(Type::kCaptureFixedPostGain, s.type());
-    float v;
-    s.GetFloat(&v);
-    EXPECT_EQ(1.25f, v);
-  }
-
-#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
-  EXPECT_DEATH(AudioProcessing::RuntimeSetting::CreateCapturePreGain(0.1f), "");
-#endif
-}
-
 TEST(RuntimeSettingTest, TestUsageWithSwapQueue) {
   SwapQueue<AudioProcessing::RuntimeSetting> q(1);
   auto s = AudioProcessing::RuntimeSetting();