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}
This commit is contained in:
Per Åhgren 2021-03-03 10:52:44 +00:00 committed by Commit Bot
parent 99bcf60a41
commit 652ada5029
9 changed files with 146 additions and 16 deletions

View File

@ -48,6 +48,13 @@ class EchoControl {
// Provides an optional external estimate of the audio buffer delay. // Provides an optional external estimate of the audio buffer delay.
virtual void SetAudioBufferDelay(int delay_ms) = 0; 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. // Returns wheter the signal is altered.
virtual bool ActiveProcessing() const = 0; virtual bool ActiveProcessing() const = 0;

View File

@ -833,6 +833,11 @@ void EchoCanceller3::SetAudioBufferDelay(int delay_ms) {
block_processor_->SetAudioBufferDelay(delay_ms); 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 { bool EchoCanceller3::ActiveProcessing() const {
return true; return true;
} }

View File

@ -118,6 +118,12 @@ class EchoCanceller3 : public EchoControl {
// Provides an optional external estimate of the audio buffer delay. // Provides an optional external estimate of the audio buffer delay.
void SetAudioBufferDelay(int delay_ms) override; 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; bool ActiveProcessing() const override;
// Signals whether an external detector has detected echo leakage from the // Signals whether an external detector has detected echo leakage from the

View File

@ -49,8 +49,6 @@
namespace webrtc { namespace webrtc {
constexpr int kRuntimeSettingQueueSize = 100;
namespace { namespace {
static bool LayoutHasKeyboard(AudioProcessing::ChannelLayout layout) { static bool LayoutHasKeyboard(AudioProcessing::ChannelLayout layout) {
@ -253,8 +251,8 @@ AudioProcessingImpl::AudioProcessingImpl(
new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))), new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
use_setup_specific_default_aec3_config_( use_setup_specific_default_aec3_config_(
UseSetupSpecificDefaultAec3Congfig()), UseSetupSpecificDefaultAec3Congfig()),
capture_runtime_settings_(kRuntimeSettingQueueSize), capture_runtime_settings_(RuntimeSettingQueueSize()),
render_runtime_settings_(kRuntimeSettingQueueSize), render_runtime_settings_(RuntimeSettingQueueSize()),
capture_runtime_settings_enqueuer_(&capture_runtime_settings_), capture_runtime_settings_enqueuer_(&capture_runtime_settings_),
render_runtime_settings_enqueuer_(&render_runtime_settings_), render_runtime_settings_enqueuer_(&render_runtime_settings_),
echo_control_factory_(std::move(echo_control_factory)), echo_control_factory_(std::move(echo_control_factory)),
@ -674,6 +672,10 @@ void AudioProcessingImpl::HandleCaptureOutputUsedSetting(
submodules_.agc_manager->HandleCaptureOutputUsedChange( submodules_.agc_manager->HandleCaptureOutputUsedChange(
capture_.capture_output_used); capture_.capture_output_used);
} }
if (submodules_.echo_controller) {
submodules_.echo_controller->SetCaptureOutputUsage(
capture_.capture_output_used);
}
} }
void AudioProcessingImpl::SetRuntimeSetting(RuntimeSetting setting) { void AudioProcessingImpl::SetRuntimeSetting(RuntimeSetting setting) {
@ -720,19 +722,13 @@ AudioProcessingImpl::RuntimeSettingEnqueuer::~RuntimeSettingEnqueuer() =
bool AudioProcessingImpl::RuntimeSettingEnqueuer::Enqueue( bool AudioProcessingImpl::RuntimeSettingEnqueuer::Enqueue(
RuntimeSetting setting) { RuntimeSetting setting) {
int remaining_attempts = 10; const bool successful_insert = runtime_settings_.Insert(&setting);
while (!runtime_settings_.Insert(&setting) && remaining_attempts-- > 0) {
RuntimeSetting setting_to_discard; if (!successful_insert) {
if (runtime_settings_.Remove(&setting_to_discard)) {
RTC_LOG(LS_ERROR)
<< "The runtime settings queue is full. Oldest setting discarded.";
}
}
if (remaining_attempts == 0) {
RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.ApmRuntimeSettingCannotEnqueue", 1); RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.ApmRuntimeSettingCannotEnqueue", 1);
RTC_LOG(LS_ERROR) << "Cannot enqueue a new runtime setting."; RTC_LOG(LS_ERROR) << "Cannot enqueue a new runtime setting.";
} }
return remaining_attempts == 0; return successful_insert;
} }
int AudioProcessingImpl::MaybeInitializeCapture( int AudioProcessingImpl::MaybeInitializeCapture(
@ -806,6 +802,7 @@ int AudioProcessingImpl::ProcessStream(const float* const* src,
void AudioProcessingImpl::HandleCaptureRuntimeSettings() { void AudioProcessingImpl::HandleCaptureRuntimeSettings() {
RuntimeSetting setting; RuntimeSetting setting;
int num_settings_processed = 0;
while (capture_runtime_settings_.Remove(&setting)) { while (capture_runtime_settings_.Remove(&setting)) {
if (aec_dump_) { if (aec_dump_) {
aec_dump_->WriteRuntimeSetting(setting); aec_dump_->WriteRuntimeSetting(setting);
@ -864,6 +861,23 @@ void AudioProcessingImpl::HandleCaptureRuntimeSettings() {
HandleCaptureOutputUsedSetting(value); HandleCaptureOutputUsedSetting(value);
break; 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);
} }
} }

View File

@ -342,6 +342,12 @@ class AudioProcessingImpl : public AudioProcessing {
void RecordAudioProcessingState() void RecordAudioProcessingState()
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_); 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 // AecDump instance used for optionally logging APM config, input
// and output to file in the AEC-dump format defined in debug.proto. // and output to file in the AEC-dump format defined in debug.proto.
std::unique_ptr<AecDump> aec_dump_; std::unique_ptr<AecDump> aec_dump_;

View File

@ -14,6 +14,7 @@
#include <memory> #include <memory>
#include "api/scoped_refptr.h" #include "api/scoped_refptr.h"
#include "modules/audio_processing/common.h"
#include "modules/audio_processing/include/audio_processing.h" #include "modules/audio_processing/include/audio_processing.h"
#include "modules/audio_processing/optionally_built_submodule_creators.h" #include "modules/audio_processing/optionally_built_submodule_creators.h"
#include "modules/audio_processing/test/audio_processing_builder_for_testing.h" #include "modules/audio_processing/test/audio_processing_builder_for_testing.h"
@ -202,6 +203,88 @@ TEST(AudioProcessingImplTest, UpdateCapturePreGainRuntimeSetting) {
<< "Frame should be amplified."; << "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, TEST(AudioProcessingImplTest,
EchoControllerObservesPreAmplifierEchoPathGainChange) { EchoControllerObservesPreAmplifierEchoPathGainChange) {
// Tests that the echo controller observes an echo path gain change when the // Tests that the echo controller observes an echo path gain change when the

View File

@ -16,6 +16,10 @@
namespace webrtc { namespace webrtc {
constexpr int RuntimeSettingQueueSize() {
return 100;
}
static inline size_t ChannelsFromLayout(AudioProcessing::ChannelLayout layout) { static inline size_t ChannelsFromLayout(AudioProcessing::ChannelLayout layout) {
switch (layout) { switch (layout) {
case AudioProcessing::kMono: case AudioProcessing::kMono:

View File

@ -431,8 +431,9 @@ class RTC_EXPORT AudioProcessing : public rtc::RefCountInterface {
return {Type::kCustomRenderProcessingRuntimeSetting, payload}; return {Type::kCustomRenderProcessingRuntimeSetting, payload};
} }
static RuntimeSetting CreateCaptureOutputUsedSetting(bool payload) { static RuntimeSetting CreateCaptureOutputUsedSetting(
return {Type::kCaptureOutputUsed, payload}; bool capture_output_used) {
return {Type::kCaptureOutputUsed, capture_output_used};
} }
Type type() const { return type_; } Type type() const { return type_; }

View File

@ -34,6 +34,10 @@ class MockEchoControl : public EchoControl {
(override)); (override));
MOCK_METHOD(EchoControl::Metrics, GetMetrics, (), (const, override)); MOCK_METHOD(EchoControl::Metrics, GetMetrics, (), (const, override));
MOCK_METHOD(void, SetAudioBufferDelay, (int delay_ms), (override)); MOCK_METHOD(void, SetAudioBufferDelay, (int delay_ms), (override));
MOCK_METHOD(void,
SetCaptureOutputUsage,
(bool capture_output_used),
(override));
MOCK_METHOD(bool, ActiveProcessing, (), (const, override)); MOCK_METHOD(bool, ActiveProcessing, (), (const, override));
}; };