AGC2 lightweight noise floor estimator
The current noise level estimator has a bug due to which the estimated
level decays to the lower bound in a few seconds when speech is observed.
Instead of fixing the current implementation, which is based on a
stationarity classifier, an alternative, lightweight, noise floor
estimator has been added and tuned for AGC2.
Tested on several AEC dumps including HW mute, music and fast talking.
Bug: webrtc:7494
Change-Id: Iae4cff9fc955a716878f830957e893cd5bc59446
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/214133
Commit-Queue: Alessio Bazzica <alessiob@webrtc.org>
Reviewed-by: Per Ã…hgren <peah@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#33733}
diff --git a/modules/audio_processing/agc2/adaptive_agc.cc b/modules/audio_processing/agc2/adaptive_agc.cc
index ca9959a..37f11d2 100644
--- a/modules/audio_processing/agc2/adaptive_agc.cc
+++ b/modules/audio_processing/agc2/adaptive_agc.cc
@@ -20,6 +20,11 @@
namespace webrtc {
namespace {
+using AdaptiveDigitalConfig =
+ AudioProcessing::Config::GainController2::AdaptiveDigital;
+using NoiseEstimatorType =
+ AudioProcessing::Config::GainController2::NoiseEstimator;
+
void DumpDebugData(const AdaptiveDigitalGainApplier::FrameInfo& info,
ApmDataDumper& dumper) {
dumper.DumpRaw("agc2_vad_probability", info.vad_result.speech_probability);
@@ -35,7 +40,7 @@
// Detects the available CPU features and applies any kill-switches.
AvailableCpuFeatures GetAllowedCpuFeatures(
- const AudioProcessing::Config::GainController2::AdaptiveDigital& config) {
+ const AdaptiveDigitalConfig& config) {
AvailableCpuFeatures features = GetAvailableCpuFeatures();
if (!config.sse2_allowed) {
features.sse2 = false;
@@ -49,6 +54,20 @@
return features;
}
+std::unique_ptr<NoiseLevelEstimator> CreateNoiseLevelEstimator(
+ NoiseEstimatorType estimator_type,
+ ApmDataDumper* apm_data_dumper) {
+ switch (estimator_type) {
+ case NoiseEstimatorType::kStationaryNoise:
+ return CreateStationaryNoiseEstimator(apm_data_dumper);
+ case NoiseEstimatorType::kNoiseFloor:
+ return CreateNoiseFloorEstimator(apm_data_dumper);
+ }
+}
+
+constexpr NoiseEstimatorType kDefaultNoiseLevelEstimatorType =
+ NoiseEstimatorType::kNoiseFloor;
+
} // namespace
AdaptiveAgc::AdaptiveAgc(ApmDataDumper* apm_data_dumper)
@@ -58,31 +77,32 @@
kMaxGainChangePerSecondDb,
kMaxOutputNoiseLevelDbfs),
apm_data_dumper_(apm_data_dumper),
- noise_level_estimator_(CreateNoiseLevelEstimator(apm_data_dumper)) {
+ noise_level_estimator_(
+ CreateNoiseLevelEstimator(kDefaultNoiseLevelEstimatorType,
+ apm_data_dumper)) {
RTC_DCHECK(apm_data_dumper);
}
AdaptiveAgc::AdaptiveAgc(ApmDataDumper* apm_data_dumper,
- const AudioProcessing::Config::GainController2& config)
+ const AdaptiveDigitalConfig& config)
: speech_level_estimator_(
apm_data_dumper,
- config.adaptive_digital.level_estimator,
- config.adaptive_digital
- .level_estimator_adjacent_speech_frames_threshold,
- config.adaptive_digital.initial_saturation_margin_db,
- config.adaptive_digital.extra_saturation_margin_db),
- vad_(config.adaptive_digital.vad_reset_period_ms,
- config.adaptive_digital.vad_probability_attack,
- GetAllowedCpuFeatures(config.adaptive_digital)),
- gain_applier_(
- apm_data_dumper,
- config.adaptive_digital.gain_applier_adjacent_speech_frames_threshold,
- config.adaptive_digital.max_gain_change_db_per_second,
- config.adaptive_digital.max_output_noise_level_dbfs),
+ config.level_estimator,
+ config.level_estimator_adjacent_speech_frames_threshold,
+ config.initial_saturation_margin_db,
+ config.extra_saturation_margin_db),
+ vad_(config.vad_reset_period_ms,
+ config.vad_probability_attack,
+ GetAllowedCpuFeatures(config)),
+ gain_applier_(apm_data_dumper,
+ config.gain_applier_adjacent_speech_frames_threshold,
+ config.max_gain_change_db_per_second,
+ config.max_output_noise_level_dbfs),
apm_data_dumper_(apm_data_dumper),
- noise_level_estimator_(CreateNoiseLevelEstimator(apm_data_dumper)) {
+ noise_level_estimator_(
+ CreateNoiseLevelEstimator(config.noise_estimator, apm_data_dumper)) {
RTC_DCHECK(apm_data_dumper);
- if (!config.adaptive_digital.use_saturation_protector) {
+ if (!config.use_saturation_protector) {
RTC_LOG(LS_WARNING) << "The saturation protector cannot be disabled.";
}
}
diff --git a/modules/audio_processing/agc2/adaptive_agc.h b/modules/audio_processing/agc2/adaptive_agc.h
index b861c48..525cab7 100644
--- a/modules/audio_processing/agc2/adaptive_agc.h
+++ b/modules/audio_processing/agc2/adaptive_agc.h
@@ -29,8 +29,9 @@
public:
explicit AdaptiveAgc(ApmDataDumper* apm_data_dumper);
// TODO(crbug.com/webrtc/7494): Remove ctor above.
- AdaptiveAgc(ApmDataDumper* apm_data_dumper,
- const AudioProcessing::Config::GainController2& config);
+ AdaptiveAgc(
+ ApmDataDumper* apm_data_dumper,
+ const AudioProcessing::Config::GainController2::AdaptiveDigital& config);
~AdaptiveAgc();
// Analyzes `frame` and applies a digital adaptive gain to it. Takes into
diff --git a/modules/audio_processing/agc2/agc2_common.h b/modules/audio_processing/agc2/agc2_common.h
index db67113..ccd04bc 100644
--- a/modules/audio_processing/agc2/agc2_common.h
+++ b/modules/audio_processing/agc2/agc2_common.h
@@ -35,7 +35,7 @@
// This is the threshold for speech. Speech frames are used for updating the
// speech level, measuring the amount of speech, and decide when to allow target
// gain reduction.
-constexpr float kVadConfidenceThreshold = 0.9f;
+constexpr float kVadConfidenceThreshold = 0.95f;
// The amount of 'memory' of the Level Estimator. Decides leak factors.
constexpr int kFullBufferSizeMs = 1200;
diff --git a/modules/audio_processing/agc2/noise_level_estimator.cc b/modules/audio_processing/agc2/noise_level_estimator.cc
index 6aa942c..ae8a501 100644
--- a/modules/audio_processing/agc2/noise_level_estimator.cc
+++ b/modules/audio_processing/agc2/noise_level_estimator.cc
@@ -46,13 +46,15 @@
public:
NoiseLevelEstimatorImpl(ApmDataDumper* data_dumper)
: data_dumper_(data_dumper), signal_classifier_(data_dumper) {
- Initialize(48000);
+ // Initially assume that 48 kHz will be used. `Analyze()` will detect the
+ // used sample rate and call `Initialize()` again if needed.
+ Initialize(/*sample_rate_hz=*/48000);
}
NoiseLevelEstimatorImpl(const NoiseLevelEstimatorImpl&) = delete;
NoiseLevelEstimatorImpl& operator=(const NoiseLevelEstimatorImpl&) = delete;
~NoiseLevelEstimatorImpl() = default;
- float Analyze(const AudioFrameView<const float>& frame) {
+ float Analyze(const AudioFrameView<const float>& frame) override {
data_dumper_->DumpRaw("agc2_noise_level_estimator_hold_counter",
noise_energy_hold_counter_);
const int sample_rate_hz =
@@ -122,6 +124,7 @@
sample_rate_hz_ = sample_rate_hz;
noise_energy_ = 1.0f;
first_update_ = true;
+ // Initialize the minimum noise energy to -84 dBFS.
min_noise_energy_ = sample_rate_hz * 2.0f * 2.0f / kFramesPerSecond;
noise_energy_hold_counter_ = 0;
signal_classifier_.Initialize(sample_rate_hz);
@@ -136,11 +139,122 @@
SignalClassifier signal_classifier_;
};
+// Updates the noise floor with instant decay and slow attack. This tuning is
+// specific for AGC2, so that (i) it can promptly increase the gain if the noise
+// floor drops (instant decay) and (ii) in case of music or fast speech, due to
+// which the noise floor can be overestimated, the gain reduction is slowed
+// down.
+float SmoothNoiseFloorEstimate(float current_estimate, float new_estimate) {
+ constexpr float kAttack = 0.5f;
+ if (current_estimate < new_estimate) {
+ // Attack phase.
+ return kAttack * new_estimate + (1.0f - kAttack) * current_estimate;
+ }
+ // Instant attack.
+ return new_estimate;
+}
+
+class NoiseFloorEstimator : public NoiseLevelEstimator {
+ public:
+ // Update the noise floor every 5 seconds.
+ static constexpr int kUpdatePeriodNumFrames = 500;
+ static_assert(kUpdatePeriodNumFrames >= 200,
+ "A too small value may cause noise level overestimation.");
+ static_assert(kUpdatePeriodNumFrames <= 1500,
+ "A too large value may make AGC2 slow at reacting to increased "
+ "noise levels.");
+
+ NoiseFloorEstimator(ApmDataDumper* data_dumper) : data_dumper_(data_dumper) {
+ // Initially assume that 48 kHz will be used. `Analyze()` will detect the
+ // used sample rate and call `Initialize()` again if needed.
+ Initialize(/*sample_rate_hz=*/48000);
+ }
+ NoiseFloorEstimator(const NoiseFloorEstimator&) = delete;
+ NoiseFloorEstimator& operator=(const NoiseFloorEstimator&) = delete;
+ ~NoiseFloorEstimator() = default;
+
+ float Analyze(const AudioFrameView<const float>& frame) override {
+ // Detect sample rate changes.
+ const int sample_rate_hz =
+ static_cast<int>(frame.samples_per_channel() * kFramesPerSecond);
+ if (sample_rate_hz != sample_rate_hz_) {
+ Initialize(sample_rate_hz);
+ }
+
+ const float frame_energy = FrameEnergy(frame);
+ if (frame_energy <= min_noise_energy_) {
+ // Ignore frames when muted or below the minimum measurable energy.
+ data_dumper_->DumpRaw("agc2_noise_floor_preliminary_level",
+ noise_energy_);
+ return EnergyToDbfs(noise_energy_, frame.samples_per_channel());
+ }
+
+ if (preliminary_noise_energy_set_) {
+ preliminary_noise_energy_ =
+ std::min(preliminary_noise_energy_, frame_energy);
+ } else {
+ preliminary_noise_energy_ = frame_energy;
+ preliminary_noise_energy_set_ = true;
+ }
+ data_dumper_->DumpRaw("agc2_noise_floor_preliminary_level",
+ preliminary_noise_energy_);
+
+ if (counter_ == 0) {
+ // Full period observed.
+ first_period_ = false;
+ // Update the estimated noise floor energy with the preliminary
+ // estimation.
+ noise_energy_ = SmoothNoiseFloorEstimate(
+ /*current_estimate=*/noise_energy_,
+ /*new_estimate=*/preliminary_noise_energy_);
+ // Reset for a new observation period.
+ counter_ = kUpdatePeriodNumFrames;
+ preliminary_noise_energy_set_ = false;
+ } else if (first_period_) {
+ // While analyzing the signal during the initial period, continuously
+ // update the estimated noise energy, which is monotonic.
+ noise_energy_ = preliminary_noise_energy_;
+ counter_--;
+ } else {
+ // During the observation period it's only allowed to lower the energy.
+ noise_energy_ = std::min(noise_energy_, preliminary_noise_energy_);
+ counter_--;
+ }
+ return EnergyToDbfs(noise_energy_, frame.samples_per_channel());
+ }
+
+ private:
+ void Initialize(int sample_rate_hz) {
+ sample_rate_hz_ = sample_rate_hz;
+ first_period_ = true;
+ preliminary_noise_energy_set_ = false;
+ // Initialize the minimum noise energy to -84 dBFS.
+ min_noise_energy_ = sample_rate_hz * 2.0f * 2.0f / kFramesPerSecond;
+ preliminary_noise_energy_ = min_noise_energy_;
+ noise_energy_ = min_noise_energy_;
+ counter_ = kUpdatePeriodNumFrames;
+ }
+
+ ApmDataDumper* const data_dumper_;
+ int sample_rate_hz_;
+ float min_noise_energy_;
+ bool first_period_;
+ bool preliminary_noise_energy_set_;
+ float preliminary_noise_energy_;
+ float noise_energy_;
+ int counter_;
+};
+
} // namespace
-std::unique_ptr<NoiseLevelEstimator> CreateNoiseLevelEstimator(
+std::unique_ptr<NoiseLevelEstimator> CreateStationaryNoiseEstimator(
ApmDataDumper* data_dumper) {
return std::make_unique<NoiseLevelEstimatorImpl>(data_dumper);
}
+std::unique_ptr<NoiseLevelEstimator> CreateNoiseFloorEstimator(
+ ApmDataDumper* data_dumper) {
+ return std::make_unique<NoiseFloorEstimator>(data_dumper);
+}
+
} // namespace webrtc
diff --git a/modules/audio_processing/agc2/noise_level_estimator.h b/modules/audio_processing/agc2/noise_level_estimator.h
index 7e57b4c..94aecda 100644
--- a/modules/audio_processing/agc2/noise_level_estimator.h
+++ b/modules/audio_processing/agc2/noise_level_estimator.h
@@ -28,7 +28,11 @@
};
// Creates a noise level estimator based on stationarity detection.
-std::unique_ptr<NoiseLevelEstimator> CreateNoiseLevelEstimator(
+std::unique_ptr<NoiseLevelEstimator> CreateStationaryNoiseEstimator(
+ ApmDataDumper* data_dumper);
+
+// Creates a noise level estimator based on noise floor detection.
+std::unique_ptr<NoiseLevelEstimator> CreateNoiseFloorEstimator(
ApmDataDumper* data_dumper);
} // namespace webrtc
diff --git a/modules/audio_processing/agc2/noise_level_estimator_unittest.cc b/modules/audio_processing/agc2/noise_level_estimator_unittest.cc
index ccee34a..51ad1ba 100644
--- a/modules/audio_processing/agc2/noise_level_estimator_unittest.cc
+++ b/modules/audio_processing/agc2/noise_level_estimator_unittest.cc
@@ -11,6 +11,7 @@
#include "modules/audio_processing/agc2/noise_level_estimator.h"
#include <array>
+#include <cmath>
#include <functional>
#include <limits>
@@ -29,21 +30,19 @@
// Runs the noise estimator on audio generated by 'sample_generator'
// for kNumIterations. Returns the last noise level estimate.
float RunEstimator(rtc::FunctionView<float()> sample_generator,
+ NoiseLevelEstimator& estimator,
int sample_rate_hz) {
- ApmDataDumper data_dumper(0);
- auto estimator = CreateNoiseLevelEstimator(&data_dumper);
const int samples_per_channel =
rtc::CheckedDivExact(sample_rate_hz, kFramesPerSecond);
VectorFloatFrame signal(1, samples_per_channel, 0.0f);
-
for (int i = 0; i < kNumIterations; ++i) {
AudioFrameView<float> frame_view = signal.float_frame_view();
for (int j = 0; j < samples_per_channel; ++j) {
frame_view.channel(0)[j] = sample_generator();
}
- estimator->Analyze(frame_view);
+ estimator.Analyze(frame_view);
}
- return estimator->Analyze(signal.float_frame_view());
+ return estimator.Analyze(signal.float_frame_view());
}
class NoiseEstimatorParametrization : public ::testing::TestWithParam<int> {
@@ -53,32 +52,82 @@
// White random noise is stationary, but does not trigger the detector
// every frame due to the randomness.
-TEST_P(NoiseEstimatorParametrization, RandomNoise) {
+TEST_P(NoiseEstimatorParametrization, StationaryNoiseEstimatorWithRandomNoise) {
+ ApmDataDumper data_dumper(0);
+ auto estimator = CreateStationaryNoiseEstimator(&data_dumper);
+
test::WhiteNoiseGenerator gen(/*min_amplitude=*/test::kMinS16,
/*max_amplitude=*/test::kMaxS16);
- const float noise_level_dbfs = RunEstimator(gen, sample_rate_hz());
+ const float noise_level_dbfs =
+ RunEstimator(gen, *estimator, sample_rate_hz());
EXPECT_NEAR(noise_level_dbfs, -5.5f, 1.0f);
}
// Sine curves are (very) stationary. They trigger the detector all
// the time. Except for a few initial frames.
-TEST_P(NoiseEstimatorParametrization, SineTone) {
+TEST_P(NoiseEstimatorParametrization, StationaryNoiseEstimatorWithSineTone) {
+ ApmDataDumper data_dumper(0);
+ auto estimator = CreateStationaryNoiseEstimator(&data_dumper);
+
test::SineGenerator gen(/*amplitude=*/test::kMaxS16, /*frequency_hz=*/600.0f,
sample_rate_hz());
- const float noise_level_dbfs = RunEstimator(gen, sample_rate_hz());
+ const float noise_level_dbfs =
+ RunEstimator(gen, *estimator, sample_rate_hz());
EXPECT_NEAR(noise_level_dbfs, -3.0f, 1.0f);
}
// Pulses are transient if they are far enough apart. They shouldn't
// trigger the noise detector.
-TEST_P(NoiseEstimatorParametrization, PulseTone) {
+TEST_P(NoiseEstimatorParametrization, StationaryNoiseEstimatorWithPulseTone) {
+ ApmDataDumper data_dumper(0);
+ auto estimator = CreateStationaryNoiseEstimator(&data_dumper);
+
test::PulseGenerator gen(/*pulse_amplitude=*/test::kMaxS16,
/*no_pulse_amplitude=*/10.0f, /*frequency_hz=*/20.0f,
sample_rate_hz());
- const int noise_level_dbfs = RunEstimator(gen, sample_rate_hz());
+ const int noise_level_dbfs = RunEstimator(gen, *estimator, sample_rate_hz());
EXPECT_NEAR(noise_level_dbfs, -79.0f, 1.0f);
}
+// Checks that full scale white noise maps to about -5.5 dBFS.
+TEST_P(NoiseEstimatorParametrization, NoiseFloorEstimatorWithRandomNoise) {
+ ApmDataDumper data_dumper(0);
+ auto estimator = CreateNoiseFloorEstimator(&data_dumper);
+
+ test::WhiteNoiseGenerator gen(/*min_amplitude=*/test::kMinS16,
+ /*max_amplitude=*/test::kMaxS16);
+ const float noise_level_dbfs =
+ RunEstimator(gen, *estimator, sample_rate_hz());
+ EXPECT_NEAR(noise_level_dbfs, -5.5f, 0.5f);
+}
+
+// Checks that a full scale sine wave maps to about -3 dBFS.
+TEST_P(NoiseEstimatorParametrization, NoiseFloorEstimatorWithSineTone) {
+ ApmDataDumper data_dumper(0);
+ auto estimator = CreateNoiseFloorEstimator(&data_dumper);
+
+ test::SineGenerator gen(/*amplitude=*/test::kMaxS16, /*frequency_hz=*/600.0f,
+ sample_rate_hz());
+ const float noise_level_dbfs =
+ RunEstimator(gen, *estimator, sample_rate_hz());
+ EXPECT_NEAR(noise_level_dbfs, -3.0f, 0.1f);
+}
+
+// Check that sufficiently spaced periodic pulses do not raise the estimated
+// noise floor, which is determined by the amplitude of the non-pulse samples.
+TEST_P(NoiseEstimatorParametrization, NoiseFloorEstimatorWithPulseTone) {
+ ApmDataDumper data_dumper(0);
+ auto estimator = CreateNoiseFloorEstimator(&data_dumper);
+
+ constexpr float kNoPulseAmplitude = 10.0f;
+ test::PulseGenerator gen(/*pulse_amplitude=*/test::kMaxS16, kNoPulseAmplitude,
+ /*frequency_hz=*/20.0f, sample_rate_hz());
+ const int noise_level_dbfs = RunEstimator(gen, *estimator, sample_rate_hz());
+ const float expected_noise_floor_dbfs =
+ 20.0f * std::log10f(kNoPulseAmplitude / test::kMaxS16);
+ EXPECT_NEAR(noise_level_dbfs, expected_noise_floor_dbfs, 0.5f);
+}
+
INSTANTIATE_TEST_SUITE_P(GainController2NoiseEstimator,
NoiseEstimatorParametrization,
::testing::Values(8000, 16000, 32000, 48000));
diff --git a/modules/audio_processing/gain_controller2.cc b/modules/audio_processing/gain_controller2.cc
index bdb223b..6c5e24e 100644
--- a/modules/audio_processing/gain_controller2.cc
+++ b/modules/audio_processing/gain_controller2.cc
@@ -90,7 +90,8 @@
}
gain_applier_.SetGainFactor(DbToRatio(config_.fixed_digital.gain_db));
if (config_.adaptive_digital.enabled) {
- adaptive_agc_ = std::make_unique<AdaptiveAgc>(&data_dumper_, config_);
+ adaptive_agc_ =
+ std::make_unique<AdaptiveAgc>(&data_dumper_, config_.adaptive_digital);
} else {
adaptive_agc_.reset();
}
diff --git a/modules/audio_processing/gain_controller2_unittest.cc b/modules/audio_processing/gain_controller2_unittest.cc
index 09bad50..274c821 100644
--- a/modules/audio_processing/gain_controller2_unittest.cc
+++ b/modules/audio_processing/gain_controller2_unittest.cc
@@ -351,7 +351,7 @@
config.adaptive_digital.extra_saturation_margin_db = 0.f;
gain_controller2.ApplyConfig(config);
- EXPECT_GT(GainAfterProcessingFile(&gain_controller2), 2.f);
+ EXPECT_GT(GainAfterProcessingFile(&gain_controller2), 1.9f);
}
} // namespace test
diff --git a/modules/audio_processing/include/audio_processing.cc b/modules/audio_processing/include/audio_processing.cc
index 6e726d9..790b1a7 100644
--- a/modules/audio_processing/include/audio_processing.cc
+++ b/modules/audio_processing/include/audio_processing.cc
@@ -57,6 +57,17 @@
RTC_CHECK_NOTREACHED();
}
+std::string GainController2NoiseEstimatorToString(
+ const Agc2Config::NoiseEstimator& type) {
+ switch (type) {
+ case Agc2Config::NoiseEstimator::kStationaryNoise:
+ return "StationaryNoise";
+ case Agc2Config::NoiseEstimator::kNoiseFloor:
+ return "NoiseFloor";
+ }
+ RTC_CHECK_NOTREACHED();
+}
+
} // namespace
constexpr int AudioProcessing::kNativeSampleRatesHz[];
@@ -160,7 +171,9 @@
<< ", fixed_digital: { gain_db: "
<< gain_controller2.fixed_digital.gain_db
<< " }, adaptive_digital: { enabled: "
- << gain_controller2.adaptive_digital.enabled
+ << gain_controller2.adaptive_digital.enabled << ", noise_estimator: "
+ << GainController2NoiseEstimatorToString(
+ gain_controller2.adaptive_digital.noise_estimator)
<< ", level_estimator: { vad_probability_attack: "
<< gain_controller2.adaptive_digital.vad_probability_attack << ", type: "
<< GainController2LevelEstimatorToString(
diff --git a/modules/audio_processing/include/audio_processing.h b/modules/audio_processing/include/audio_processing.h
index a5c266a..781b17e 100644
--- a/modules/audio_processing/include/audio_processing.h
+++ b/modules/audio_processing/include/audio_processing.h
@@ -350,21 +350,23 @@
}
enum LevelEstimator { kRms, kPeak };
+ enum NoiseEstimator { kStationaryNoise, kNoiseFloor };
bool enabled = false;
struct FixedDigital {
float gain_db = 0.0f;
} fixed_digital;
struct AdaptiveDigital {
bool enabled = false;
+ NoiseEstimator noise_estimator = kNoiseFloor;
int vad_reset_period_ms = 1500;
- float vad_probability_attack = 0.3f;
+ float vad_probability_attack = 0.9f;
LevelEstimator level_estimator = kRms;
- int level_estimator_adjacent_speech_frames_threshold = 6;
+ int level_estimator_adjacent_speech_frames_threshold = 11;
// TODO(crbug.com/webrtc/7494): Remove `use_saturation_protector`.
bool use_saturation_protector = true;
float initial_saturation_margin_db = 20.0f;
float extra_saturation_margin_db = 5.0f;
- int gain_applier_adjacent_speech_frames_threshold = 6;
+ int gain_applier_adjacent_speech_frames_threshold = 11;
float max_gain_change_db_per_second = 3.0f;
float max_output_noise_level_dbfs = -55.0f;
bool sse2_allowed = true;