AudioProcessingImpl: Remove the use of transient suppressor
Remove the use of transient suppression, i.e.: - Transient suppressor submodule (ignore the config), - WebRTC-TransientSuppressorForcedOff fieldtrial, - Voice activity detection submodule (use AGC2/AGC VAD instead), - Submodule overrides, and - WEBRTC_EXCLUDE_TRANSIENT_SUPPRESSOR macro. Bug: webrtc:7494, webrtc:13663, webrtc:357281131 Change-Id: I7edb46c7ff048992ac5a10473800405bad268895 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/355880 Reviewed-by: Henrik Andreassson <henrika@webrtc.org> Commit-Queue: Hanna Silen <silen@webrtc.org> Reviewed-by: Gustaf Ullberg <gustaf@webrtc.org> Cr-Commit-Position: refs/heads/main@{#42724}
This commit is contained in:
parent
53c424eba7
commit
90c430cda4
4
BUILD.gn
4
BUILD.gn
@ -367,10 +367,6 @@ config("common_config") {
|
||||
defines += [ "RTC_DISABLE_METRICS" ]
|
||||
}
|
||||
|
||||
if (rtc_exclude_transient_suppressor) {
|
||||
defines += [ "WEBRTC_EXCLUDE_TRANSIENT_SUPPRESSOR" ]
|
||||
}
|
||||
|
||||
if (rtc_exclude_audio_processing_module) {
|
||||
defines += [ "WEBRTC_EXCLUDE_AUDIO_PROCESSING_MODULE" ]
|
||||
}
|
||||
|
||||
@ -152,7 +152,6 @@ rtc_library("audio_processing") {
|
||||
":audio_frame_view",
|
||||
":gain_controller2",
|
||||
":high_pass_filter",
|
||||
":optionally_built_submodule_creators",
|
||||
":rms_level",
|
||||
"../../api:array_view",
|
||||
"../../api:function_view",
|
||||
@ -191,7 +190,6 @@ rtc_library("audio_processing") {
|
||||
"agc2:input_volume_stats_reporter",
|
||||
"capture_levels_adjuster",
|
||||
"ns",
|
||||
"transient:transient_suppressor_api",
|
||||
"vad",
|
||||
"//third_party/abseil-cpp/absl/base:nullability",
|
||||
"//third_party/abseil-cpp/absl/strings",
|
||||
@ -239,17 +237,6 @@ rtc_library("residual_echo_detector") {
|
||||
]
|
||||
}
|
||||
|
||||
rtc_library("optionally_built_submodule_creators") {
|
||||
sources = [
|
||||
"optionally_built_submodule_creators.cc",
|
||||
"optionally_built_submodule_creators.h",
|
||||
]
|
||||
deps = [
|
||||
"transient:transient_suppressor_api",
|
||||
"transient:transient_suppressor_impl",
|
||||
]
|
||||
}
|
||||
|
||||
rtc_source_set("rms_level") {
|
||||
visibility = [ "*" ]
|
||||
sources = [
|
||||
@ -435,7 +422,6 @@ if (rtc_include_tests) {
|
||||
":audioproc_protobuf_utils",
|
||||
":audioproc_test_utils",
|
||||
":audioproc_unittest_proto",
|
||||
":optionally_built_submodule_creators",
|
||||
":residual_echo_detector",
|
||||
":rms_level",
|
||||
":runtime_settings_protobuf_utils",
|
||||
|
||||
@ -31,7 +31,6 @@
|
||||
#include "modules/audio_processing/audio_buffer.h"
|
||||
#include "modules/audio_processing/include/audio_frame_view.h"
|
||||
#include "modules/audio_processing/logging/apm_data_dumper.h"
|
||||
#include "modules/audio_processing/optionally_built_submodule_creators.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/experiments/field_trial_parser.h"
|
||||
#include "rtc_base/logging.h"
|
||||
@ -324,12 +323,6 @@ constexpr int kUnspecifiedDataDumpInputVolume = -100;
|
||||
// Throughout webrtc, it's assumed that success is represented by zero.
|
||||
static_assert(AudioProcessing::kNoError == 0, "kNoError must be zero");
|
||||
|
||||
bool AudioProcessingImpl::UseApmVadSubModule(
|
||||
const AudioProcessing::Config& config) {
|
||||
// Without "WebRTC-Audio-GainController2" always return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
AudioProcessingImpl::SubmoduleStates::SubmoduleStates(
|
||||
bool capture_post_processor_enabled,
|
||||
bool render_pre_processor_enabled,
|
||||
@ -344,10 +337,8 @@ bool AudioProcessingImpl::SubmoduleStates::Update(
|
||||
bool noise_suppressor_enabled,
|
||||
bool adaptive_gain_controller_enabled,
|
||||
bool gain_controller2_enabled,
|
||||
bool voice_activity_detector_enabled,
|
||||
bool gain_adjustment_enabled,
|
||||
bool echo_controller_enabled,
|
||||
bool transient_suppressor_enabled) {
|
||||
bool echo_controller_enabled) {
|
||||
bool changed = false;
|
||||
changed |= (high_pass_filter_enabled != high_pass_filter_enabled_);
|
||||
changed |=
|
||||
@ -356,21 +347,16 @@ bool AudioProcessingImpl::SubmoduleStates::Update(
|
||||
changed |=
|
||||
(adaptive_gain_controller_enabled != adaptive_gain_controller_enabled_);
|
||||
changed |= (gain_controller2_enabled != gain_controller2_enabled_);
|
||||
changed |=
|
||||
(voice_activity_detector_enabled != voice_activity_detector_enabled_);
|
||||
changed |= (gain_adjustment_enabled != gain_adjustment_enabled_);
|
||||
changed |= (echo_controller_enabled != echo_controller_enabled_);
|
||||
changed |= (transient_suppressor_enabled != transient_suppressor_enabled_);
|
||||
if (changed) {
|
||||
high_pass_filter_enabled_ = high_pass_filter_enabled;
|
||||
mobile_echo_controller_enabled_ = mobile_echo_controller_enabled;
|
||||
noise_suppressor_enabled_ = noise_suppressor_enabled;
|
||||
adaptive_gain_controller_enabled_ = adaptive_gain_controller_enabled;
|
||||
gain_controller2_enabled_ = gain_controller2_enabled;
|
||||
voice_activity_detector_enabled_ = voice_activity_detector_enabled;
|
||||
gain_adjustment_enabled_ = gain_adjustment_enabled;
|
||||
echo_controller_enabled_ = echo_controller_enabled;
|
||||
transient_suppressor_enabled_ = transient_suppressor_enabled;
|
||||
}
|
||||
|
||||
changed |= first_update_;
|
||||
@ -447,7 +433,6 @@ AudioProcessingImpl::AudioProcessingImpl(
|
||||
: data_dumper_(new ApmDataDumper(instance_count_.fetch_add(1) + 1)),
|
||||
use_setup_specific_default_aec3_config_(
|
||||
UseSetupSpecificDefaultAec3Congfig()),
|
||||
transient_suppressor_vad_mode_(TransientSuppressor::VadMode::kDefault),
|
||||
capture_runtime_settings_(RuntimeSettingQueueSize()),
|
||||
render_runtime_settings_(RuntimeSettingQueueSize()),
|
||||
capture_runtime_settings_enqueuer_(&capture_runtime_settings_),
|
||||
@ -466,8 +451,7 @@ AudioProcessingImpl::AudioProcessingImpl(
|
||||
!field_trial::IsEnabled(
|
||||
"WebRTC-ApmExperimentalMultiChannelCaptureKillSwitch"),
|
||||
EnforceSplitBandHpf(),
|
||||
MinimizeProcessingForUnusedOutput(),
|
||||
field_trial::IsEnabled("WebRTC-TransientSuppressorForcedOff")),
|
||||
MinimizeProcessingForUnusedOutput()),
|
||||
capture_(),
|
||||
capture_nonlocked_(),
|
||||
applied_input_volume_stats_reporter_(
|
||||
@ -487,6 +471,10 @@ AudioProcessingImpl::AudioProcessingImpl(
|
||||
RTC_LOG(LS_INFO) << "Denormal disabler unsupported";
|
||||
}
|
||||
|
||||
// TODO(bugs.webrtc.org/7494): Remove transient suppression from the config.
|
||||
// Disable for clarity; enabling transient suppression has no effect.
|
||||
config_.transient_suppression.enabled = false;
|
||||
|
||||
RTC_LOG(LS_INFO) << "AudioProcessing: " << config_.ToString();
|
||||
|
||||
// Mark Echo Controller enabled if a factory is injected.
|
||||
@ -588,12 +576,10 @@ void AudioProcessingImpl::InitializeLocked() {
|
||||
AllocateRenderQueue();
|
||||
|
||||
InitializeGainController1();
|
||||
InitializeTransientSuppressor();
|
||||
InitializeHighPassFilter(true);
|
||||
InitializeResidualEchoDetector();
|
||||
InitializeEchoController();
|
||||
InitializeGainController2();
|
||||
InitializeVoiceActivityDetector();
|
||||
InitializeNoiseSuppressor();
|
||||
InitializeAnalyzer();
|
||||
InitializePostProcessor();
|
||||
@ -714,9 +700,6 @@ void AudioProcessingImpl::ApplyConfig(const AudioProcessing::Config& config) {
|
||||
config_.noise_suppression.enabled != config.noise_suppression.enabled ||
|
||||
config_.noise_suppression.level != config.noise_suppression.level;
|
||||
|
||||
const bool ts_config_changed = config_.transient_suppression.enabled !=
|
||||
config.transient_suppression.enabled;
|
||||
|
||||
const bool pre_amplifier_config_changed =
|
||||
config_.pre_amplifier.enabled != config.pre_amplifier.enabled ||
|
||||
config_.pre_amplifier.fixed_gain_factor !=
|
||||
@ -727,6 +710,10 @@ void AudioProcessingImpl::ApplyConfig(const AudioProcessing::Config& config) {
|
||||
|
||||
config_ = config;
|
||||
|
||||
// TODO(bugs.webrtc.org/7494): Remove transient suppression from the config.
|
||||
// Disable for clarity; enabling transient suppression has no effect.
|
||||
config_.transient_suppression.enabled = false;
|
||||
|
||||
if (aec_config_changed) {
|
||||
InitializeEchoController();
|
||||
}
|
||||
@ -735,10 +722,6 @@ void AudioProcessingImpl::ApplyConfig(const AudioProcessing::Config& config) {
|
||||
InitializeNoiseSuppressor();
|
||||
}
|
||||
|
||||
if (ts_config_changed) {
|
||||
InitializeTransientSuppressor();
|
||||
}
|
||||
|
||||
InitializeHighPassFilter(false);
|
||||
|
||||
if (agc1_config_changed) {
|
||||
@ -752,11 +735,8 @@ void AudioProcessingImpl::ApplyConfig(const AudioProcessing::Config& config) {
|
||||
config_.gain_controller2 = AudioProcessing::Config::GainController2();
|
||||
}
|
||||
|
||||
if (agc2_config_changed || ts_config_changed) {
|
||||
// AGC2 also depends on TS because of the possible dependency on the APM VAD
|
||||
// sub-module.
|
||||
if (agc2_config_changed) {
|
||||
InitializeGainController2();
|
||||
InitializeVoiceActivityDetector();
|
||||
}
|
||||
|
||||
if (pre_amplifier_config_changed || gain_adjustment_config_changed) {
|
||||
@ -770,12 +750,6 @@ void AudioProcessingImpl::ApplyConfig(const AudioProcessing::Config& config) {
|
||||
}
|
||||
}
|
||||
|
||||
void AudioProcessingImpl::OverrideSubmoduleCreationForTesting(
|
||||
const ApmSubmoduleCreationOverrides& overrides) {
|
||||
MutexLock lock(&mutex_capture_);
|
||||
submodule_creation_overrides_ = overrides;
|
||||
}
|
||||
|
||||
int AudioProcessingImpl::proc_sample_rate_hz() const {
|
||||
// Used as callback from submodules, hence locking is not allowed.
|
||||
return capture_nonlocked_.capture_processing_format.sample_rate_hz();
|
||||
@ -1471,42 +1445,6 @@ int AudioProcessingImpl::ProcessCaptureStreamLocked() {
|
||||
capture_buffer->num_frames()));
|
||||
}
|
||||
|
||||
absl::optional<float> voice_probability;
|
||||
if (!!submodules_.voice_activity_detector) {
|
||||
voice_probability =
|
||||
submodules_.voice_activity_detector->Analyze(capture_buffer->view());
|
||||
}
|
||||
|
||||
if (submodules_.transient_suppressor) {
|
||||
float transient_suppressor_voice_probability = 1.0f;
|
||||
switch (transient_suppressor_vad_mode_) {
|
||||
case TransientSuppressor::VadMode::kDefault:
|
||||
if (submodules_.agc_manager) {
|
||||
transient_suppressor_voice_probability =
|
||||
submodules_.agc_manager->voice_probability();
|
||||
}
|
||||
break;
|
||||
case TransientSuppressor::VadMode::kRnnVad:
|
||||
RTC_DCHECK(voice_probability.has_value());
|
||||
transient_suppressor_voice_probability = *voice_probability;
|
||||
break;
|
||||
case TransientSuppressor::VadMode::kNoVad:
|
||||
// The transient suppressor will ignore `voice_probability`.
|
||||
break;
|
||||
}
|
||||
float delayed_voice_probability =
|
||||
submodules_.transient_suppressor->Suppress(
|
||||
capture_buffer->channels()[0], capture_buffer->num_frames(),
|
||||
capture_buffer->num_channels(),
|
||||
capture_buffer->split_bands_const(0)[kBand0To8kHz],
|
||||
capture_buffer->num_frames_per_band(),
|
||||
/*reference_data=*/nullptr, /*reference_length=*/0,
|
||||
transient_suppressor_voice_probability, capture_.key_pressed);
|
||||
if (voice_probability.has_value()) {
|
||||
*voice_probability = delayed_voice_probability;
|
||||
}
|
||||
}
|
||||
|
||||
// Experimental APM sub-module that analyzes `capture_buffer`.
|
||||
if (submodules_.capture_analyzer) {
|
||||
submodules_.capture_analyzer->Analyze(capture_buffer);
|
||||
@ -1516,8 +1454,8 @@ int AudioProcessingImpl::ProcessCaptureStreamLocked() {
|
||||
// TODO(bugs.webrtc.org/7494): Let AGC2 detect applied input volume
|
||||
// changes.
|
||||
submodules_.gain_controller2->Process(
|
||||
voice_probability, capture_.applied_input_volume_changed,
|
||||
capture_buffer);
|
||||
/*speech_probability=*/std::nullopt,
|
||||
capture_.applied_input_volume_changed, capture_buffer);
|
||||
}
|
||||
|
||||
if (submodules_.capture_post_processor) {
|
||||
@ -1916,43 +1854,9 @@ bool AudioProcessingImpl::UpdateActiveSubmoduleStates() {
|
||||
return submodule_states_.Update(
|
||||
config_.high_pass_filter.enabled, !!submodules_.echo_control_mobile,
|
||||
!!submodules_.noise_suppressor, !!submodules_.gain_control,
|
||||
!!submodules_.gain_controller2, !!submodules_.voice_activity_detector,
|
||||
!!submodules_.gain_controller2,
|
||||
config_.pre_amplifier.enabled || config_.capture_level_adjustment.enabled,
|
||||
capture_nonlocked_.echo_controller_enabled,
|
||||
!!submodules_.transient_suppressor);
|
||||
}
|
||||
|
||||
void AudioProcessingImpl::InitializeTransientSuppressor() {
|
||||
// Choose the VAD mode for TS and detect a VAD mode change.
|
||||
const TransientSuppressor::VadMode previous_vad_mode =
|
||||
transient_suppressor_vad_mode_;
|
||||
transient_suppressor_vad_mode_ = TransientSuppressor::VadMode::kDefault;
|
||||
if (UseApmVadSubModule(config_)) {
|
||||
transient_suppressor_vad_mode_ = TransientSuppressor::VadMode::kRnnVad;
|
||||
}
|
||||
const bool vad_mode_changed =
|
||||
previous_vad_mode != transient_suppressor_vad_mode_;
|
||||
|
||||
if (config_.transient_suppression.enabled &&
|
||||
!constants_.transient_suppressor_forced_off) {
|
||||
// Attempt to create a transient suppressor, if one is not already created.
|
||||
if (!submodules_.transient_suppressor || vad_mode_changed) {
|
||||
submodules_.transient_suppressor = CreateTransientSuppressor(
|
||||
submodule_creation_overrides_, transient_suppressor_vad_mode_,
|
||||
proc_fullband_sample_rate_hz(), capture_nonlocked_.split_rate,
|
||||
num_proc_channels());
|
||||
if (!submodules_.transient_suppressor) {
|
||||
RTC_LOG(LS_WARNING)
|
||||
<< "No transient suppressor created (probably disabled)";
|
||||
}
|
||||
} else {
|
||||
submodules_.transient_suppressor->Initialize(
|
||||
proc_fullband_sample_rate_hz(), capture_nonlocked_.split_rate,
|
||||
num_proc_channels());
|
||||
}
|
||||
} else {
|
||||
submodules_.transient_suppressor.reset();
|
||||
}
|
||||
capture_nonlocked_.echo_controller_enabled);
|
||||
}
|
||||
|
||||
void AudioProcessingImpl::InitializeHighPassFilter(bool forced_reset) {
|
||||
@ -2140,28 +2044,14 @@ void AudioProcessingImpl::InitializeGainController2() {
|
||||
// AGC2.
|
||||
const InputVolumeController::Config input_volume_controller_config =
|
||||
InputVolumeController::Config{};
|
||||
// If the APM VAD sub-module is not used, let AGC2 use its internal VAD.
|
||||
const bool use_internal_vad = !UseApmVadSubModule(config_);
|
||||
submodules_.gain_controller2 = std::make_unique<GainController2>(
|
||||
config_.gain_controller2, input_volume_controller_config,
|
||||
proc_fullband_sample_rate_hz(), num_output_channels(), use_internal_vad);
|
||||
proc_fullband_sample_rate_hz(), num_output_channels(),
|
||||
/*use_internal_vad=*/true);
|
||||
submodules_.gain_controller2->SetCaptureOutputUsed(
|
||||
capture_.capture_output_used);
|
||||
}
|
||||
|
||||
void AudioProcessingImpl::InitializeVoiceActivityDetector() {
|
||||
if (!UseApmVadSubModule(config_)) {
|
||||
submodules_.voice_activity_detector.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(bugs.webrtc.org/13663): Cache CPU features in APM and use here.
|
||||
submodules_.voice_activity_detector =
|
||||
std::make_unique<VoiceActivityDetectorWrapper>(
|
||||
submodules_.gain_controller2->GetCpuFeatures(),
|
||||
proc_fullband_sample_rate_hz());
|
||||
}
|
||||
|
||||
void AudioProcessingImpl::InitializeNoiseSuppressor() {
|
||||
submodules_.noise_suppressor.reset();
|
||||
|
||||
@ -2296,8 +2186,9 @@ void AudioProcessingImpl::WriteAecDumpConfigMessage(bool forced) {
|
||||
apm_config.ns_enabled = config_.noise_suppression.enabled;
|
||||
apm_config.ns_level = static_cast<int>(config_.noise_suppression.level);
|
||||
|
||||
apm_config.transient_suppression_enabled =
|
||||
config_.transient_suppression.enabled;
|
||||
// TODO(bugs.webrtc.org/7494): Remove transient suppression from the config.
|
||||
// Disable for clarity; enabling transient suppression has no effect.
|
||||
apm_config.transient_suppression_enabled = false;
|
||||
apm_config.experiments_description = experiments_description;
|
||||
apm_config.pre_amplifier_enabled = config_.pre_amplifier.enabled;
|
||||
apm_config.pre_amplifier_fixed_gain_factor =
|
||||
|
||||
@ -40,10 +40,8 @@
|
||||
#include "modules/audio_processing/include/aec_dump.h"
|
||||
#include "modules/audio_processing/include/audio_frame_proxies.h"
|
||||
#include "modules/audio_processing/ns/noise_suppressor.h"
|
||||
#include "modules/audio_processing/optionally_built_submodule_creators.h"
|
||||
#include "modules/audio_processing/render_queue_item_verifier.h"
|
||||
#include "modules/audio_processing/rms_level.h"
|
||||
#include "modules/audio_processing/transient/transient_suppressor.h"
|
||||
#include "rtc_base/gtest_prod_util.h"
|
||||
#include "rtc_base/swap_queue.h"
|
||||
#include "rtc_base/synchronization/mutex.h"
|
||||
@ -157,21 +155,12 @@ class AudioProcessingImpl : public AudioProcessing {
|
||||
FRIEND_TEST_ALL_PREFIXES(ApmConfiguration, DefaultBehavior);
|
||||
FRIEND_TEST_ALL_PREFIXES(ApmConfiguration, ValidConfigBehavior);
|
||||
FRIEND_TEST_ALL_PREFIXES(ApmConfiguration, InValidConfigBehavior);
|
||||
FRIEND_TEST_ALL_PREFIXES(ApmWithSubmodulesExcludedTest,
|
||||
ToggleTransientSuppressor);
|
||||
FRIEND_TEST_ALL_PREFIXES(ApmWithSubmodulesExcludedTest,
|
||||
ReinitializeTransientSuppressor);
|
||||
FRIEND_TEST_ALL_PREFIXES(ApmWithSubmodulesExcludedTest,
|
||||
BitexactWithDisabledModules);
|
||||
|
||||
void set_stream_analog_level_locked(int level)
|
||||
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_);
|
||||
void UpdateRecommendedInputVolumeLocked()
|
||||
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_);
|
||||
|
||||
void OverrideSubmoduleCreationForTesting(
|
||||
const ApmSubmoduleCreationOverrides& overrides);
|
||||
|
||||
// Class providing thread-safe message pipe functionality for
|
||||
// `runtime_settings_`.
|
||||
class RuntimeSettingEnqueuer {
|
||||
@ -191,12 +180,6 @@ class AudioProcessingImpl : public AudioProcessing {
|
||||
static std::atomic<int> instance_count_;
|
||||
const bool use_setup_specific_default_aec3_config_;
|
||||
|
||||
// Deprecated.
|
||||
// TODO(bugs.webrtc.org/7494): Remove.
|
||||
static bool UseApmVadSubModule(const AudioProcessing::Config& config);
|
||||
|
||||
TransientSuppressor::VadMode transient_suppressor_vad_mode_;
|
||||
|
||||
SwapQueue<RuntimeSetting> capture_runtime_settings_;
|
||||
SwapQueue<RuntimeSetting> render_runtime_settings_;
|
||||
|
||||
@ -217,10 +200,8 @@ class AudioProcessingImpl : public AudioProcessing {
|
||||
bool noise_suppressor_enabled,
|
||||
bool adaptive_gain_controller_enabled,
|
||||
bool gain_controller2_enabled,
|
||||
bool voice_activity_detector_enabled,
|
||||
bool gain_adjustment_enabled,
|
||||
bool echo_controller_enabled,
|
||||
bool transient_suppressor_enabled);
|
||||
bool echo_controller_enabled);
|
||||
bool CaptureMultiBandSubModulesActive() const;
|
||||
bool CaptureMultiBandProcessingPresent() const;
|
||||
bool CaptureMultiBandProcessingActive(bool ec_processing_active) const;
|
||||
@ -239,11 +220,9 @@ class AudioProcessingImpl : public AudioProcessing {
|
||||
bool mobile_echo_controller_enabled_ = false;
|
||||
bool noise_suppressor_enabled_ = false;
|
||||
bool adaptive_gain_controller_enabled_ = false;
|
||||
bool voice_activity_detector_enabled_ = false;
|
||||
bool gain_controller2_enabled_ = false;
|
||||
bool gain_adjustment_enabled_ = false;
|
||||
bool echo_controller_enabled_ = false;
|
||||
bool transient_suppressor_enabled_ = false;
|
||||
bool first_update_ = true;
|
||||
};
|
||||
|
||||
@ -280,18 +259,9 @@ class AudioProcessingImpl : public AudioProcessing {
|
||||
void InitializeHighPassFilter(bool forced_reset)
|
||||
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_);
|
||||
void InitializeGainController1() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_);
|
||||
void InitializeTransientSuppressor()
|
||||
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_);
|
||||
// Initializes the `GainController2` sub-module. If the sub-module is enabled,
|
||||
// recreates it.
|
||||
void InitializeGainController2() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_);
|
||||
// Initializes the `VoiceActivityDetectorWrapper` sub-module. If the
|
||||
// sub-module is enabled, recreates it. Call `InitializeGainController2()`
|
||||
// first.
|
||||
// TODO(bugs.webrtc.org/13663): Remove if TS is removed otherwise remove call
|
||||
// order requirement - i.e., decouple from `InitializeGainController2()`.
|
||||
void InitializeVoiceActivityDetector()
|
||||
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_);
|
||||
void InitializeNoiseSuppressor() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_);
|
||||
void InitializeCaptureLevelsAdjuster()
|
||||
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_);
|
||||
@ -386,10 +356,6 @@ class AudioProcessingImpl : public AudioProcessing {
|
||||
// Struct containing the Config specifying the behavior of APM.
|
||||
AudioProcessing::Config config_;
|
||||
|
||||
// Overrides for testing the exclusion of some submodules from the build.
|
||||
ApmSubmoduleCreationOverrides submodule_creation_overrides_
|
||||
RTC_GUARDED_BY(mutex_capture_);
|
||||
|
||||
// Class containing information about what submodules are active.
|
||||
SubmoduleStates submodule_states_;
|
||||
|
||||
@ -411,12 +377,10 @@ class AudioProcessingImpl : public AudioProcessing {
|
||||
std::unique_ptr<AgcManagerDirect> agc_manager;
|
||||
std::unique_ptr<GainControlImpl> gain_control;
|
||||
std::unique_ptr<GainController2> gain_controller2;
|
||||
std::unique_ptr<VoiceActivityDetectorWrapper> voice_activity_detector;
|
||||
std::unique_ptr<HighPassFilter> high_pass_filter;
|
||||
std::unique_ptr<EchoControl> echo_controller;
|
||||
std::unique_ptr<EchoControlMobileImpl> echo_control_mobile;
|
||||
std::unique_ptr<NoiseSuppressor> noise_suppressor;
|
||||
std::unique_ptr<TransientSuppressor> transient_suppressor;
|
||||
std::unique_ptr<CaptureLevelsAdjuster> capture_levels_adjuster;
|
||||
} submodules_;
|
||||
|
||||
@ -442,19 +406,16 @@ class AudioProcessingImpl : public AudioProcessing {
|
||||
ApmConstants(bool multi_channel_render_support,
|
||||
bool multi_channel_capture_support,
|
||||
bool enforce_split_band_hpf,
|
||||
bool minimize_processing_for_unused_output,
|
||||
bool transient_suppressor_forced_off)
|
||||
bool minimize_processing_for_unused_output)
|
||||
: multi_channel_render_support(multi_channel_render_support),
|
||||
multi_channel_capture_support(multi_channel_capture_support),
|
||||
enforce_split_band_hpf(enforce_split_band_hpf),
|
||||
minimize_processing_for_unused_output(
|
||||
minimize_processing_for_unused_output),
|
||||
transient_suppressor_forced_off(transient_suppressor_forced_off) {}
|
||||
minimize_processing_for_unused_output) {}
|
||||
bool multi_channel_render_support;
|
||||
bool multi_channel_capture_support;
|
||||
bool enforce_split_band_hpf;
|
||||
bool minimize_processing_for_unused_output;
|
||||
bool transient_suppressor_forced_off;
|
||||
} constants_;
|
||||
|
||||
struct ApmCaptureState {
|
||||
|
||||
@ -19,7 +19,6 @@
|
||||
#include "api/audio/audio_processing.h"
|
||||
#include "api/make_ref_counted.h"
|
||||
#include "api/scoped_refptr.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/echo_canceller_test_tools.h"
|
||||
#include "modules/audio_processing/test/echo_control_mock.h"
|
||||
@ -633,151 +632,6 @@ TEST(AudioProcessingImplTest, RenderPreProcessorBeforeEchoDetector) {
|
||||
test_echo_detector->last_render_audio_first_sample());
|
||||
}
|
||||
|
||||
// Disabling build-optional submodules and trying to enable them via the APM
|
||||
// config should be bit-exact with running APM with said submodules disabled.
|
||||
// This mainly tests that SetCreateOptionalSubmodulesForTesting has an effect.
|
||||
TEST(ApmWithSubmodulesExcludedTest, BitexactWithDisabledModules) {
|
||||
auto apm = rtc::make_ref_counted<AudioProcessingImpl>();
|
||||
ASSERT_EQ(apm->Initialize(), AudioProcessing::kNoError);
|
||||
|
||||
ApmSubmoduleCreationOverrides overrides;
|
||||
overrides.transient_suppression = true;
|
||||
apm->OverrideSubmoduleCreationForTesting(overrides);
|
||||
|
||||
AudioProcessing::Config apm_config = apm->GetConfig();
|
||||
apm_config.transient_suppression.enabled = true;
|
||||
apm->ApplyConfig(apm_config);
|
||||
|
||||
rtc::scoped_refptr<AudioProcessing> apm_reference =
|
||||
AudioProcessingBuilder().Create();
|
||||
apm_config = apm_reference->GetConfig();
|
||||
apm_config.transient_suppression.enabled = false;
|
||||
apm_reference->ApplyConfig(apm_config);
|
||||
|
||||
constexpr int kSampleRateHz = 16000;
|
||||
constexpr int kNumChannels = 1;
|
||||
std::array<float, kSampleRateHz / 100> buffer;
|
||||
std::array<float, kSampleRateHz / 100> buffer_reference;
|
||||
float* channel_pointers[] = {buffer.data()};
|
||||
float* channel_pointers_reference[] = {buffer_reference.data()};
|
||||
StreamConfig stream_config(/*sample_rate_hz=*/kSampleRateHz,
|
||||
/*num_channels=*/kNumChannels);
|
||||
Random random_generator(2341U);
|
||||
constexpr int kFramesToProcessPerConfiguration = 10;
|
||||
|
||||
for (int i = 0; i < kFramesToProcessPerConfiguration; ++i) {
|
||||
RandomizeSampleVector(&random_generator, buffer);
|
||||
std::copy(buffer.begin(), buffer.end(), buffer_reference.begin());
|
||||
ASSERT_EQ(apm->ProcessStream(channel_pointers, stream_config, stream_config,
|
||||
channel_pointers),
|
||||
kNoErr);
|
||||
ASSERT_EQ(
|
||||
apm_reference->ProcessStream(channel_pointers_reference, stream_config,
|
||||
stream_config, channel_pointers_reference),
|
||||
kNoErr);
|
||||
for (int j = 0; j < kSampleRateHz / 100; ++j) {
|
||||
EXPECT_EQ(buffer[j], buffer_reference[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Disable transient suppressor creation and run APM in ways that should trigger
|
||||
// calls to the transient suppressor API.
|
||||
TEST(ApmWithSubmodulesExcludedTest, ReinitializeTransientSuppressor) {
|
||||
auto apm = rtc::make_ref_counted<AudioProcessingImpl>();
|
||||
ASSERT_EQ(apm->Initialize(), kNoErr);
|
||||
|
||||
ApmSubmoduleCreationOverrides overrides;
|
||||
overrides.transient_suppression = true;
|
||||
apm->OverrideSubmoduleCreationForTesting(overrides);
|
||||
|
||||
AudioProcessing::Config config = apm->GetConfig();
|
||||
config.transient_suppression.enabled = true;
|
||||
apm->ApplyConfig(config);
|
||||
// 960 samples per frame: 10 ms of <= 48 kHz audio with <= 2 channels.
|
||||
float buffer[960];
|
||||
float* channel_pointers[] = {&buffer[0], &buffer[480]};
|
||||
Random random_generator(2341U);
|
||||
constexpr int kFramesToProcessPerConfiguration = 3;
|
||||
|
||||
StreamConfig initial_stream_config(/*sample_rate_hz=*/16000,
|
||||
/*num_channels=*/1);
|
||||
for (int i = 0; i < kFramesToProcessPerConfiguration; ++i) {
|
||||
RandomizeSampleVector(&random_generator, buffer);
|
||||
EXPECT_EQ(apm->ProcessStream(channel_pointers, initial_stream_config,
|
||||
initial_stream_config, channel_pointers),
|
||||
kNoErr);
|
||||
}
|
||||
|
||||
StreamConfig stereo_stream_config(/*sample_rate_hz=*/16000,
|
||||
/*num_channels=*/2);
|
||||
for (int i = 0; i < kFramesToProcessPerConfiguration; ++i) {
|
||||
RandomizeSampleVector(&random_generator, buffer);
|
||||
EXPECT_EQ(apm->ProcessStream(channel_pointers, stereo_stream_config,
|
||||
stereo_stream_config, channel_pointers),
|
||||
kNoErr);
|
||||
}
|
||||
|
||||
StreamConfig high_sample_rate_stream_config(/*sample_rate_hz=*/48000,
|
||||
/*num_channels=*/2);
|
||||
for (int i = 0; i < kFramesToProcessPerConfiguration; ++i) {
|
||||
RandomizeSampleVector(&random_generator, buffer);
|
||||
EXPECT_EQ(
|
||||
apm->ProcessStream(channel_pointers, high_sample_rate_stream_config,
|
||||
high_sample_rate_stream_config, channel_pointers),
|
||||
kNoErr);
|
||||
}
|
||||
}
|
||||
|
||||
// Disable transient suppressor creation and run APM in ways that should trigger
|
||||
// calls to the transient suppressor API.
|
||||
TEST(ApmWithSubmodulesExcludedTest, ToggleTransientSuppressor) {
|
||||
auto apm = rtc::make_ref_counted<AudioProcessingImpl>();
|
||||
ASSERT_EQ(apm->Initialize(), AudioProcessing::kNoError);
|
||||
|
||||
ApmSubmoduleCreationOverrides overrides;
|
||||
overrides.transient_suppression = true;
|
||||
apm->OverrideSubmoduleCreationForTesting(overrides);
|
||||
|
||||
// 960 samples per frame: 10 ms of <= 48 kHz audio with <= 2 channels.
|
||||
float buffer[960];
|
||||
float* channel_pointers[] = {&buffer[0], &buffer[480]};
|
||||
Random random_generator(2341U);
|
||||
constexpr int kFramesToProcessPerConfiguration = 3;
|
||||
StreamConfig stream_config(/*sample_rate_hz=*/16000,
|
||||
/*num_channels=*/1);
|
||||
|
||||
AudioProcessing::Config config = apm->GetConfig();
|
||||
config.transient_suppression.enabled = true;
|
||||
apm->ApplyConfig(config);
|
||||
for (int i = 0; i < kFramesToProcessPerConfiguration; ++i) {
|
||||
RandomizeSampleVector(&random_generator, buffer);
|
||||
EXPECT_EQ(apm->ProcessStream(channel_pointers, stream_config, stream_config,
|
||||
channel_pointers),
|
||||
kNoErr);
|
||||
}
|
||||
|
||||
config = apm->GetConfig();
|
||||
config.transient_suppression.enabled = false;
|
||||
apm->ApplyConfig(config);
|
||||
for (int i = 0; i < kFramesToProcessPerConfiguration; ++i) {
|
||||
RandomizeSampleVector(&random_generator, buffer);
|
||||
EXPECT_EQ(apm->ProcessStream(channel_pointers, stream_config, stream_config,
|
||||
channel_pointers),
|
||||
kNoErr);
|
||||
}
|
||||
|
||||
config = apm->GetConfig();
|
||||
config.transient_suppression.enabled = true;
|
||||
apm->ApplyConfig(config);
|
||||
for (int i = 0; i < kFramesToProcessPerConfiguration; ++i) {
|
||||
RandomizeSampleVector(&random_generator, buffer);
|
||||
EXPECT_EQ(apm->ProcessStream(channel_pointers, stream_config, stream_config,
|
||||
channel_pointers),
|
||||
kNoErr);
|
||||
}
|
||||
}
|
||||
|
||||
class StartupInputVolumeParameterizedTest
|
||||
: public ::testing::TestWithParam<int> {};
|
||||
|
||||
@ -1026,6 +880,53 @@ TEST_P(Agc2ParametrizedTest, ProcessSucceedsWhenOneAgcEnabled) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(Agc2ParametrizedTest,
|
||||
BitExactWithAndWithoutTransientSuppressionEnabledInConfig) {
|
||||
// Enable transient suppression in the config (expect no effect).
|
||||
auto config = GetParam();
|
||||
config.transient_suppression.enabled = true;
|
||||
auto apm = AudioProcessingBuilder().SetConfig(config).Create();
|
||||
ASSERT_EQ(apm->Initialize(), AudioProcessing::kNoError);
|
||||
// Disable transient suppression in the config.
|
||||
auto config_reference = GetParam();
|
||||
config_reference.transient_suppression.enabled = false;
|
||||
auto apm_reference =
|
||||
AudioProcessingBuilder().SetConfig(config_reference).Create();
|
||||
ASSERT_EQ(apm_reference->Initialize(), AudioProcessing::kNoError);
|
||||
|
||||
constexpr int kSampleRateHz = 16000;
|
||||
constexpr int kNumChannels = 1;
|
||||
std::array<float, kSampleRateHz / 100> buffer;
|
||||
std::array<float, kSampleRateHz / 100> buffer_reference;
|
||||
float* channel_pointers[] = {buffer.data()};
|
||||
float* channel_pointers_reference[] = {buffer_reference.data()};
|
||||
StreamConfig stream_config(/*sample_rate_hz=*/kSampleRateHz,
|
||||
/*num_channels=*/kNumChannels);
|
||||
Random random_generator(2341U);
|
||||
constexpr int kFramesToProcessPerConfiguration = 100;
|
||||
int volume = 100;
|
||||
int volume_reference = 100;
|
||||
for (int i = 0; i < kFramesToProcessPerConfiguration; ++i) {
|
||||
RandomizeSampleVector(&random_generator, buffer);
|
||||
std::copy(buffer.begin(), buffer.end(), buffer_reference.begin());
|
||||
apm->set_stream_analog_level(volume);
|
||||
apm_reference->set_stream_analog_level(volume_reference);
|
||||
ASSERT_EQ(apm->ProcessStream(channel_pointers, stream_config, stream_config,
|
||||
channel_pointers),
|
||||
kNoErr);
|
||||
ASSERT_EQ(
|
||||
apm_reference->ProcessStream(channel_pointers_reference, stream_config,
|
||||
stream_config, channel_pointers_reference),
|
||||
kNoErr);
|
||||
volume = apm->recommended_stream_analog_level();
|
||||
volume_reference = apm_reference->recommended_stream_analog_level();
|
||||
for (int j = 0; j < kSampleRateHz / 100; ++j) {
|
||||
// Expect no effect from transient suppression.
|
||||
EXPECT_EQ(buffer[j], buffer_reference[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
AudioProcessingImplTest,
|
||||
Agc2ParametrizedTest,
|
||||
@ -1038,14 +939,6 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
.analog_gain_controller = {.enabled = true,
|
||||
.enable_digital_adaptive = true}},
|
||||
.gain_controller2 = {.enabled = false}},
|
||||
// Full AGC1, TS enabled.
|
||||
AudioProcessing::Config{
|
||||
.transient_suppression = {.enabled = true},
|
||||
.gain_controller1 =
|
||||
{.enabled = true,
|
||||
.analog_gain_controller = {.enabled = true,
|
||||
.enable_digital_adaptive = true}},
|
||||
.gain_controller2 = {.enabled = false}},
|
||||
// Hybrid AGC, TS disabled.
|
||||
AudioProcessing::Config{
|
||||
.transient_suppression = {.enabled = false},
|
||||
@ -1055,28 +948,9 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
.enable_digital_adaptive = false}},
|
||||
.gain_controller2 = {.enabled = true,
|
||||
.adaptive_digital = {.enabled = true}}},
|
||||
// Hybrid AGC, TS enabled.
|
||||
AudioProcessing::Config{
|
||||
.transient_suppression = {.enabled = true},
|
||||
.gain_controller1 =
|
||||
{.enabled = true,
|
||||
.analog_gain_controller = {.enabled = true,
|
||||
.enable_digital_adaptive = false}},
|
||||
.gain_controller2 = {.enabled = true,
|
||||
.adaptive_digital = {.enabled = true}}},
|
||||
// Full AGC2, TS disabled.
|
||||
AudioProcessing::Config{
|
||||
.transient_suppression = {.enabled = false},
|
||||
.gain_controller1 =
|
||||
{.enabled = false,
|
||||
.analog_gain_controller = {.enabled = false,
|
||||
.enable_digital_adaptive = false}},
|
||||
.gain_controller2 = {.enabled = true,
|
||||
.input_volume_controller = {.enabled = true},
|
||||
.adaptive_digital = {.enabled = true}}},
|
||||
// Full AGC2, TS enabled.
|
||||
AudioProcessing::Config{
|
||||
.transient_suppression = {.enabled = true},
|
||||
.gain_controller1 =
|
||||
{.enabled = false,
|
||||
.analog_gain_controller = {.enabled = false,
|
||||
@ -1101,20 +975,20 @@ TEST(AudioProcessingImplTest, CanDisableTransientSuppressor) {
|
||||
EXPECT_FALSE(apm->GetConfig().transient_suppression.enabled);
|
||||
}
|
||||
|
||||
TEST(AudioProcessingImplTest, CanEnableTs) {
|
||||
TEST(AudioProcessingImplTest, CannotEnableTs) {
|
||||
constexpr AudioProcessing::Config kOriginal = {
|
||||
.transient_suppression = {.enabled = true}};
|
||||
|
||||
// Test config application via `AudioProcessing` ctor.
|
||||
auto adjusted =
|
||||
AudioProcessingBuilder().SetConfig(kOriginal).Create()->GetConfig();
|
||||
EXPECT_TRUE(adjusted.transient_suppression.enabled);
|
||||
EXPECT_FALSE(adjusted.transient_suppression.enabled);
|
||||
|
||||
// Test config application via `AudioProcessing::ApplyConfig()`.
|
||||
auto apm = AudioProcessingBuilder().Create();
|
||||
apm->ApplyConfig(kOriginal);
|
||||
adjusted = apm->GetConfig();
|
||||
EXPECT_TRUE(adjusted.transient_suppression.enabled);
|
||||
EXPECT_FALSE(adjusted.transient_suppression.enabled);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
@ -68,6 +68,7 @@ class GainController2 {
|
||||
// computes the speech probability via `vad_`.
|
||||
// Handles input volume changes; if the caller cannot determine whether an
|
||||
// input volume change occurred, set `input_volume_changed` to false.
|
||||
// TODO(bugs.webrtc.org/7494): Remove `speech_probability`.
|
||||
void Process(absl::optional<float> speech_probability,
|
||||
bool input_volume_changed,
|
||||
AudioBuffer* audio);
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/audio_processing/optionally_built_submodule_creators.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "modules/audio_processing/transient/transient_suppressor_impl.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
std::unique_ptr<TransientSuppressor> CreateTransientSuppressor(
|
||||
const ApmSubmoduleCreationOverrides& overrides,
|
||||
TransientSuppressor::VadMode vad_mode,
|
||||
int sample_rate_hz,
|
||||
int detection_rate_hz,
|
||||
int num_channels) {
|
||||
#ifdef WEBRTC_EXCLUDE_TRANSIENT_SUPPRESSOR
|
||||
return nullptr;
|
||||
#else
|
||||
if (overrides.transient_suppression) {
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_unique<TransientSuppressorImpl>(
|
||||
vad_mode, sample_rate_hz, detection_rate_hz, num_channels);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_AUDIO_PROCESSING_OPTIONALLY_BUILT_SUBMODULE_CREATORS_H_
|
||||
#define MODULES_AUDIO_PROCESSING_OPTIONALLY_BUILT_SUBMODULE_CREATORS_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "modules/audio_processing/transient/transient_suppressor.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// These overrides are only to be used for testing purposes.
|
||||
// Each flag emulates a preprocessor macro to exclude a submodule of APM from
|
||||
// the build, e.g. WEBRTC_EXCLUDE_TRANSIENT_SUPPRESSOR. If the corresponding
|
||||
// flag `transient_suppression` is enabled, then the creators will return
|
||||
// nullptr instead of a submodule instance, as if the macro had been defined.
|
||||
struct ApmSubmoduleCreationOverrides {
|
||||
bool transient_suppression = false;
|
||||
};
|
||||
|
||||
// Creates a transient suppressor.
|
||||
// Will instead return nullptr if one of the following is true:
|
||||
// * WEBRTC_EXCLUDE_TRANSIENT_SUPPRESSOR is defined
|
||||
// * The corresponding override in `overrides` is enabled.
|
||||
std::unique_ptr<TransientSuppressor> CreateTransientSuppressor(
|
||||
const ApmSubmoduleCreationOverrides& overrides,
|
||||
TransientSuppressor::VadMode vad_mode,
|
||||
int sample_rate_hz,
|
||||
int detection_rate_hz,
|
||||
int num_channels);
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_AUDIO_PROCESSING_OPTIONALLY_BUILT_SUBMODULE_CREATORS_H_
|
||||
@ -108,10 +108,3 @@ macro for you.
|
||||
|
||||
[metrics_h]: https://webrtc.googlesource.com/src/+/main/system_wrappers/include/metrics.h
|
||||
|
||||
## `WEBRTC_EXCLUDE_TRANSIENT_SUPPRESSOR`
|
||||
The transient suppressor functionality in the audio processing module is not
|
||||
always used. If you wish to exclude it from the build in order to preserve
|
||||
binary size, then define the preprocessor macro
|
||||
`WEBRTC_EXCLUDE_TRANSIENT_SUPPRESSOR`. If you use GN, you can just set the GN
|
||||
argument `rtc_exclude_transient_suppressor` to true and GN will define the macro
|
||||
for you.
|
||||
|
||||
@ -336,10 +336,6 @@ declare_args() {
|
||||
|
||||
# Set this to true to disable webrtc metrics.
|
||||
rtc_disable_metrics = false
|
||||
|
||||
# Set this to true to exclude the transient suppressor in the audio processing
|
||||
# module from the build.
|
||||
rtc_exclude_transient_suppressor = false
|
||||
}
|
||||
|
||||
declare_args() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user