Enabling a safe fall-back functionality for overruns in the runtime settings
Bug: b/177830919
Change-Id: I9369f6fc004ceb2b626d33b36262bc8aeabdb1a0
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/206988
Commit-Queue: Per Åhgren <peah@webrtc.org>
Reviewed-by: Alessio Bazzica <alessiob@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#33371}
diff --git a/api/audio/echo_control.h b/api/audio/echo_control.h
index 8d567bf..74fbc27 100644
--- a/api/audio/echo_control.h
+++ b/api/audio/echo_control.h
@@ -48,6 +48,13 @@
// Provides an optional external estimate of the audio buffer delay.
virtual void SetAudioBufferDelay(int delay_ms) = 0;
+ // Specifies whether the capture output will be used. The purpose of this is
+ // to allow the echo controller to deactivate some of the processing when the
+ // resulting output is anyway not used, for instance when the endpoint is
+ // muted.
+ // TODO(b/177830919): Make pure virtual.
+ virtual void SetCaptureOutputUsage(bool capture_output_used) {}
+
// Returns wheter the signal is altered.
virtual bool ActiveProcessing() const = 0;
diff --git a/modules/audio_processing/aec3/echo_canceller3.cc b/modules/audio_processing/aec3/echo_canceller3.cc
index 0f8e35d..698ede7 100644
--- a/modules/audio_processing/aec3/echo_canceller3.cc
+++ b/modules/audio_processing/aec3/echo_canceller3.cc
@@ -833,6 +833,11 @@
block_processor_->SetAudioBufferDelay(delay_ms);
}
+void EchoCanceller3::SetCaptureOutputUsage(bool capture_output_used) {
+ // TODO(b/177830919): Add functionality for reducing the complexity when the
+ // echo canceller output is not used.
+}
+
bool EchoCanceller3::ActiveProcessing() const {
return true;
}
diff --git a/modules/audio_processing/aec3/echo_canceller3.h b/modules/audio_processing/aec3/echo_canceller3.h
index bacd5df..a4aab49 100644
--- a/modules/audio_processing/aec3/echo_canceller3.h
+++ b/modules/audio_processing/aec3/echo_canceller3.h
@@ -118,6 +118,12 @@
// Provides an optional external estimate of the audio buffer delay.
void SetAudioBufferDelay(int delay_ms) override;
+ // Specifies whether the capture output will be used. The purpose of this is
+ // to allow the echo controller to deactivate some of the processing when the
+ // resulting output is anyway not used, for instance when the endpoint is
+ // muted.
+ void SetCaptureOutputUsage(bool capture_output_used) override;
+
bool ActiveProcessing() const override;
// Signals whether an external detector has detected echo leakage from the
diff --git a/modules/audio_processing/audio_processing_impl.cc b/modules/audio_processing/audio_processing_impl.cc
index 1d32c85..79a3151 100644
--- a/modules/audio_processing/audio_processing_impl.cc
+++ b/modules/audio_processing/audio_processing_impl.cc
@@ -49,8 +49,6 @@
namespace webrtc {
-constexpr int kRuntimeSettingQueueSize = 100;
-
namespace {
static bool LayoutHasKeyboard(AudioProcessing::ChannelLayout layout) {
@@ -253,8 +251,8 @@
new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
use_setup_specific_default_aec3_config_(
UseSetupSpecificDefaultAec3Congfig()),
- capture_runtime_settings_(kRuntimeSettingQueueSize),
- render_runtime_settings_(kRuntimeSettingQueueSize),
+ capture_runtime_settings_(RuntimeSettingQueueSize()),
+ render_runtime_settings_(RuntimeSettingQueueSize()),
capture_runtime_settings_enqueuer_(&capture_runtime_settings_),
render_runtime_settings_enqueuer_(&render_runtime_settings_),
echo_control_factory_(std::move(echo_control_factory)),
@@ -674,6 +672,10 @@
submodules_.agc_manager->HandleCaptureOutputUsedChange(
capture_.capture_output_used);
}
+ if (submodules_.echo_controller) {
+ submodules_.echo_controller->SetCaptureOutputUsage(
+ capture_.capture_output_used);
+ }
}
void AudioProcessingImpl::SetRuntimeSetting(RuntimeSetting setting) {
@@ -720,19 +722,13 @@
bool AudioProcessingImpl::RuntimeSettingEnqueuer::Enqueue(
RuntimeSetting setting) {
- int remaining_attempts = 10;
- while (!runtime_settings_.Insert(&setting) && remaining_attempts-- > 0) {
- RuntimeSetting setting_to_discard;
- if (runtime_settings_.Remove(&setting_to_discard)) {
- RTC_LOG(LS_ERROR)
- << "The runtime settings queue is full. Oldest setting discarded.";
- }
- }
- if (remaining_attempts == 0) {
+ const bool successful_insert = runtime_settings_.Insert(&setting);
+
+ if (!successful_insert) {
RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.ApmRuntimeSettingCannotEnqueue", 1);
RTC_LOG(LS_ERROR) << "Cannot enqueue a new runtime setting.";
}
- return remaining_attempts == 0;
+ return successful_insert;
}
int AudioProcessingImpl::MaybeInitializeCapture(
@@ -806,6 +802,7 @@
void AudioProcessingImpl::HandleCaptureRuntimeSettings() {
RuntimeSetting setting;
+ int num_settings_processed = 0;
while (capture_runtime_settings_.Remove(&setting)) {
if (aec_dump_) {
aec_dump_->WriteRuntimeSetting(setting);
@@ -864,6 +861,23 @@
HandleCaptureOutputUsedSetting(value);
break;
}
+ ++num_settings_processed;
+ }
+
+ if (num_settings_processed >= RuntimeSettingQueueSize()) {
+ // Handle overrun of the runtime settings queue, which likely will has
+ // caused settings to be discarded.
+ HandleOverrunInCaptureRuntimeSettingsQueue();
+ }
+}
+
+void AudioProcessingImpl::HandleOverrunInCaptureRuntimeSettingsQueue() {
+ // Fall back to a safe state for the case when a setting for capture output
+ // usage setting has been missed.
+ capture_.capture_output_used = true;
+ if (submodules_.echo_controller) {
+ submodules_.echo_controller->SetCaptureOutputUsage(
+ capture_.capture_output_used);
}
}
diff --git a/modules/audio_processing/audio_processing_impl.h b/modules/audio_processing/audio_processing_impl.h
index 23028ec..8306ac7 100644
--- a/modules/audio_processing/audio_processing_impl.h
+++ b/modules/audio_processing/audio_processing_impl.h
@@ -342,6 +342,12 @@
void RecordAudioProcessingState()
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_);
+ // Ensures that overruns in the capture runtime settings queue is properly
+ // handled by the code, providing safe-fallbacks to mitigate the implications
+ // of any settings being missed.
+ void HandleOverrunInCaptureRuntimeSettingsQueue()
+ RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_);
+
// AecDump instance used for optionally logging APM config, input
// and output to file in the AEC-dump format defined in debug.proto.
std::unique_ptr<AecDump> aec_dump_;
diff --git a/modules/audio_processing/audio_processing_impl_unittest.cc b/modules/audio_processing/audio_processing_impl_unittest.cc
index e289c31..65de4d6 100644
--- a/modules/audio_processing/audio_processing_impl_unittest.cc
+++ b/modules/audio_processing/audio_processing_impl_unittest.cc
@@ -14,6 +14,7 @@
#include <memory>
#include "api/scoped_refptr.h"
+#include "modules/audio_processing/common.h"
#include "modules/audio_processing/include/audio_processing.h"
#include "modules/audio_processing/optionally_built_submodule_creators.h"
#include "modules/audio_processing/test/audio_processing_builder_for_testing.h"
@@ -202,6 +203,88 @@
<< "Frame should be amplified.";
}
+TEST(AudioProcessingImplTest, EchoControllerObservesSetCaptureUsageChange) {
+ // Tests that the echo controller observes that the capture usage has been
+ // updated.
+ auto echo_control_factory = std::make_unique<MockEchoControlFactory>();
+ const MockEchoControlFactory* echo_control_factory_ptr =
+ echo_control_factory.get();
+
+ std::unique_ptr<AudioProcessing> apm(
+ AudioProcessingBuilderForTesting()
+ .SetEchoControlFactory(std::move(echo_control_factory))
+ .Create());
+
+ constexpr int16_t kAudioLevel = 10000;
+ constexpr int kSampleRateHz = 48000;
+ constexpr int kNumChannels = 2;
+ std::array<int16_t, kNumChannels * kSampleRateHz / 100> frame;
+ StreamConfig config(kSampleRateHz, kNumChannels, /*has_keyboard=*/false);
+ frame.fill(kAudioLevel);
+
+ MockEchoControl* echo_control_mock = echo_control_factory_ptr->GetNext();
+
+ // Ensure that SetCaptureOutputUsage is not called when no runtime settings
+ // are passed.
+ EXPECT_CALL(*echo_control_mock, SetCaptureOutputUsage(testing::_)).Times(0);
+ apm->ProcessStream(frame.data(), config, config, frame.data());
+
+ // Ensure that SetCaptureOutputUsage is called with the right information when
+ // a runtime setting is passed.
+ EXPECT_CALL(*echo_control_mock,
+ SetCaptureOutputUsage(/*capture_output_used=*/false))
+ .Times(1);
+ EXPECT_TRUE(apm->PostRuntimeSetting(
+ AudioProcessing::RuntimeSetting::CreateCaptureOutputUsedSetting(
+ /*capture_output_used=*/false)));
+ apm->ProcessStream(frame.data(), config, config, frame.data());
+
+ EXPECT_CALL(*echo_control_mock,
+ SetCaptureOutputUsage(/*capture_output_used=*/true))
+ .Times(1);
+ EXPECT_TRUE(apm->PostRuntimeSetting(
+ AudioProcessing::RuntimeSetting::CreateCaptureOutputUsedSetting(
+ /*capture_output_used=*/true)));
+ apm->ProcessStream(frame.data(), config, config, frame.data());
+
+ // The number of positions to place items in the queue is equal to the queue
+ // size minus 1.
+ constexpr int kNumSlotsInQueue = RuntimeSettingQueueSize();
+
+ // Ensure that SetCaptureOutputUsage is called with the right information when
+ // many runtime settings are passed.
+ for (int k = 0; k < kNumSlotsInQueue - 1; ++k) {
+ EXPECT_TRUE(apm->PostRuntimeSetting(
+ AudioProcessing::RuntimeSetting::CreateCaptureOutputUsedSetting(
+ /*capture_output_used=*/false)));
+ }
+ EXPECT_CALL(*echo_control_mock,
+ SetCaptureOutputUsage(/*capture_output_used=*/false))
+ .Times(kNumSlotsInQueue - 1);
+ apm->ProcessStream(frame.data(), config, config, frame.data());
+
+ // Ensure that SetCaptureOutputUsage is properly called with the fallback
+ // value when the runtime settings queue becomes full.
+ for (int k = 0; k < kNumSlotsInQueue; ++k) {
+ EXPECT_TRUE(apm->PostRuntimeSetting(
+ AudioProcessing::RuntimeSetting::CreateCaptureOutputUsedSetting(
+ /*capture_output_used=*/false)));
+ }
+ EXPECT_FALSE(apm->PostRuntimeSetting(
+ AudioProcessing::RuntimeSetting::CreateCaptureOutputUsedSetting(
+ /*capture_output_used=*/false)));
+ EXPECT_FALSE(apm->PostRuntimeSetting(
+ AudioProcessing::RuntimeSetting::CreateCaptureOutputUsedSetting(
+ /*capture_output_used=*/false)));
+ EXPECT_CALL(*echo_control_mock,
+ SetCaptureOutputUsage(/*capture_output_used=*/false))
+ .Times(kNumSlotsInQueue);
+ EXPECT_CALL(*echo_control_mock,
+ SetCaptureOutputUsage(/*capture_output_used=*/true))
+ .Times(1);
+ apm->ProcessStream(frame.data(), config, config, frame.data());
+}
+
TEST(AudioProcessingImplTest,
EchoControllerObservesPreAmplifierEchoPathGainChange) {
// Tests that the echo controller observes an echo path gain change when the
diff --git a/modules/audio_processing/common.h b/modules/audio_processing/common.h
index d8532c5..2c88c4e 100644
--- a/modules/audio_processing/common.h
+++ b/modules/audio_processing/common.h
@@ -16,6 +16,10 @@
namespace webrtc {
+constexpr int RuntimeSettingQueueSize() {
+ return 100;
+}
+
static inline size_t ChannelsFromLayout(AudioProcessing::ChannelLayout layout) {
switch (layout) {
case AudioProcessing::kMono:
diff --git a/modules/audio_processing/include/audio_processing.h b/modules/audio_processing/include/audio_processing.h
index 2312ffd..36d8256 100644
--- a/modules/audio_processing/include/audio_processing.h
+++ b/modules/audio_processing/include/audio_processing.h
@@ -431,8 +431,9 @@
return {Type::kCustomRenderProcessingRuntimeSetting, payload};
}
- static RuntimeSetting CreateCaptureOutputUsedSetting(bool payload) {
- return {Type::kCaptureOutputUsed, payload};
+ static RuntimeSetting CreateCaptureOutputUsedSetting(
+ bool capture_output_used) {
+ return {Type::kCaptureOutputUsed, capture_output_used};
}
Type type() const { return type_; }
diff --git a/modules/audio_processing/test/echo_control_mock.h b/modules/audio_processing/test/echo_control_mock.h
index 927de43..763d6e4 100644
--- a/modules/audio_processing/test/echo_control_mock.h
+++ b/modules/audio_processing/test/echo_control_mock.h
@@ -34,6 +34,10 @@
(override));
MOCK_METHOD(EchoControl::Metrics, GetMetrics, (), (const, override));
MOCK_METHOD(void, SetAudioBufferDelay, (int delay_ms), (override));
+ MOCK_METHOD(void,
+ SetCaptureOutputUsage,
+ (bool capture_output_used),
+ (override));
MOCK_METHOD(bool, ActiveProcessing, (), (const, override));
};