Simplifications/refactoring of the analog AGC to make it multichannel

This CL prepares parts the analog AGC code to make it properly
multichannel.

Bug: webrtc:10859
Change-Id: I693d0d004dd2c7495ebdc60a43e9a53a441a93e0
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/158896
Commit-Queue: Per Åhgren <peah@webrtc.org>
Reviewed-by: Sam Zackrisson <saza@webrtc.org>
Reviewed-by: Alessio Bazzica <alessiob@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#29718}
diff --git a/modules/audio_processing/agc/agc.cc b/modules/audio_processing/agc/agc.cc
index c24db0d..a89ae11 100644
--- a/modules/audio_processing/agc/agc.cc
+++ b/modules/audio_processing/agc/agc.cc
@@ -33,17 +33,7 @@
       histogram_(LoudnessHistogram::Create(kNumAnalysisFrames)),
       inactive_histogram_(LoudnessHistogram::Create()) {}
 
-Agc::~Agc() {}
-
-float Agc::AnalyzePreproc(const int16_t* audio, size_t length) {
-  RTC_DCHECK_GT(length, 0);
-  size_t num_clipped = 0;
-  for (size_t i = 0; i < length; ++i) {
-    if (audio[i] == 32767 || audio[i] == -32768)
-      ++num_clipped;
-  }
-  return 1.0f * num_clipped / length;
-}
+Agc::~Agc() = default;
 
 void Agc::Process(const int16_t* audio, size_t length, int sample_rate_hz) {
   vad_.ProcessChunk(audio, length, sample_rate_hz);
diff --git a/modules/audio_processing/agc/agc.h b/modules/audio_processing/agc/agc.h
index abd68d5..b9bd5ea 100644
--- a/modules/audio_processing/agc/agc.h
+++ b/modules/audio_processing/agc/agc.h
@@ -24,9 +24,6 @@
   Agc();
   virtual ~Agc();
 
-  // Returns the proportion of samples in the buffer which are at full-scale
-  // (and presumably clipped).
-  virtual float AnalyzePreproc(const int16_t* audio, size_t length);
   // |audio| must be mono; in a multi-channel stream, provide the first (usually
   // left) channel.
   virtual void Process(const int16_t* audio, size_t length, int sample_rate_hz);
diff --git a/modules/audio_processing/agc/agc_manager_direct.cc b/modules/audio_processing/agc/agc_manager_direct.cc
index cc0b482..2a6b800 100644
--- a/modules/audio_processing/agc/agc_manager_direct.cc
+++ b/modules/audio_processing/agc/agc_manager_direct.cc
@@ -141,6 +141,24 @@
   return 0;
 }
 
+// Returns the proportion of samples in the buffer which are at full-scale
+// (and presumably clipped).
+float ComputeClippedRatio(const float* const* audio,
+                          size_t num_channels,
+                          size_t samples_per_channel) {
+  RTC_DCHECK_GT(num_channels * samples_per_channel, 0);
+  int num_clipped = 0;
+  for (size_t ch = 0; ch < num_channels; ++ch) {
+    for (size_t i = 0; i < samples_per_channel; ++i) {
+      RTC_DCHECK(audio[ch]);
+      if (audio[ch][i] >= 32767.f || audio[ch][i] <= -32768.f) {
+        ++num_clipped;
+      }
+    }
+  }
+  return static_cast<float>(num_clipped) / (num_channels * samples_per_channel);
+}
+
 }  // namespace
 
 // Facility for dumping debug audio files. All methods are no-ops in the
@@ -253,28 +271,14 @@
   return InitializeGainControl(gctrl_, disable_digital_adaptive_);
 }
 
-void AgcManagerDirect::AnalyzePreProcess(float* audio,
+void AgcManagerDirect::AnalyzePreProcess(const float* const* audio,
                                          int num_channels,
                                          size_t samples_per_channel) {
-  size_t length = num_channels * samples_per_channel;
+  RTC_DCHECK(audio);
   if (capture_muted_) {
     return;
   }
 
-  std::array<int16_t, kMaxNumSamplesPerChannel * kMaxNumChannels> audio_data;
-  int16_t* audio_fix;
-  size_t safe_length;
-  if (audio) {
-    audio_fix = audio_data.data();
-    safe_length = std::min(audio_data.size(), length);
-    FloatS16ToS16(audio, length, audio_fix);
-  } else {
-    audio_fix = nullptr;
-    safe_length = length;
-  }
-
-  file_preproc_->Write(audio_fix, safe_length);
-
   if (frames_since_clipped_ < kClippedWaitFrames) {
     ++frames_since_clipped_;
     return;
@@ -289,7 +293,8 @@
   // maximum. This harsh treatment is an effort to avoid repeated clipped echo
   // events. As compensation for this restriction, the maximum compression
   // gain is increased, through SetMaxLevel().
-  float clipped_ratio = agc_->AnalyzePreproc(audio_fix, safe_length);
+  float clipped_ratio =
+      ComputeClippedRatio(audio, num_channels, samples_per_channel);
   if (clipped_ratio > kClippedRatioThreshold) {
     RTC_DLOG(LS_INFO) << "[agc] Clipping detected. clipped_ratio="
                       << clipped_ratio;
@@ -308,10 +313,6 @@
     }
     frames_since_clipped_ = 0;
   }
-
-  if (audio) {
-    S16ToFloatS16(audio_fix, safe_length, audio);
-  }
 }
 
 void AgcManagerDirect::Process(const float* audio,
diff --git a/modules/audio_processing/agc/agc_manager_direct.h b/modules/audio_processing/agc/agc_manager_direct.h
index ddb14e5..69bc358 100644
--- a/modules/audio_processing/agc/agc_manager_direct.h
+++ b/modules/audio_processing/agc/agc_manager_direct.h
@@ -56,7 +56,7 @@
   ~AgcManagerDirect();
 
   int Initialize();
-  void AnalyzePreProcess(float* audio,
+  void AnalyzePreProcess(const float* const* audio,
                          int num_channels,
                          size_t samples_per_channel);
   void Process(const float* audio, size_t length, int sample_rate_hz);
diff --git a/modules/audio_processing/agc/agc_manager_direct_unittest.cc b/modules/audio_processing/agc/agc_manager_direct_unittest.cc
index faab5c0..41f1904 100644
--- a/modules/audio_processing/agc/agc_manager_direct_unittest.cc
+++ b/modules/audio_processing/agc/agc_manager_direct_unittest.cc
@@ -71,9 +71,14 @@
  protected:
   AgcManagerDirectTest()
       : agc_(new MockAgc),
-        manager_(agc_, &gctrl_, &volume_, kInitialVolume, kClippedMin) {
+        manager_(agc_, &gctrl_, &volume_, kInitialVolume, kClippedMin),
+        audio(kNumChannels),
+        audio_data(kNumChannels * kSamplesPerChannel, 0.f) {
     ExpectInitialize();
     manager_.Initialize();
+    for (size_t ch = 0; ch < kNumChannels; ++ch) {
+      audio[ch] = &audio_data[ch * kSamplesPerChannel];
+    }
   }
 
   void FirstProcess() {
@@ -106,9 +111,14 @@
     }
   }
 
-  void CallPreProc(int num_calls) {
+  void CallPreProc(int num_calls, float clipped_ratio) {
+    RTC_DCHECK_GE(1.f, clipped_ratio);
+    int num_clipped = kNumChannels * kSamplesPerChannel * clipped_ratio;
+    std::fill(audio_data.begin(), audio_data.begin() + num_clipped, 32767.f);
+
     for (int i = 0; i < num_calls; ++i) {
-      manager_.AnalyzePreProcess(nullptr, kNumChannels, kSamplesPerChannel);
+      manager_.AnalyzePreProcess(audio.data(), kNumChannels,
+                                 kSamplesPerChannel);
     }
   }
 
@@ -116,6 +126,8 @@
   MockGainControl gctrl_;
   TestVolumeCallbacks volume_;
   AgcManagerDirect manager_;
+  std::vector<float*> audio;
+  std::vector<float> audio_data;
 };
 
 TEST_F(AgcManagerDirectTest, StartupMinVolumeConfigurationIsRespected) {
@@ -477,73 +489,58 @@
 TEST_F(AgcManagerDirectTest, NoClippingHasNoImpact) {
   FirstProcess();
 
-  EXPECT_CALL(*agc_, AnalyzePreproc(_, _)).WillRepeatedly(Return(0));
-  CallPreProc(100);
+  CallPreProc(100, 0);
   EXPECT_EQ(128, volume_.GetMicVolume());
 }
 
 TEST_F(AgcManagerDirectTest, ClippingUnderThresholdHasNoImpact) {
   FirstProcess();
 
-  EXPECT_CALL(*agc_, AnalyzePreproc(_, _)).WillOnce(Return(0.099));
-  CallPreProc(1);
+  CallPreProc(1, 0.099);
   EXPECT_EQ(128, volume_.GetMicVolume());
 }
 
 TEST_F(AgcManagerDirectTest, ClippingLowersVolume) {
   SetVolumeAndProcess(255);
 
-  EXPECT_CALL(*agc_, AnalyzePreproc(_, _)).WillOnce(Return(0.101));
   EXPECT_CALL(*agc_, Reset()).Times(AtLeast(1));
-  CallPreProc(1);
+  CallPreProc(1, 0.2);
   EXPECT_EQ(240, volume_.GetMicVolume());
 }
 
 TEST_F(AgcManagerDirectTest, WaitingPeriodBetweenClippingChecks) {
   SetVolumeAndProcess(255);
 
-  EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
-      .WillOnce(Return(kAboveClippedThreshold));
   EXPECT_CALL(*agc_, Reset()).Times(AtLeast(1));
-  CallPreProc(1);
+  CallPreProc(1, kAboveClippedThreshold);
   EXPECT_EQ(240, volume_.GetMicVolume());
 
-  EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
-      .WillRepeatedly(Return(kAboveClippedThreshold));
   EXPECT_CALL(*agc_, Reset()).Times(0);
-  CallPreProc(300);
+  CallPreProc(300, kAboveClippedThreshold);
   EXPECT_EQ(240, volume_.GetMicVolume());
 
-  EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
-      .WillOnce(Return(kAboveClippedThreshold));
   EXPECT_CALL(*agc_, Reset()).Times(AtLeast(1));
-  CallPreProc(1);
+  CallPreProc(1, kAboveClippedThreshold);
   EXPECT_EQ(225, volume_.GetMicVolume());
 }
 
 TEST_F(AgcManagerDirectTest, ClippingLoweringIsLimited) {
   SetVolumeAndProcess(180);
 
-  EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
-      .WillOnce(Return(kAboveClippedThreshold));
   EXPECT_CALL(*agc_, Reset()).Times(AtLeast(1));
-  CallPreProc(1);
+  CallPreProc(1, kAboveClippedThreshold);
   EXPECT_EQ(kClippedMin, volume_.GetMicVolume());
 
-  EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
-      .WillRepeatedly(Return(kAboveClippedThreshold));
   EXPECT_CALL(*agc_, Reset()).Times(0);
-  CallPreProc(1000);
+  CallPreProc(1000, kAboveClippedThreshold);
   EXPECT_EQ(kClippedMin, volume_.GetMicVolume());
 }
 
 TEST_F(AgcManagerDirectTest, ClippingMaxIsRespectedWhenEqualToLevel) {
   SetVolumeAndProcess(255);
 
-  EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
-      .WillOnce(Return(kAboveClippedThreshold));
   EXPECT_CALL(*agc_, Reset()).Times(AtLeast(1));
-  CallPreProc(1);
+  CallPreProc(1, kAboveClippedThreshold);
   EXPECT_EQ(240, volume_.GetMicVolume());
 
   EXPECT_CALL(*agc_, GetRmsErrorDb(_))
@@ -555,10 +552,8 @@
 TEST_F(AgcManagerDirectTest, ClippingMaxIsRespectedWhenHigherThanLevel) {
   SetVolumeAndProcess(200);
 
-  EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
-      .WillOnce(Return(kAboveClippedThreshold));
   EXPECT_CALL(*agc_, Reset()).Times(AtLeast(1));
-  CallPreProc(1);
+  CallPreProc(1, kAboveClippedThreshold);
   EXPECT_EQ(185, volume_.GetMicVolume());
 
   EXPECT_CALL(*agc_, GetRmsErrorDb(_))
@@ -572,10 +567,8 @@
 TEST_F(AgcManagerDirectTest, MaxCompressionIsIncreasedAfterClipping) {
   SetVolumeAndProcess(210);
 
-  EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
-      .WillOnce(Return(kAboveClippedThreshold));
   EXPECT_CALL(*agc_, Reset()).Times(AtLeast(1));
-  CallPreProc(1);
+  CallPreProc(1, kAboveClippedThreshold);
   EXPECT_EQ(195, volume_.GetMicVolume());
 
   EXPECT_CALL(*agc_, GetRmsErrorDb(_))
@@ -600,36 +593,26 @@
   CallProcess(1);
 
   // Continue clipping until we hit the maximum surplus compression.
-  CallPreProc(300);
-  EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
-      .WillOnce(Return(kAboveClippedThreshold));
+  CallPreProc(300, kAboveClippedThreshold);
   EXPECT_CALL(*agc_, Reset()).Times(AtLeast(1));
-  CallPreProc(1);
+  CallPreProc(1, kAboveClippedThreshold);
   EXPECT_EQ(180, volume_.GetMicVolume());
 
-  CallPreProc(300);
-  EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
-      .WillOnce(Return(kAboveClippedThreshold));
+  CallPreProc(300, kAboveClippedThreshold);
   EXPECT_CALL(*agc_, Reset()).Times(AtLeast(1));
-  CallPreProc(1);
+  CallPreProc(1, kAboveClippedThreshold);
   EXPECT_EQ(kClippedMin, volume_.GetMicVolume());
 
   // Current level is now at the minimum, but the maximum allowed level still
   // has more to decrease.
-  CallPreProc(300);
-  EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
-      .WillOnce(Return(kAboveClippedThreshold));
-  CallPreProc(1);
+  CallPreProc(300, kAboveClippedThreshold);
+  CallPreProc(1, kAboveClippedThreshold);
 
-  CallPreProc(300);
-  EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
-      .WillOnce(Return(kAboveClippedThreshold));
-  CallPreProc(1);
+  CallPreProc(300, kAboveClippedThreshold);
+  CallPreProc(1, kAboveClippedThreshold);
 
-  CallPreProc(300);
-  EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
-      .WillOnce(Return(kAboveClippedThreshold));
-  CallPreProc(1);
+  CallPreProc(300, kAboveClippedThreshold);
+  CallPreProc(1, kAboveClippedThreshold);
 
   EXPECT_CALL(*agc_, GetRmsErrorDb(_))
       .WillOnce(DoAll(SetArgPointee<0>(16), Return(true)))
@@ -653,10 +636,8 @@
 TEST_F(AgcManagerDirectTest, UserCanRaiseVolumeAfterClipping) {
   SetVolumeAndProcess(225);
 
-  EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
-      .WillOnce(Return(kAboveClippedThreshold));
   EXPECT_CALL(*agc_, Reset()).Times(AtLeast(1));
-  CallPreProc(1);
+  CallPreProc(1, kAboveClippedThreshold);
   EXPECT_EQ(210, volume_.GetMicVolume());
 
   // High enough error to trigger a volume check.
@@ -688,11 +669,9 @@
 TEST_F(AgcManagerDirectTest, ClippingDoesNotPullLowVolumeBackUp) {
   SetVolumeAndProcess(80);
 
-  EXPECT_CALL(*agc_, AnalyzePreproc(_, _))
-      .WillOnce(Return(kAboveClippedThreshold));
   EXPECT_CALL(*agc_, Reset()).Times(0);
   int initial_volume = volume_.GetMicVolume();
-  CallPreProc(1);
+  CallPreProc(1, kAboveClippedThreshold);
   EXPECT_EQ(initial_volume, volume_.GetMicVolume());
 }
 
diff --git a/modules/audio_processing/agc/mock_agc.h b/modules/audio_processing/agc/mock_agc.h
index d31c265..6542acc 100644
--- a/modules/audio_processing/agc/mock_agc.h
+++ b/modules/audio_processing/agc/mock_agc.h
@@ -19,7 +19,6 @@
 class MockAgc : public Agc {
  public:
   virtual ~MockAgc() {}
-  MOCK_METHOD2(AnalyzePreproc, float(const int16_t* audio, size_t length));
   MOCK_METHOD3(Process,
                void(const int16_t* audio, size_t length, int sample_rate_hz));
   MOCK_METHOD1(GetRmsErrorDb, bool(int* error));
diff --git a/modules/audio_processing/audio_processing_impl.cc b/modules/audio_processing/audio_processing_impl.cc
index fb46e04..cab7677 100644
--- a/modules/audio_processing/audio_processing_impl.cc
+++ b/modules/audio_processing/audio_processing_impl.cc
@@ -1278,7 +1278,7 @@
   if (constants_.use_experimental_agc &&
       submodules_.gain_control->is_enabled()) {
     submodules_.agc_manager->AnalyzePreProcess(
-        capture_buffer->channels_f()[0], capture_buffer->num_channels(),
+        capture_buffer->channels_const(), capture_buffer->num_channels(),
         capture_nonlocked_.capture_processing_format.num_frames());
 
     if (constants_.use_experimental_agc_process_before_aec) {