AEC3: Adding explicit handling of microphone gain changes

This CL re-activates the explicit handling of microphone
gain changes in the AEC3 code. The implementation is done
beneath a kill-switch so that when that switch is active
the changes in this CL are bitexact.


Bug: webrtc:9526,chromium:863826
Change-Id: I58e93d8bc0bce7bec91e102de9891ad48ebc55d8
Reviewed-on: https://webrtc-review.googlesource.com/88620
Commit-Queue: Per Åhgren <peah@webrtc.org>
Reviewed-by: Sam Zackrisson <saza@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#23986}
diff --git a/modules/audio_processing/aec3/aec_state.cc b/modules/audio_processing/aec3/aec_state.cc
index f6ce23b..119b0ee 100644
--- a/modules/audio_processing/aec3/aec_state.cc
+++ b/modules/audio_processing/aec3/aec_state.cc
@@ -103,24 +103,10 @@
 
   // TODO(peah): Refine the reset scheme according to the type of gain and
   // delay adjustment.
-  if (echo_path_variability.gain_change) {
-    full_reset();
-  }
 
   if (echo_path_variability.delay_change !=
-      EchoPathVariability::DelayAdjustment::kBufferReadjustment) {
+      EchoPathVariability::DelayAdjustment::kNone) {
     full_reset();
-  } else if (echo_path_variability.delay_change !=
-             EchoPathVariability::DelayAdjustment::kBufferFlush) {
-    full_reset();
-  } else if (echo_path_variability.delay_change !=
-             EchoPathVariability::DelayAdjustment::kDelayReset) {
-    full_reset();
-  } else if (echo_path_variability.delay_change !=
-             EchoPathVariability::DelayAdjustment::kNewDetectedDelay) {
-    full_reset();
-  } else if (echo_path_variability.gain_change) {
-    blocks_since_reset_ = kNumBlocksPerSecond;
   }
 
   subtractor_output_analyzer_.HandleEchoPathChange();
diff --git a/modules/audio_processing/aec3/aec_state_unittest.cc b/modules/audio_processing/aec3/aec_state_unittest.cc
index 6111979..ef05707 100644
--- a/modules/audio_processing/aec3/aec_state_unittest.cc
+++ b/modules/audio_processing/aec3/aec_state_unittest.cc
@@ -70,7 +70,7 @@
   // Verify that linear AEC usability becomes false after an echo path change is
   // reported
   state.HandleEchoPathChange(EchoPathVariability(
-      true, EchoPathVariability::DelayAdjustment::kNone, false));
+      false, EchoPathVariability::DelayAdjustment::kBufferReadjustment, false));
   state.Update(delay_estimate, converged_filter_frequency_response,
                impulse_response, *render_delay_buffer->GetRenderBuffer(),
                E2_main, Y2, output, y);
diff --git a/modules/audio_processing/aec3/echo_remover.cc b/modules/audio_processing/aec3/echo_remover.cc
index 2e69298..67929b1 100644
--- a/modules/audio_processing/aec3/echo_remover.cc
+++ b/modules/audio_processing/aec3/echo_remover.cc
@@ -31,6 +31,7 @@
 #include "modules/audio_processing/logging/apm_data_dumper.h"
 #include "rtc_base/atomicops.h"
 #include "rtc_base/constructormagic.h"
+#include "rtc_base/logging.h"
 
 namespace webrtc {
 
@@ -66,7 +67,7 @@
   // Removes the echo from a block of samples from the capture signal. The
   // supplied render signal is assumed to be pre-aligned with the capture
   // signal.
-  void ProcessCapture(const EchoPathVariability& echo_path_variability,
+  void ProcessCapture(EchoPathVariability echo_path_variability,
                       bool capture_signal_saturation,
                       const absl::optional<DelayEstimate>& external_delay,
                       RenderBuffer* render_buffer,
@@ -104,6 +105,8 @@
   std::array<float, kFftLengthBy2> e_old_;
   std::array<float, kFftLengthBy2> x_old_;
   std::array<float, kFftLengthBy2> y_old_;
+  size_t block_counter_ = 0;
+  int gain_change_hangover_ = 0;
 
   RTC_DISALLOW_COPY_AND_ASSIGN(EchoRemoverImpl);
 };
@@ -141,11 +144,12 @@
 }
 
 void EchoRemoverImpl::ProcessCapture(
-    const EchoPathVariability& echo_path_variability,
+    EchoPathVariability echo_path_variability,
     bool capture_signal_saturation,
     const absl::optional<DelayEstimate>& external_delay,
     RenderBuffer* render_buffer,
     std::vector<std::vector<float>>* capture) {
+  ++block_counter_;
   const std::vector<std::vector<float>>& x = render_buffer->Block(0);
   std::vector<std::vector<float>>* y = capture;
   RTC_DCHECK(render_buffer);
@@ -167,10 +171,29 @@
   aec_state_.UpdateCaptureSaturation(capture_signal_saturation);
 
   if (echo_path_variability.AudioPathChanged()) {
+    // Ensure that the gain change is only acted on once per frame.
+    if (echo_path_variability.gain_change) {
+      if (gain_change_hangover_ == 0) {
+        constexpr int kMaxBlocksPerFrame = 3;
+        gain_change_hangover_ = kMaxBlocksPerFrame;
+        RTC_LOG(LS_WARNING)
+            << "Gain change detected at block " << block_counter_;
+      } else {
+        echo_path_variability.gain_change = false;
+      }
+    }
+
     subtractor_.HandleEchoPathChange(echo_path_variability);
     aec_state_.HandleEchoPathChange(echo_path_variability);
-    suppression_gain_.SetInitialState(true);
-    initial_state_ = true;
+
+    if (echo_path_variability.delay_change !=
+        EchoPathVariability::DelayAdjustment::kNone) {
+      suppression_gain_.SetInitialState(true);
+      initial_state_ = true;
+    }
+  }
+  if (gain_change_hangover_ > 0) {
+    --gain_change_hangover_;
   }
 
   std::array<float, kFftLengthBy2Plus1> Y2;
diff --git a/modules/audio_processing/aec3/echo_remover.h b/modules/audio_processing/aec3/echo_remover.h
index 710afac..cc8dae9 100644
--- a/modules/audio_processing/aec3/echo_remover.h
+++ b/modules/audio_processing/aec3/echo_remover.h
@@ -36,7 +36,7 @@
   // supplied render signal is assumed to be pre-aligned with the capture
   // signal.
   virtual void ProcessCapture(
-      const EchoPathVariability& echo_path_variability,
+      EchoPathVariability echo_path_variability,
       bool capture_signal_saturation,
       const absl::optional<DelayEstimate>& external_delay,
       RenderBuffer* render_buffer,
diff --git a/modules/audio_processing/aec3/main_filter_update_gain.cc b/modules/audio_processing/aec3/main_filter_update_gain.cc
index 6aa5780..6d31ed0 100644
--- a/modules/audio_processing/aec3/main_filter_update_gain.cc
+++ b/modules/audio_processing/aec3/main_filter_update_gain.cc
@@ -22,6 +22,7 @@
 namespace {
 
 constexpr float kHErrorInitial = 10000.f;
+constexpr float kHErrorGainChange = 10000.f;
 constexpr int kPoorExcitationCounterInitial = 1000;
 
 }  // namespace
@@ -46,10 +47,19 @@
 
 void MainFilterUpdateGain::HandleEchoPathChange(
     const EchoPathVariability& echo_path_variability) {
-  // TODO(peah): Add even-specific behavior.
-  H_error_.fill(kHErrorInitial);
-  poor_excitation_counter_ = kPoorExcitationCounterInitial;
-  call_counter_ = 0;
+  if (echo_path_variability.gain_change) {
+    H_error_.fill(kHErrorGainChange);
+  }
+
+  if (echo_path_variability.delay_change !=
+      EchoPathVariability::DelayAdjustment::kNone) {
+    H_error_.fill(kHErrorInitial);
+  }
+
+  if (!echo_path_variability.gain_change) {
+    poor_excitation_counter_ = kPoorExcitationCounterInitial;
+    call_counter_ = 0;
+  }
 }
 
 void MainFilterUpdateGain::Compute(
diff --git a/modules/audio_processing/aec3/mock/mock_echo_remover.h b/modules/audio_processing/aec3/mock/mock_echo_remover.h
index 0eb9508..6b64785 100644
--- a/modules/audio_processing/aec3/mock/mock_echo_remover.h
+++ b/modules/audio_processing/aec3/mock/mock_echo_remover.h
@@ -27,7 +27,7 @@
   virtual ~MockEchoRemover() = default;
 
   MOCK_METHOD5(ProcessCapture,
-               void(const EchoPathVariability& echo_path_variability,
+               void(EchoPathVariability echo_path_variability,
                     bool capture_signal_saturation,
                     const absl::optional<DelayEstimate>& delay_estimate,
                     RenderBuffer* render_buffer,
diff --git a/modules/audio_processing/aec3/subtractor.cc b/modules/audio_processing/aec3/subtractor.cc
index a9a3e6f..123ad7d 100644
--- a/modules/audio_processing/aec3/subtractor.cc
+++ b/modules/audio_processing/aec3/subtractor.cc
@@ -16,6 +16,7 @@
 #include "api/array_view.h"
 #include "modules/audio_processing/logging/apm_data_dumper.h"
 #include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
 #include "rtc_base/numerics/safe_minmax.h"
 #include "system_wrappers/include/field_trial.h"
 
@@ -23,6 +24,10 @@
 
 namespace {
 
+bool EnableAgcGainChangeResponse() {
+  return !field_trial::IsEnabled("WebRTC-Aec3AgcGainChangeResponseKillSwitch");
+}
+
 bool EnableAdaptationDuringSaturation() {
   return !field_trial::IsEnabled("WebRTC-Aec3RapidAgcGainRecoveryKillSwitch");
 }
@@ -77,6 +82,7 @@
       config_(config),
       adaptation_during_saturation_(EnableAdaptationDuringSaturation()),
       enable_misadjustment_estimator_(EnableMisadjustmentEstimator()),
+      enable_agc_gain_change_response_(EnableAgcGainChangeResponse()),
       main_filter_(config_.filter.main.length_blocks,
                    config_.filter.main_initial.length_blocks,
                    config.filter.config_change_duration_blocks,
@@ -117,18 +123,15 @@
         config_.filter.shadow_initial.length_blocks, true);
   };
 
-  // TODO(peah): Add delay-change specific reset behavior.
-  if ((echo_path_variability.delay_change ==
-       EchoPathVariability::DelayAdjustment::kBufferFlush) ||
-      (echo_path_variability.delay_change ==
-       EchoPathVariability::DelayAdjustment::kDelayReset)) {
+  if (echo_path_variability.delay_change !=
+      EchoPathVariability::DelayAdjustment::kNone) {
     full_reset();
-  } else if (echo_path_variability.delay_change ==
-             EchoPathVariability::DelayAdjustment::kNewDetectedDelay) {
-    full_reset();
-  } else if (echo_path_variability.delay_change ==
-             EchoPathVariability::DelayAdjustment::kBufferReadjustment) {
-    full_reset();
+  }
+
+  if (echo_path_variability.gain_change && enable_agc_gain_change_response_) {
+    RTC_LOG(LS_WARNING) << "Resetting main filter adaptation speed due to "
+                           "microphone gain change";
+    G_main_.HandleEchoPathChange(echo_path_variability);
   }
 }
 
diff --git a/modules/audio_processing/aec3/subtractor.h b/modules/audio_processing/aec3/subtractor.h
index 67ecdc0..9af6499 100644
--- a/modules/audio_processing/aec3/subtractor.h
+++ b/modules/audio_processing/aec3/subtractor.h
@@ -106,6 +106,7 @@
   const EchoCanceller3Config config_;
   const bool adaptation_during_saturation_;
   const bool enable_misadjustment_estimator_;
+  const bool enable_agc_gain_change_response_;
   AdaptiveFirFilter main_filter_;
   AdaptiveFirFilter shadow_filter_;
   MainFilterUpdateGain G_main_;
diff --git a/modules/audio_processing/audio_processing_impl.cc b/modules/audio_processing/audio_processing_impl.cc
index e87d2cf..7e5955a 100644
--- a/modules/audio_processing/audio_processing_impl.cc
+++ b/modules/audio_processing/audio_processing_impl.cc
@@ -1198,9 +1198,13 @@
   }
 
   if (private_submodules_->echo_controller) {
-    // TODO(peah): Reactivate analogue AGC gain detection once the analogue AGC
-    // issues have been addressed.
-    capture_.echo_path_gain_change = false;
+    // Detect and flag any change in the analog gain.
+    int analog_mic_level = gain_control()->stream_analog_level();
+    capture_.echo_path_gain_change =
+        capture_.prev_analog_mic_level != analog_mic_level &&
+        capture_.prev_analog_mic_level != -1;
+    capture_.prev_analog_mic_level = analog_mic_level;
+
     private_submodules_->echo_controller->AnalyzeCapture(capture_buffer);
   }
 
@@ -2049,7 +2053,8 @@
       transient_suppressor_enabled(transient_suppressor_enabled),
       capture_processing_format(kSampleRate16kHz),
       split_rate(kSampleRate16kHz),
-      echo_path_gain_change(false) {}
+      echo_path_gain_change(false),
+      prev_analog_mic_level(-1) {}
 
 AudioProcessingImpl::ApmCaptureState::~ApmCaptureState() = default;
 
diff --git a/modules/audio_processing/audio_processing_impl.h b/modules/audio_processing/audio_processing_impl.h
index 8a5d245..44d0d08 100644
--- a/modules/audio_processing/audio_processing_impl.h
+++ b/modules/audio_processing/audio_processing_impl.h
@@ -390,6 +390,7 @@
     StreamConfig capture_processing_format;
     int split_rate;
     bool echo_path_gain_change;
+    int prev_analog_mic_level;
   } capture_ RTC_GUARDED_BY(crit_capture_);
 
   struct ApmCaptureNonLockedState {
diff --git a/test/fuzzers/audio_processing_configs_fuzzer.cc b/test/fuzzers/audio_processing_configs_fuzzer.cc
index 46f8918..545e455 100644
--- a/test/fuzzers/audio_processing_configs_fuzzer.cc
+++ b/test/fuzzers/audio_processing_configs_fuzzer.cc
@@ -45,7 +45,8 @@
     "WebRTC-Aec3RapidAgcGainRecoveryKillSwitch",
     "WebRTC-Aec3SlowFilterAdaptationKillSwitch",
     "WebRTC-Aec3SmoothUpdatesTailFreqRespKillSwitch",
-    "WebRTC-Aec3SuppressorNearendAveragingKillSwitch"};
+    "WebRTC-Aec3SuppressorNearendAveragingKillSwitch",
+    "WebRTC-Aec3AgcGainChangeResponseKillSwitch"};
 
 std::unique_ptr<AudioProcessing> CreateApm(test::FuzzDataHelper* fuzz_data,
                                            std::string* field_trial_string) {