Add PlayoutVolumeChange RuntimeSetting.

Add a PlayoutVolumeChange RuntimeSetting. Trigger an echo path change when the playout volume is changed.

Bug: webrtc:10608
Change-Id: I1e736b93c1865d08c7d2582f6fe00216c1e1f72e
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/135746
Reviewed-by: Per Ã…hgren <peah@webrtc.org>
Reviewed-by: Fredrik Hernqvist <fhernqvist@webrtc.org>
Commit-Queue: Fredrik Hernqvist <fhernqvist@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#27913}
diff --git a/modules/audio_processing/aec_dump/aec_dump_impl.cc b/modules/audio_processing/aec_dump/aec_dump_impl.cc
index e4ee872..0057343 100644
--- a/modules/audio_processing/aec_dump/aec_dump_impl.cc
+++ b/modules/audio_processing/aec_dump/aec_dump_impl.cc
@@ -192,11 +192,18 @@
       // Runtime AGC1 compression gain is ignored.
       // TODO(http://bugs.webrtc.org/10432): Store compression gain in aecdumps.
       break;
-    case AudioProcessing::RuntimeSetting::Type::kCaptureFixedPostGain:
+    case AudioProcessing::RuntimeSetting::Type::kCaptureFixedPostGain: {
       float x;
       runtime_setting.GetFloat(&x);
       setting->set_capture_fixed_post_gain(x);
       break;
+    }
+    case AudioProcessing::RuntimeSetting::Type::kPlayoutVolumeChange: {
+      int x;
+      runtime_setting.GetInt(&x);
+      setting->set_playout_volume_change(x);
+      break;
+    }
     case AudioProcessing::RuntimeSetting::Type::kNotSpecified:
       RTC_NOTREACHED();
       break;
diff --git a/modules/audio_processing/audio_processing_impl.cc b/modules/audio_processing/audio_processing_impl.cc
index 13db45f..c2ff7f0 100644
--- a/modules/audio_processing/audio_processing_impl.cc
+++ b/modules/audio_processing/audio_processing_impl.cc
@@ -853,6 +853,7 @@
     case RuntimeSetting::Type::kCapturePreGain:
     case RuntimeSetting::Type::kCaptureCompressionGain:
     case RuntimeSetting::Type::kCaptureFixedPostGain:
+    case RuntimeSetting::Type::kPlayoutVolumeChange:
       capture_runtime_settings_enqueuer_.Enqueue(setting);
       return;
   }
@@ -998,6 +999,12 @@
         }
         break;
       }
+      case RuntimeSetting::Type::kPlayoutVolumeChange: {
+        int value;
+        setting.GetInt(&value);
+        capture_.playout_volume = value;
+        break;
+      }
       case RuntimeSetting::Type::kCustomRenderProcessingRuntimeSetting:
         RTC_NOTREACHED();
         break;
@@ -1023,6 +1030,7 @@
       case RuntimeSetting::Type::kCapturePreGain:          // fall-through
       case RuntimeSetting::Type::kCaptureCompressionGain:  // fall-through
       case RuntimeSetting::Type::kCaptureFixedPostGain:    // fall-through
+      case RuntimeSetting::Type::kPlayoutVolumeChange:     // fall-through
       case RuntimeSetting::Type::kNotSpecified:
         RTC_NOTREACHED();
         break;
@@ -1291,6 +1299,14 @@
            capture_.prev_pre_amp_gain >= 0.f);
       capture_.prev_pre_amp_gain = pre_amp_gain;
     }
+
+    // Detect volume change.
+    capture_.echo_path_gain_change =
+        capture_.echo_path_gain_change ||
+        (capture_.prev_playout_volume != capture_.playout_volume &&
+         capture_.prev_playout_volume >= 0);
+    capture_.prev_playout_volume = capture_.playout_volume;
+
     private_submodules_->echo_controller->AnalyzeCapture(capture_buffer);
   }
 
@@ -2087,7 +2103,9 @@
       split_rate(kSampleRate16kHz),
       echo_path_gain_change(false),
       prev_analog_mic_level(-1),
-      prev_pre_amp_gain(-1.f) {}
+      prev_pre_amp_gain(-1.f),
+      playout_volume(-1),
+      prev_playout_volume(-1) {}
 
 AudioProcessingImpl::ApmCaptureState::~ApmCaptureState() = default;
 
diff --git a/modules/audio_processing/audio_processing_impl.h b/modules/audio_processing/audio_processing_impl.h
index 47eed0a..a2b023a 100644
--- a/modules/audio_processing/audio_processing_impl.h
+++ b/modules/audio_processing/audio_processing_impl.h
@@ -398,6 +398,8 @@
     bool echo_path_gain_change;
     int prev_analog_mic_level;
     float prev_pre_amp_gain;
+    int playout_volume;
+    int prev_playout_volume;
     AudioProcessingStats stats;
   } capture_ RTC_GUARDED_BY(crit_capture_);
 
diff --git a/modules/audio_processing/audio_processing_impl_unittest.cc b/modules/audio_processing/audio_processing_impl_unittest.cc
index 7359f6b..d688db0 100644
--- a/modules/audio_processing/audio_processing_impl_unittest.cc
+++ b/modules/audio_processing/audio_processing_impl_unittest.cc
@@ -296,6 +296,59 @@
   apm->ProcessStream(&frame);
 }
 
+TEST(AudioProcessingImplTest, EchoControllerObservesPlayoutVolumeChange) {
+  // Tests that the echo controller observes an echo path gain change when a
+  // playout volume change is reported.
+  auto echo_control_factory = absl::make_unique<MockEchoControlFactory>();
+  const auto* echo_control_factory_ptr = echo_control_factory.get();
+
+  std::unique_ptr<AudioProcessing> apm(
+      AudioProcessingBuilder()
+          .SetEchoControlFactory(std::move(echo_control_factory))
+          .Create());
+  apm->gain_control()->Enable(false);  // Disable AGC.
+  apm->gain_control()->set_mode(GainControl::Mode::kFixedDigital);
+
+  AudioFrame frame;
+  constexpr int16_t kAudioLevel = 10000;
+  constexpr size_t kSampleRateHz = 48000;
+  constexpr size_t kNumChannels = 2;
+  InitializeAudioFrame(kSampleRateHz, kNumChannels, &frame);
+  FillFixedFrame(kAudioLevel, &frame);
+
+  MockEchoControl* echo_control_mock = echo_control_factory_ptr->GetNext();
+
+  EXPECT_CALL(*echo_control_mock, AnalyzeCapture(NotNull())).Times(1);
+  EXPECT_CALL(*echo_control_mock,
+              ProcessCapture(NotNull(), /*echo_path_change=*/false))
+      .Times(1);
+  apm->ProcessStream(&frame);
+
+  EXPECT_CALL(*echo_control_mock, AnalyzeCapture(NotNull())).Times(1);
+  EXPECT_CALL(*echo_control_mock,
+              ProcessCapture(NotNull(), /*echo_path_change=*/false))
+      .Times(1);
+  apm->SetRuntimeSetting(
+      AudioProcessing::RuntimeSetting::CreatePlayoutVolumeChange(50));
+  apm->ProcessStream(&frame);
+
+  EXPECT_CALL(*echo_control_mock, AnalyzeCapture(NotNull())).Times(1);
+  EXPECT_CALL(*echo_control_mock,
+              ProcessCapture(NotNull(), /*echo_path_change=*/false))
+      .Times(1);
+  apm->SetRuntimeSetting(
+      AudioProcessing::RuntimeSetting::CreatePlayoutVolumeChange(50));
+  apm->ProcessStream(&frame);
+
+  EXPECT_CALL(*echo_control_mock, AnalyzeCapture(NotNull())).Times(1);
+  EXPECT_CALL(*echo_control_mock,
+              ProcessCapture(NotNull(), /*echo_path_change=*/true))
+      .Times(1);
+  apm->SetRuntimeSetting(
+      AudioProcessing::RuntimeSetting::CreatePlayoutVolumeChange(100));
+  apm->ProcessStream(&frame);
+}
+
 TEST(AudioProcessingImplTest, RenderPreProcessorBeforeEchoDetector) {
   // Make sure that signal changes caused by a render pre-processing sub-module
   // take place before any echo detector analysis.
diff --git a/modules/audio_processing/debug.proto b/modules/audio_processing/debug.proto
index 2af7c81..0c50a65 100644
--- a/modules/audio_processing/debug.proto
+++ b/modules/audio_processing/debug.proto
@@ -84,6 +84,7 @@
   optional float capture_pre_gain = 1;
   optional float custom_render_processing_setting = 2;
   optional float capture_fixed_post_gain = 3;
+  optional int32 playout_volume_change = 4;
 }
 
 message Event {
diff --git a/modules/audio_processing/include/audio_processing.h b/modules/audio_processing/include/audio_processing.h
index 68054d1..f6e4331 100644
--- a/modules/audio_processing/include/audio_processing.h
+++ b/modules/audio_processing/include/audio_processing.h
@@ -392,6 +392,7 @@
       kCapturePreGain,
       kCaptureCompressionGain,
       kCaptureFixedPostGain,
+      kPlayoutVolumeChange,
       kCustomRenderProcessingRuntimeSetting
     };
 
@@ -419,6 +420,10 @@
       return {Type::kCaptureFixedPostGain, gain_db};
     }
 
+    static RuntimeSetting CreatePlayoutVolumeChange(int volume) {
+      return {Type::kPlayoutVolumeChange, volume};
+    }
+
     static RuntimeSetting CreateCustomRenderSetting(float payload) {
       return {Type::kCustomRenderProcessingRuntimeSetting, payload};
     }
@@ -426,13 +431,24 @@
     Type type() const { return type_; }
     void GetFloat(float* value) const {
       RTC_DCHECK(value);
-      *value = value_;
+      *value = value_.float_value;
+    }
+    void GetInt(int* value) const {
+      RTC_DCHECK(value);
+      *value = value_.int_value;
     }
 
    private:
     RuntimeSetting(Type id, float value) : type_(id), value_(value) {}
+    RuntimeSetting(Type id, int value) : type_(id), value_(value) {}
     Type type_;
-    float value_;
+    union U {
+      U() {}
+      U(int value) : int_value(value) {}
+      U(float value) : float_value(value) {}
+      float float_value;
+      int int_value;
+    } value_;
   };
 
   ~AudioProcessing() override {}
diff --git a/modules/audio_processing/test/aec_dump_based_simulator.cc b/modules/audio_processing/test/aec_dump_based_simulator.cc
index e52dfcf..00fd25e 100644
--- a/modules/audio_processing/test/aec_dump_based_simulator.cc
+++ b/modules/audio_processing/test/aec_dump_based_simulator.cc
@@ -584,6 +584,10 @@
           AudioProcessing::RuntimeSetting::CreateCaptureFixedPostGain(
               msg.capture_fixed_post_gain()));
     }
+  } else if (msg.has_playout_volume_change()) {
+    ap_->SetRuntimeSetting(
+        AudioProcessing::RuntimeSetting::CreatePlayoutVolumeChange(
+            msg.playout_volume_change()));
   }
 }
 
diff --git a/modules/audio_processing/test/runtime_setting_util.cc b/modules/audio_processing/test/runtime_setting_util.cc
index bc5f700..a78ca18 100644
--- a/modules/audio_processing/test/runtime_setting_util.cc
+++ b/modules/audio_processing/test/runtime_setting_util.cc
@@ -18,10 +18,11 @@
                           const webrtc::audioproc::RuntimeSetting& setting) {
   RTC_CHECK(apm);
   // TODO(bugs.webrtc.org/9138): Add ability to handle different types
-  // of settings. Currently only CapturePreGain and CaptureFixedPostGain are
-  // supported.
+  // of settings. Currently CapturePreGain, CaptureFixedPostGain and
+  // PlayoutVolumeChange are supported.
   RTC_CHECK(setting.has_capture_pre_gain() ||
-            setting.has_capture_fixed_post_gain());
+            setting.has_capture_fixed_post_gain() ||
+            setting.has_playout_volume_change());
 
   if (setting.has_capture_pre_gain()) {
     apm->SetRuntimeSetting(
@@ -31,6 +32,10 @@
     apm->SetRuntimeSetting(
         AudioProcessing::RuntimeSetting::CreateCaptureFixedPostGain(
             setting.capture_fixed_post_gain()));
+  } else if (setting.has_playout_volume_change()) {
+    apm->SetRuntimeSetting(
+        AudioProcessing::RuntimeSetting::CreatePlayoutVolumeChange(
+            setting.playout_volume_change()));
   }
 }
 }  // namespace webrtc