AEC3: Unbounded echo spectrum for dominant nearend detection.
The dominant nearend detector uses the residual echo spectrum for determining whether in nearend state. The residual echo spectrum in computed using the ERLE. To reduce the risk of echo leaks in the suppressor, the ERLE is capped. While minimizing echo leaks, the capping of the ERLE can affect the dominant nearend classification negatively as the residual echo spectrum is often over estimated. This change enables the dominant nearend detector to use a residual echo spectrum computed with a virtually non-capped ERLE. This ERLE is only used for dominant nearend detection and leads to increased transparency. The feature is currently disabled by default and can be enabled with the field trial "WebRTC-Aec3UseUnboundedEchoSpectrum". Bug: webrtc:12870 Change-Id: Icb675c6f5d42ab9286e623b5fb38424d5c9cbee4 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/221920 Reviewed-by: Jesus de Vicente Pena <devicentepena@webrtc.org> Commit-Queue: Gustaf Ullberg <gustaf@webrtc.org> Cr-Commit-Position: refs/heads/master@{#34270}
This commit is contained in:
committed by
WebRTC LUCI CQ
parent
1b4807ff65
commit
a63d152423
@ -75,6 +75,12 @@ class AecState {
|
||||
return erle_estimator_.Erle(onset_compensated);
|
||||
}
|
||||
|
||||
// Returns the non-capped ERLE.
|
||||
rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>> ErleUnbounded()
|
||||
const {
|
||||
return erle_estimator_.ErleUnbounded();
|
||||
}
|
||||
|
||||
// Returns the fullband ERLE estimate in log2 units.
|
||||
float FullBandErleLog2() const { return erle_estimator_.FullbandErleLog2(); }
|
||||
|
||||
|
||||
@ -172,6 +172,7 @@ class EchoRemoverImpl final : public EchoRemover {
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> Y2_heap_;
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> E2_heap_;
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> R2_heap_;
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> R2_unbounded_heap_;
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> S2_linear_heap_;
|
||||
std::vector<FftData> Y_heap_;
|
||||
std::vector<FftData> E_heap_;
|
||||
@ -218,6 +219,7 @@ EchoRemoverImpl::EchoRemoverImpl(const EchoCanceller3Config& config,
|
||||
Y2_heap_(NumChannelsOnHeap(num_capture_channels_)),
|
||||
E2_heap_(NumChannelsOnHeap(num_capture_channels_)),
|
||||
R2_heap_(NumChannelsOnHeap(num_capture_channels_)),
|
||||
R2_unbounded_heap_(NumChannelsOnHeap(num_capture_channels_)),
|
||||
S2_linear_heap_(NumChannelsOnHeap(num_capture_channels_)),
|
||||
Y_heap_(NumChannelsOnHeap(num_capture_channels_)),
|
||||
E_heap_(NumChannelsOnHeap(num_capture_channels_)),
|
||||
@ -264,6 +266,8 @@ void EchoRemoverImpl::ProcessCapture(
|
||||
E2_stack;
|
||||
std::array<std::array<float, kFftLengthBy2Plus1>, kMaxNumChannelsOnStack>
|
||||
R2_stack;
|
||||
std::array<std::array<float, kFftLengthBy2Plus1>, kMaxNumChannelsOnStack>
|
||||
R2_unbounded_stack;
|
||||
std::array<std::array<float, kFftLengthBy2Plus1>, kMaxNumChannelsOnStack>
|
||||
S2_linear_stack;
|
||||
std::array<FftData, kMaxNumChannelsOnStack> Y_stack;
|
||||
@ -280,6 +284,8 @@ void EchoRemoverImpl::ProcessCapture(
|
||||
E2_stack.data(), num_capture_channels_);
|
||||
rtc::ArrayView<std::array<float, kFftLengthBy2Plus1>> R2(
|
||||
R2_stack.data(), num_capture_channels_);
|
||||
rtc::ArrayView<std::array<float, kFftLengthBy2Plus1>> R2_unbounded(
|
||||
R2_unbounded_stack.data(), num_capture_channels_);
|
||||
rtc::ArrayView<std::array<float, kFftLengthBy2Plus1>> S2_linear(
|
||||
S2_linear_stack.data(), num_capture_channels_);
|
||||
rtc::ArrayView<FftData> Y(Y_stack.data(), num_capture_channels_);
|
||||
@ -301,6 +307,8 @@ void EchoRemoverImpl::ProcessCapture(
|
||||
E2_heap_.data(), num_capture_channels_);
|
||||
R2 = rtc::ArrayView<std::array<float, kFftLengthBy2Plus1>>(
|
||||
R2_heap_.data(), num_capture_channels_);
|
||||
R2_unbounded = rtc::ArrayView<std::array<float, kFftLengthBy2Plus1>>(
|
||||
R2_unbounded_heap_.data(), num_capture_channels_);
|
||||
S2_linear = rtc::ArrayView<std::array<float, kFftLengthBy2Plus1>>(
|
||||
S2_linear_heap_.data(), num_capture_channels_);
|
||||
Y = rtc::ArrayView<FftData>(Y_heap_.data(), num_capture_channels_);
|
||||
@ -406,8 +414,8 @@ void EchoRemoverImpl::ProcessCapture(
|
||||
if (capture_output_used_) {
|
||||
// Estimate the residual echo power.
|
||||
residual_echo_estimator_.Estimate(aec_state_, *render_buffer, S2_linear, Y2,
|
||||
suppression_gain_.IsDominantNearend(),
|
||||
R2);
|
||||
suppression_gain_.IsDominantNearend(), R2,
|
||||
R2_unbounded);
|
||||
|
||||
// Suppressor nearend estimate.
|
||||
if (aec_state_.UsableLinearEstimate()) {
|
||||
@ -430,7 +438,7 @@ void EchoRemoverImpl::ProcessCapture(
|
||||
|
||||
// Compute preferred gains.
|
||||
float high_bands_gain;
|
||||
suppression_gain_.GetGain(nearend_spectrum, echo_spectrum, R2,
|
||||
suppression_gain_.GetGain(nearend_spectrum, echo_spectrum, R2, R2_unbounded,
|
||||
cng_.NoiseSpectrum(), render_signal_analyzer_,
|
||||
aec_state_, x, clock_drift, &high_bands_gain, &G);
|
||||
|
||||
|
||||
@ -62,6 +62,18 @@ class ErleEstimator {
|
||||
: subband_erle_estimator_.Erle(onset_compensated);
|
||||
}
|
||||
|
||||
// Returns the non-capped subband ERLE.
|
||||
rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>> ErleUnbounded()
|
||||
const {
|
||||
// Unbounded ERLE is only used with the subband erle estimator where the
|
||||
// ERLE is often capped at low values. When the signal dependent ERLE
|
||||
// estimator is used the capped ERLE is returned.
|
||||
return !signal_dependent_erle_estimator_
|
||||
? subband_erle_estimator_.ErleUnbounded()
|
||||
: signal_dependent_erle_estimator_->Erle(
|
||||
/*onset_compensated=*/false);
|
||||
}
|
||||
|
||||
// Returns the subband ERLE that are estimated during onsets (only used for
|
||||
// testing).
|
||||
rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>> ErleDuringOnsets()
|
||||
|
||||
@ -50,6 +50,16 @@ void VerifyErle(
|
||||
EXPECT_NEAR(kTrueErle, erle_time_domain, 0.5);
|
||||
}
|
||||
|
||||
void VerifyErleGreaterOrEqual(
|
||||
rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>> erle1,
|
||||
rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>> erle2) {
|
||||
for (size_t ch = 0; ch < erle1.size(); ++ch) {
|
||||
for (size_t i = 0; i < kFftLengthBy2Plus1; ++i) {
|
||||
EXPECT_GE(erle1[ch][i], erle2[ch][i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FormFarendTimeFrame(std::vector<std::vector<std::vector<float>>>* x) {
|
||||
const std::array<float, kBlockSize> frame = {
|
||||
7459.88, 17209.6, 17383, 20768.9, 16816.7, 18386.3, 4492.83, 9675.85,
|
||||
@ -156,9 +166,10 @@ TEST_P(ErleEstimatorMultiChannel, VerifyErleIncreaseAndHold) {
|
||||
kNumBands, std::vector<std::vector<float>>(
|
||||
num_render_channels, std::vector<float>(kBlockSize, 0.f)));
|
||||
std::vector<std::vector<std::array<float, kFftLengthBy2Plus1>>>
|
||||
filter_frequency_response(
|
||||
config.filter.refined.length_blocks,
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>>(num_capture_channels));
|
||||
filter_frequency_response(
|
||||
config.filter.refined.length_blocks,
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>>(
|
||||
num_capture_channels));
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(config, kSampleRateHz, num_render_channels));
|
||||
|
||||
@ -181,6 +192,10 @@ TEST_P(ErleEstimatorMultiChannel, VerifyErleIncreaseAndHold) {
|
||||
VerifyErle(estimator.Erle(/*onset_compensated=*/true),
|
||||
std::pow(2.f, estimator.FullbandErleLog2()), config.erle.max_l,
|
||||
config.erle.max_h);
|
||||
VerifyErleGreaterOrEqual(estimator.Erle(/*onset_compensated=*/false),
|
||||
estimator.Erle(/*onset_compensated=*/true));
|
||||
VerifyErleGreaterOrEqual(estimator.ErleUnbounded(),
|
||||
estimator.Erle(/*onset_compensated=*/false));
|
||||
|
||||
FormNearendFrame(&x, &X2, E2, Y2);
|
||||
// Verifies that the ERLE is not immediately decreased during nearend
|
||||
@ -194,6 +209,10 @@ TEST_P(ErleEstimatorMultiChannel, VerifyErleIncreaseAndHold) {
|
||||
VerifyErle(estimator.Erle(/*onset_compensated=*/true),
|
||||
std::pow(2.f, estimator.FullbandErleLog2()), config.erle.max_l,
|
||||
config.erle.max_h);
|
||||
VerifyErleGreaterOrEqual(estimator.Erle(/*onset_compensated=*/false),
|
||||
estimator.Erle(/*onset_compensated=*/true));
|
||||
VerifyErleGreaterOrEqual(estimator.ErleUnbounded(),
|
||||
estimator.Erle(/*onset_compensated=*/false));
|
||||
}
|
||||
|
||||
TEST_P(ErleEstimatorMultiChannel, VerifyErleTrackingOnOnsets) {
|
||||
@ -212,9 +231,10 @@ TEST_P(ErleEstimatorMultiChannel, VerifyErleTrackingOnOnsets) {
|
||||
kNumBands, std::vector<std::vector<float>>(
|
||||
num_render_channels, std::vector<float>(kBlockSize, 0.f)));
|
||||
std::vector<std::vector<std::array<float, kFftLengthBy2Plus1>>>
|
||||
filter_frequency_response(
|
||||
config.filter.refined.length_blocks,
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>>(num_capture_channels));
|
||||
filter_frequency_response(
|
||||
config.filter.refined.length_blocks,
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>>(
|
||||
num_capture_channels));
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(config, kSampleRateHz, num_render_channels));
|
||||
|
||||
|
||||
@ -177,7 +177,8 @@ void ResidualEchoEstimator::Estimate(
|
||||
rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>> S2_linear,
|
||||
rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>> Y2,
|
||||
bool dominant_nearend,
|
||||
rtc::ArrayView<std::array<float, kFftLengthBy2Plus1>> R2) {
|
||||
rtc::ArrayView<std::array<float, kFftLengthBy2Plus1>> R2,
|
||||
rtc::ArrayView<std::array<float, kFftLengthBy2Plus1>> R2_unbounded) {
|
||||
RTC_DCHECK_EQ(R2.size(), Y2.size());
|
||||
RTC_DCHECK_EQ(R2.size(), S2_linear.size());
|
||||
|
||||
@ -193,14 +194,18 @@ void ResidualEchoEstimator::Estimate(
|
||||
if (aec_state.SaturatedEcho()) {
|
||||
for (size_t ch = 0; ch < num_capture_channels; ++ch) {
|
||||
std::copy(Y2[ch].begin(), Y2[ch].end(), R2[ch].begin());
|
||||
std::copy(Y2[ch].begin(), Y2[ch].end(), R2_unbounded[ch].begin());
|
||||
}
|
||||
} else {
|
||||
const bool onset_compensated =
|
||||
erle_onset_compensation_in_dominant_nearend_ || !dominant_nearend;
|
||||
LinearEstimate(S2_linear, aec_state.Erle(onset_compensated), R2);
|
||||
LinearEstimate(S2_linear, aec_state.ErleUnbounded(), R2_unbounded);
|
||||
}
|
||||
|
||||
AddReverb(ReverbType::kLinear, aec_state, render_buffer, R2);
|
||||
UpdateReverb(ReverbType::kLinear, aec_state, render_buffer);
|
||||
AddReverb(R2);
|
||||
AddReverb(R2_unbounded);
|
||||
} else {
|
||||
const float echo_path_gain =
|
||||
GetEchoPathGain(aec_state, /*gain_for_early_reflections=*/true);
|
||||
@ -210,6 +215,7 @@ void ResidualEchoEstimator::Estimate(
|
||||
if (aec_state.SaturatedEcho()) {
|
||||
for (size_t ch = 0; ch < num_capture_channels; ++ch) {
|
||||
std::copy(Y2[ch].begin(), Y2[ch].end(), R2[ch].begin());
|
||||
std::copy(Y2[ch].begin(), Y2[ch].end(), R2_unbounded[ch].begin());
|
||||
}
|
||||
} else {
|
||||
// Estimate the echo generating signal power.
|
||||
@ -229,11 +235,14 @@ void ResidualEchoEstimator::Estimate(
|
||||
}
|
||||
|
||||
NonLinearEstimate(echo_path_gain, X2, R2);
|
||||
NonLinearEstimate(echo_path_gain, X2, R2_unbounded);
|
||||
}
|
||||
|
||||
if (config_.echo_model.model_reverb_in_nonlinear_mode &&
|
||||
!aec_state.TransparentModeActive()) {
|
||||
AddReverb(ReverbType::kNonLinear, aec_state, render_buffer, R2);
|
||||
UpdateReverb(ReverbType::kNonLinear, aec_state, render_buffer);
|
||||
AddReverb(R2);
|
||||
AddReverb(R2_unbounded);
|
||||
}
|
||||
}
|
||||
|
||||
@ -244,6 +253,7 @@ void ResidualEchoEstimator::Estimate(
|
||||
for (size_t ch = 0; ch < num_capture_channels; ++ch) {
|
||||
for (size_t k = 0; k < kFftLengthBy2Plus1; ++k) {
|
||||
R2[ch][k] *= residual_scaling[k];
|
||||
R2_unbounded[ch][k] *= residual_scaling[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -292,14 +302,10 @@ void ResidualEchoEstimator::UpdateRenderNoisePower(
|
||||
}
|
||||
}
|
||||
|
||||
// Adds the estimated power of the reverb to the residual echo power.
|
||||
void ResidualEchoEstimator::AddReverb(
|
||||
ReverbType reverb_type,
|
||||
const AecState& aec_state,
|
||||
const RenderBuffer& render_buffer,
|
||||
rtc::ArrayView<std::array<float, kFftLengthBy2Plus1>> R2) {
|
||||
const size_t num_capture_channels = R2.size();
|
||||
|
||||
// Updates the reverb estimation.
|
||||
void ResidualEchoEstimator::UpdateReverb(ReverbType reverb_type,
|
||||
const AecState& aec_state,
|
||||
const RenderBuffer& render_buffer) {
|
||||
// Choose reverb partition based on what type of echo power model is used.
|
||||
const size_t first_reverb_partition =
|
||||
reverb_type == ReverbType::kLinear
|
||||
@ -334,6 +340,11 @@ void ResidualEchoEstimator::AddReverb(
|
||||
echo_reverb_.UpdateReverbNoFreqShaping(render_power, echo_path_gain,
|
||||
aec_state.ReverbDecay());
|
||||
}
|
||||
}
|
||||
// Adds the estimated power of the reverb to the residual echo power.
|
||||
void ResidualEchoEstimator::AddReverb(
|
||||
rtc::ArrayView<std::array<float, kFftLengthBy2Plus1>> R2) const {
|
||||
const size_t num_capture_channels = R2.size();
|
||||
|
||||
// Add the reverb power.
|
||||
rtc::ArrayView<const float, kFftLengthBy2Plus1> reverb_power =
|
||||
|
||||
@ -40,7 +40,8 @@ class ResidualEchoEstimator {
|
||||
rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>> S2_linear,
|
||||
rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>> Y2,
|
||||
bool dominant_nearend,
|
||||
rtc::ArrayView<std::array<float, kFftLengthBy2Plus1>> R2);
|
||||
rtc::ArrayView<std::array<float, kFftLengthBy2Plus1>> R2,
|
||||
rtc::ArrayView<std::array<float, kFftLengthBy2Plus1>> R2_unbounded);
|
||||
|
||||
private:
|
||||
enum class ReverbType { kLinear, kNonLinear };
|
||||
@ -52,12 +53,15 @@ class ResidualEchoEstimator {
|
||||
// render signal.
|
||||
void UpdateRenderNoisePower(const RenderBuffer& render_buffer);
|
||||
|
||||
// Updates the reverb estimation.
|
||||
void UpdateReverb(ReverbType reverb_type,
|
||||
const AecState& aec_state,
|
||||
const RenderBuffer& render_buffer);
|
||||
|
||||
// Adds the estimated unmodelled echo power to the residual echo power
|
||||
// estimate.
|
||||
void AddReverb(ReverbType reverb_type,
|
||||
const AecState& aec_state,
|
||||
const RenderBuffer& render_buffer,
|
||||
rtc::ArrayView<std::array<float, kFftLengthBy2Plus1>> R2);
|
||||
void AddReverb(
|
||||
rtc::ArrayView<std::array<float, kFftLengthBy2Plus1>> R2) const;
|
||||
|
||||
// Gets the echo path gain to apply.
|
||||
float GetEchoPathGain(const AecState& aec_state,
|
||||
|
||||
@ -48,6 +48,8 @@ TEST_P(ResidualEchoEstimatorMultiChannel, BasicTest) {
|
||||
num_capture_channels);
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> Y2(num_capture_channels);
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> R2(num_capture_channels);
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> R2_unbounded(
|
||||
num_capture_channels);
|
||||
std::vector<std::vector<std::vector<float>>> x(
|
||||
kNumBands, std::vector<std::vector<float>>(
|
||||
num_render_channels, std::vector<float>(kBlockSize, 0.f)));
|
||||
@ -100,7 +102,8 @@ TEST_P(ResidualEchoEstimatorMultiChannel, BasicTest) {
|
||||
output);
|
||||
|
||||
estimator.Estimate(aec_state, *render_delay_buffer->GetRenderBuffer(),
|
||||
S2_linear, Y2, /*dominant_nearend=*/false, R2);
|
||||
S2_linear, Y2, /*dominant_nearend=*/false, R2,
|
||||
R2_unbounded);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -49,6 +49,7 @@ SubbandErleEstimator::SubbandErleEstimator(const EchoCanceller3Config& config,
|
||||
accum_spectra_(num_capture_channels),
|
||||
erle_(num_capture_channels),
|
||||
erle_onset_compensated_(num_capture_channels),
|
||||
erle_unbounded_(num_capture_channels),
|
||||
erle_during_onsets_(num_capture_channels),
|
||||
coming_onset_(num_capture_channels),
|
||||
hold_counters_(num_capture_channels) {
|
||||
@ -62,6 +63,7 @@ void SubbandErleEstimator::Reset() {
|
||||
for (size_t ch = 0; ch < num_capture_channels; ++ch) {
|
||||
erle_[ch].fill(min_erle_);
|
||||
erle_onset_compensated_[ch].fill(min_erle_);
|
||||
erle_unbounded_[ch].fill(min_erle_);
|
||||
erle_during_onsets_[ch].fill(min_erle_);
|
||||
coming_onset_[ch].fill(true);
|
||||
hold_counters_[ch].fill(0);
|
||||
@ -90,6 +92,10 @@ void SubbandErleEstimator::Update(
|
||||
auto& erle_oc = erle_onset_compensated_[ch];
|
||||
erle_oc[0] = erle_oc[1];
|
||||
erle_oc[kFftLengthBy2] = erle_oc[kFftLengthBy2 - 1];
|
||||
|
||||
auto& erle_u = erle_unbounded_[ch];
|
||||
erle_u[0] = erle_u[1];
|
||||
erle_u[kFftLengthBy2] = erle_u[kFftLengthBy2 - 1];
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,6 +169,11 @@ void SubbandErleEstimator::UpdateBands(
|
||||
update_erle_band(erle_onset_compensated_[ch][k], new_erle[k],
|
||||
low_render_energy, min_erle_, max_erle_[k]);
|
||||
}
|
||||
|
||||
// Virtually unbounded ERLE.
|
||||
constexpr float kUnboundedErleMax = 100000.0f;
|
||||
update_erle_band(erle_unbounded_[ch][k], new_erle[k], low_render_energy,
|
||||
min_erle_, kUnboundedErleMax);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,6 +47,12 @@ class SubbandErleEstimator {
|
||||
: erle_;
|
||||
}
|
||||
|
||||
// Returns the non-capped ERLE estimate.
|
||||
rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>> ErleUnbounded()
|
||||
const {
|
||||
return erle_unbounded_;
|
||||
}
|
||||
|
||||
// Returns the ERLE estimate at onsets (only used for testing).
|
||||
rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>> ErleDuringOnsets()
|
||||
const {
|
||||
@ -88,6 +94,7 @@ class SubbandErleEstimator {
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> erle_;
|
||||
// ERLE lowered during render onsets.
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> erle_onset_compensated_;
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> erle_unbounded_;
|
||||
// Estimation of ERLE during render onsets.
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> erle_during_onsets_;
|
||||
std::vector<std::array<bool, kFftLengthBy2Plus1>> coming_onset_;
|
||||
|
||||
@ -23,10 +23,15 @@
|
||||
#include "modules/audio_processing/logging/apm_data_dumper.h"
|
||||
#include "rtc_base/atomic_ops.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "system_wrappers/include/field_trial.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
bool UseUnboundedEchoSpectrum() {
|
||||
return field_trial::IsEnabled("WebRTC-Aec3UseUnboundedEchoSpectrum");
|
||||
}
|
||||
|
||||
void LimitLowFrequencyGains(std::array<float, kFftLengthBy2Plus1>* gain) {
|
||||
// Limit the low frequency gains to avoid the impact of the high-pass filter
|
||||
// on the lower-frequency gain influencing the overall achieved gain.
|
||||
@ -342,7 +347,8 @@ SuppressionGain::SuppressionGain(const EchoCanceller3Config& config,
|
||||
config_.suppressor.nearend_tuning),
|
||||
normal_params_(config_.suppressor.last_lf_band,
|
||||
config_.suppressor.first_hf_band,
|
||||
config_.suppressor.normal_tuning) {
|
||||
config_.suppressor.normal_tuning),
|
||||
use_unbounded_echo_spectrum_(UseUnboundedEchoSpectrum()) {
|
||||
RTC_DCHECK_LT(0, state_change_duration_blocks_);
|
||||
last_gain_.fill(1.f);
|
||||
if (config_.suppressor.use_subband_nearend_detection) {
|
||||
@ -363,6 +369,8 @@ void SuppressionGain::GetGain(
|
||||
rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>> echo_spectrum,
|
||||
rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>>
|
||||
residual_echo_spectrum,
|
||||
rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>>
|
||||
residual_echo_spectrum_unbounded,
|
||||
rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>>
|
||||
comfort_noise_spectrum,
|
||||
const RenderSignalAnalyzer& render_signal_analyzer,
|
||||
@ -374,8 +382,13 @@ void SuppressionGain::GetGain(
|
||||
RTC_DCHECK(high_bands_gain);
|
||||
RTC_DCHECK(low_band_gain);
|
||||
|
||||
// Choose residual echo spectrum for the dominant nearend detector.
|
||||
const auto echo = use_unbounded_echo_spectrum_
|
||||
? residual_echo_spectrum_unbounded
|
||||
: residual_echo_spectrum;
|
||||
|
||||
// Update the nearend state selection.
|
||||
dominant_nearend_detector_->Update(nearend_spectrum, residual_echo_spectrum,
|
||||
dominant_nearend_detector_->Update(nearend_spectrum, echo,
|
||||
comfort_noise_spectrum, initial_state_);
|
||||
|
||||
// Compute gain for the lower band.
|
||||
@ -391,6 +404,9 @@ void SuppressionGain::GetGain(
|
||||
*high_bands_gain =
|
||||
UpperBandsGain(echo_spectrum, comfort_noise_spectrum, narrow_peak_band,
|
||||
aec_state.SaturatedEcho(), render, *low_band_gain);
|
||||
|
||||
data_dumper_->DumpRaw("aec3_dominant_nearend",
|
||||
dominant_nearend_detector_->IsNearendState());
|
||||
}
|
||||
|
||||
void SuppressionGain::SetInitialState(bool state) {
|
||||
|
||||
@ -42,6 +42,8 @@ class SuppressionGain {
|
||||
rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>> echo_spectrum,
|
||||
rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>>
|
||||
residual_echo_spectrum,
|
||||
rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>>
|
||||
residual_echo_spectrum_unbounded,
|
||||
rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>>
|
||||
comfort_noise_spectrum,
|
||||
const RenderSignalAnalyzer& render_signal_analyzer,
|
||||
@ -128,6 +130,9 @@ class SuppressionGain {
|
||||
std::vector<aec3::MovingAverage> nearend_smoothers_;
|
||||
const GainParameters nearend_params_;
|
||||
const GainParameters normal_params_;
|
||||
// Determines if the dominant nearend detector uses the unbounded residual
|
||||
// echo spectrum.
|
||||
const bool use_unbounded_echo_spectrum_;
|
||||
std::unique_ptr<NearendDetector> dominant_nearend_detector_;
|
||||
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(SuppressionGain);
|
||||
|
||||
@ -26,29 +26,30 @@ namespace aec3 {
|
||||
|
||||
// Verifies that the check for non-null output gains works.
|
||||
TEST(SuppressionGainDeathTest, NullOutputGains) {
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> E2(1, {0.f});
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> R2(1, {0.f});
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> E2(1, {0.0f});
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> R2(1, {0.0f});
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> R2_unbounded(1, {0.0f});
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> S2(1);
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> N2(1, {0.f});
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> N2(1, {0.0f});
|
||||
for (auto& S2_k : S2) {
|
||||
S2_k.fill(.1f);
|
||||
S2_k.fill(0.1f);
|
||||
}
|
||||
FftData E;
|
||||
FftData Y;
|
||||
E.re.fill(0.f);
|
||||
E.im.fill(0.f);
|
||||
Y.re.fill(0.f);
|
||||
Y.im.fill(0.f);
|
||||
E.re.fill(0.0f);
|
||||
E.im.fill(0.0f);
|
||||
Y.re.fill(0.0f);
|
||||
Y.im.fill(0.0f);
|
||||
|
||||
float high_bands_gain;
|
||||
AecState aec_state(EchoCanceller3Config{}, 1);
|
||||
EXPECT_DEATH(
|
||||
SuppressionGain(EchoCanceller3Config{}, DetectOptimization(), 16000, 1)
|
||||
.GetGain(E2, S2, R2, N2,
|
||||
.GetGain(E2, S2, R2, R2_unbounded, N2,
|
||||
RenderSignalAnalyzer((EchoCanceller3Config{})), aec_state,
|
||||
std::vector<std::vector<std::vector<float>>>(
|
||||
3, std::vector<std::vector<float>>(
|
||||
1, std::vector<float>(kBlockSize, 0.f))),
|
||||
1, std::vector<float>(kBlockSize, 0.0f))),
|
||||
false, &high_bands_gain, nullptr),
|
||||
"");
|
||||
}
|
||||
@ -67,15 +68,17 @@ TEST(SuppressionGain, BasicGainComputation) {
|
||||
float high_bands_gain;
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> E2(kNumCaptureChannels);
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> S2(kNumCaptureChannels,
|
||||
{0.f});
|
||||
{0.0f});
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> Y2(kNumCaptureChannels);
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> R2(kNumCaptureChannels);
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> R2_unbounded(
|
||||
kNumCaptureChannels);
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> N2(kNumCaptureChannels);
|
||||
std::array<float, kFftLengthBy2Plus1> g;
|
||||
std::vector<SubtractorOutput> output(kNumCaptureChannels);
|
||||
std::vector<std::vector<std::vector<float>>> x(
|
||||
kNumBands, std::vector<std::vector<float>>(
|
||||
kNumRenderChannels, std::vector<float>(kBlockSize, 0.f)));
|
||||
kNumRenderChannels, std::vector<float>(kBlockSize, 0.0f)));
|
||||
EchoCanceller3Config config;
|
||||
AecState aec_state(config, kNumCaptureChannels);
|
||||
ApmDataDumper data_dumper(42);
|
||||
@ -89,8 +92,9 @@ TEST(SuppressionGain, BasicGainComputation) {
|
||||
for (size_t ch = 0; ch < kNumCaptureChannels; ++ch) {
|
||||
E2[ch].fill(10.f);
|
||||
Y2[ch].fill(10.f);
|
||||
R2[ch].fill(.1f);
|
||||
N2[ch].fill(100.f);
|
||||
R2[ch].fill(0.1f);
|
||||
R2_unbounded[ch].fill(0.1f);
|
||||
N2[ch].fill(100.0f);
|
||||
}
|
||||
for (auto& subtractor_output : output) {
|
||||
subtractor_output.Reset();
|
||||
@ -107,17 +111,18 @@ TEST(SuppressionGain, BasicGainComputation) {
|
||||
aec_state.Update(delay_estimate, subtractor.FilterFrequencyResponses(),
|
||||
subtractor.FilterImpulseResponses(),
|
||||
*render_delay_buffer->GetRenderBuffer(), E2, Y2, output);
|
||||
suppression_gain.GetGain(E2, S2, R2, N2, analyzer, aec_state, x, false,
|
||||
&high_bands_gain, &g);
|
||||
suppression_gain.GetGain(E2, S2, R2, R2_unbounded, N2, analyzer, aec_state,
|
||||
x, false, &high_bands_gain, &g);
|
||||
}
|
||||
std::for_each(g.begin(), g.end(),
|
||||
[](float a) { EXPECT_NEAR(1.f, a, 0.001); });
|
||||
[](float a) { EXPECT_NEAR(1.0f, a, 0.001f); });
|
||||
|
||||
// Ensure that a strong nearend is detected to mask any echoes.
|
||||
for (size_t ch = 0; ch < kNumCaptureChannels; ++ch) {
|
||||
E2[ch].fill(100.f);
|
||||
Y2[ch].fill(100.f);
|
||||
R2[ch].fill(0.1f);
|
||||
R2_unbounded[ch].fill(0.1f);
|
||||
S2[ch].fill(0.1f);
|
||||
N2[ch].fill(0.f);
|
||||
}
|
||||
@ -126,22 +131,23 @@ TEST(SuppressionGain, BasicGainComputation) {
|
||||
aec_state.Update(delay_estimate, subtractor.FilterFrequencyResponses(),
|
||||
subtractor.FilterImpulseResponses(),
|
||||
*render_delay_buffer->GetRenderBuffer(), E2, Y2, output);
|
||||
suppression_gain.GetGain(E2, S2, R2, N2, analyzer, aec_state, x, false,
|
||||
&high_bands_gain, &g);
|
||||
suppression_gain.GetGain(E2, S2, R2, R2_unbounded, N2, analyzer, aec_state,
|
||||
x, false, &high_bands_gain, &g);
|
||||
}
|
||||
std::for_each(g.begin(), g.end(),
|
||||
[](float a) { EXPECT_NEAR(1.f, a, 0.001); });
|
||||
[](float a) { EXPECT_NEAR(1.0f, a, 0.001f); });
|
||||
|
||||
// Add a strong echo to one of the channels and ensure that it is suppressed.
|
||||
E2[1].fill(1000000000.f);
|
||||
R2[1].fill(10000000000000.f);
|
||||
E2[1].fill(1000000000.0f);
|
||||
R2[1].fill(10000000000000.0f);
|
||||
R2_unbounded[1].fill(10000000000000.0f);
|
||||
|
||||
for (int k = 0; k < 10; ++k) {
|
||||
suppression_gain.GetGain(E2, S2, R2, N2, analyzer, aec_state, x, false,
|
||||
&high_bands_gain, &g);
|
||||
suppression_gain.GetGain(E2, S2, R2, R2_unbounded, N2, analyzer, aec_state,
|
||||
x, false, &high_bands_gain, &g);
|
||||
}
|
||||
std::for_each(g.begin(), g.end(),
|
||||
[](float a) { EXPECT_NEAR(0.f, a, 0.001); });
|
||||
[](float a) { EXPECT_NEAR(0.0f, a, 0.001f); });
|
||||
}
|
||||
|
||||
} // namespace aec3
|
||||
|
||||
Reference in New Issue
Block a user