AEC3: Add support for multiple channels to the reverb modelling
This CL adds support for multiple channels in the reverb modelling. As a side effect, it also partly adds multi-channel supports for the sections of the code. Beyond adding the multi-channel support, a bug is fixed as part of this CL. Since the bug fix affects the bitexactness, as a safety precaution the CL includes the ability to override the bugfix. Apart from the contributions from the bugfix, the changes have been verified to be bitexact for a large set of mono recordings. Bug: webrtc:10913 Change-Id: I1f307b532be85ef4182f8db41384f44d40a25219 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/156382 Commit-Queue: Per Åhgren <peah@webrtc.org> Reviewed-by: Sam Zackrisson <saza@webrtc.org> Cr-Commit-Position: refs/heads/master@{#29456}
This commit is contained in:
@ -86,6 +86,8 @@ struct RTC_EXPORT EchoCanceller3Config {
|
||||
float max_h = 1.5f;
|
||||
bool onset_detection = true;
|
||||
size_t num_sections = 1;
|
||||
bool clamp_quality_estimate_to_zero = true;
|
||||
bool clamp_quality_estimate_to_one = true;
|
||||
} erle;
|
||||
|
||||
struct EpStrength {
|
||||
|
@ -197,6 +197,10 @@ void Aec3ConfigFromJsonString(absl::string_view json_string,
|
||||
ReadParam(section, "max_h", &cfg.erle.max_h);
|
||||
ReadParam(section, "onset_detection", &cfg.erle.onset_detection);
|
||||
ReadParam(section, "num_sections", &cfg.erle.num_sections);
|
||||
ReadParam(section, "clamp_quality_estimate_to_zero",
|
||||
&cfg.erle.clamp_quality_estimate_to_zero);
|
||||
ReadParam(section, "clamp_quality_estimate_to_one",
|
||||
&cfg.erle.clamp_quality_estimate_to_one);
|
||||
}
|
||||
|
||||
if (rtc::GetValueFromJsonObject(aec3_root, "ep_strength", §ion)) {
|
||||
@ -408,7 +412,11 @@ std::string Aec3ConfigToJsonString(const EchoCanceller3Config& config) {
|
||||
ost << "\"max_h\": " << config.erle.max_h << ",";
|
||||
ost << "\"onset_detection\": "
|
||||
<< (config.erle.onset_detection ? "true" : "false") << ",";
|
||||
ost << "\"num_sections\": " << config.erle.num_sections;
|
||||
ost << "\"num_sections\": " << config.erle.num_sections << ",";
|
||||
ost << "\"clamp_quality_estimate_to_zero\": "
|
||||
<< (config.erle.clamp_quality_estimate_to_zero ? "true" : "false") << ",";
|
||||
ost << "\"clamp_quality_estimate_to_one\": "
|
||||
<< (config.erle.clamp_quality_estimate_to_one ? "true" : "false");
|
||||
ost << "},";
|
||||
|
||||
ost << "\"ep_strength\": {";
|
||||
|
@ -111,29 +111,23 @@ AecState::AecState(const EchoCanceller3Config& config,
|
||||
new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
|
||||
config_(config),
|
||||
initial_state_(config_),
|
||||
delay_state_(config_),
|
||||
delay_state_(config_, num_capture_channels),
|
||||
transparent_state_(config_),
|
||||
filter_quality_state_(config_),
|
||||
filter_quality_state_(config_, num_capture_channels),
|
||||
erl_estimator_(2 * kNumBlocksPerSecond),
|
||||
erle_estimator_(2 * kNumBlocksPerSecond, config_, num_capture_channels),
|
||||
filter_analyzers_(num_capture_channels),
|
||||
filter_analyzer_(config_, num_capture_channels),
|
||||
echo_audibility_(
|
||||
config_.echo_audibility.use_stationarity_properties_at_init),
|
||||
reverb_model_estimator_(config_),
|
||||
subtractor_output_analyzers_(num_capture_channels) {
|
||||
for (size_t ch = 0; ch < num_capture_channels; ++ch) {
|
||||
filter_analyzers_[ch] = std::make_unique<FilterAnalyzer>(config_);
|
||||
}
|
||||
}
|
||||
reverb_model_estimator_(config_, num_capture_channels),
|
||||
subtractor_output_analyzers_(num_capture_channels) {}
|
||||
|
||||
AecState::~AecState() = default;
|
||||
|
||||
void AecState::HandleEchoPathChange(
|
||||
const EchoPathVariability& echo_path_variability) {
|
||||
const auto full_reset = [&]() {
|
||||
for (auto& filter_analyzer : filter_analyzers_) {
|
||||
filter_analyzer->Reset();
|
||||
}
|
||||
filter_analyzer_.Reset();
|
||||
capture_signal_saturation_ = false;
|
||||
strong_not_saturated_render_blocks_ = 0;
|
||||
blocks_with_active_render_ = 0;
|
||||
@ -161,49 +155,44 @@ void AecState::HandleEchoPathChange(
|
||||
void AecState::Update(
|
||||
const absl::optional<DelayEstimate>& external_delay,
|
||||
rtc::ArrayView<const std::vector<std::array<float, kFftLengthBy2Plus1>>>
|
||||
adaptive_filter_frequency_response,
|
||||
rtc::ArrayView<const std::vector<float>> adaptive_filter_impulse_response,
|
||||
adaptive_filter_frequency_responses,
|
||||
rtc::ArrayView<const std::vector<float>> adaptive_filter_impulse_responses,
|
||||
const RenderBuffer& render_buffer,
|
||||
rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>> E2_main,
|
||||
rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>> Y2,
|
||||
rtc::ArrayView<const SubtractorOutput> subtractor_output) {
|
||||
const size_t num_capture_channels = filter_analyzers_.size();
|
||||
const size_t num_capture_channels = subtractor_output_analyzers_.size();
|
||||
RTC_DCHECK_EQ(num_capture_channels, E2_main.size());
|
||||
RTC_DCHECK_EQ(num_capture_channels, Y2.size());
|
||||
RTC_DCHECK_EQ(num_capture_channels, subtractor_output.size());
|
||||
RTC_DCHECK_EQ(num_capture_channels, subtractor_output_analyzers_.size());
|
||||
RTC_DCHECK_EQ(num_capture_channels,
|
||||
adaptive_filter_frequency_response.size());
|
||||
RTC_DCHECK_EQ(num_capture_channels, adaptive_filter_impulse_response.size());
|
||||
adaptive_filter_frequency_responses.size());
|
||||
RTC_DCHECK_EQ(num_capture_channels, adaptive_filter_impulse_responses.size());
|
||||
|
||||
// Analyze the filter outputs and filters.
|
||||
bool any_filter_converged = false;
|
||||
bool all_filters_diverged = true;
|
||||
bool any_filter_consistent = false;
|
||||
float max_echo_path_gain = 0.f;
|
||||
for (size_t ch = 0; ch < subtractor_output.size(); ++ch) {
|
||||
subtractor_output_analyzers_[ch].Update(subtractor_output[ch]);
|
||||
any_filter_converged = any_filter_converged ||
|
||||
subtractor_output_analyzers_[ch].ConvergedFilter();
|
||||
all_filters_diverged = all_filters_diverged &&
|
||||
subtractor_output_analyzers_[ch].DivergedFilter();
|
||||
|
||||
filter_analyzers_[ch]->Update(adaptive_filter_impulse_response[ch],
|
||||
render_buffer);
|
||||
any_filter_consistent =
|
||||
any_filter_consistent || filter_analyzers_[ch]->Consistent();
|
||||
max_echo_path_gain =
|
||||
std::max(max_echo_path_gain, filter_analyzers_[ch]->Gain());
|
||||
}
|
||||
bool any_filter_consistent;
|
||||
float max_echo_path_gain;
|
||||
filter_analyzer_.Update(adaptive_filter_impulse_responses, render_buffer,
|
||||
&any_filter_consistent, &max_echo_path_gain);
|
||||
|
||||
// Estimate the direct path delay of the filter.
|
||||
if (config_.filter.use_linear_filter) {
|
||||
delay_state_.Update(filter_analyzers_, external_delay,
|
||||
delay_state_.Update(filter_analyzer_.FilterDelaysBlocks(), external_delay,
|
||||
strong_not_saturated_render_blocks_);
|
||||
}
|
||||
|
||||
const std::vector<std::vector<float>>& aligned_render_block =
|
||||
render_buffer.Block(-delay_state_.DirectPathFilterDelay())[0];
|
||||
render_buffer.Block(-delay_state_.DirectPathFilterDelays()[0])[0];
|
||||
|
||||
// Update render counters.
|
||||
bool active_render = false;
|
||||
@ -225,13 +214,13 @@ void AecState::Update(
|
||||
std::array<float, kFftLengthBy2Plus1> X2_reverb;
|
||||
|
||||
UpdateAndComputeReverb(render_buffer.GetSpectrumBuffer(),
|
||||
delay_state_.DirectPathFilterDelay(), ReverbDecay(),
|
||||
&reverb_model_, X2_reverb);
|
||||
delay_state_.DirectPathFilterDelays()[0],
|
||||
ReverbDecay(), &reverb_model_, X2_reverb);
|
||||
|
||||
if (config_.echo_audibility.use_stationarity_properties) {
|
||||
// Update the echo audibility evaluator.
|
||||
echo_audibility_.Update(render_buffer, reverb_model_.reverb(),
|
||||
delay_state_.DirectPathFilterDelay(),
|
||||
delay_state_.DirectPathFilterDelays()[0],
|
||||
delay_state_.ExternalDelayReported());
|
||||
}
|
||||
|
||||
@ -241,11 +230,12 @@ void AecState::Update(
|
||||
}
|
||||
|
||||
// TODO(bugs.webrtc.org/10913): Take all channels into account.
|
||||
const auto& X2 = render_buffer.Spectrum(delay_state_.DirectPathFilterDelay(),
|
||||
/*channel=*/0);
|
||||
const auto& X2 =
|
||||
render_buffer.Spectrum(delay_state_.DirectPathFilterDelays()[0],
|
||||
/*channel=*/0);
|
||||
const auto& X2_input_erle = X2_reverb;
|
||||
|
||||
erle_estimator_.Update(render_buffer, adaptive_filter_frequency_response[0],
|
||||
erle_estimator_.Update(render_buffer, adaptive_filter_frequency_responses[0],
|
||||
X2_input_erle, Y2[0], E2_main[0],
|
||||
subtractor_output_analyzers_[0].ConvergedFilter(),
|
||||
config_.erle.onset_detection);
|
||||
@ -262,7 +252,7 @@ void AecState::Update(
|
||||
initial_state_.Update(active_render, SaturatedCapture());
|
||||
|
||||
// Detect whether the transparent mode should be activated.
|
||||
transparent_state_.Update(delay_state_.DirectPathFilterDelay(),
|
||||
transparent_state_.Update(delay_state_.DirectPathFilterDelays()[0],
|
||||
any_filter_consistent, any_filter_converged,
|
||||
all_filters_diverged, active_render,
|
||||
SaturatedCapture());
|
||||
@ -277,11 +267,12 @@ void AecState::Update(
|
||||
config_.echo_audibility.use_stationarity_properties &&
|
||||
echo_audibility_.IsBlockStationary();
|
||||
|
||||
reverb_model_estimator_.Update(filter_analyzers_[0]->GetAdjustedFilter(),
|
||||
adaptive_filter_frequency_response[0],
|
||||
erle_estimator_.GetInstLinearQualityEstimate(),
|
||||
delay_state_.DirectPathFilterDelay(),
|
||||
UsableLinearEstimate(), stationary_block);
|
||||
reverb_model_estimator_.Update(
|
||||
filter_analyzer_.GetAdjustedFilters(),
|
||||
adaptive_filter_frequency_responses,
|
||||
erle_estimator_.GetInstLinearQualityEstimates(),
|
||||
delay_state_.DirectPathFilterDelays(),
|
||||
filter_quality_state_.UsableLinearFilterOutputs(), stationary_block);
|
||||
|
||||
erle_estimator_.Dump(data_dumper_);
|
||||
reverb_model_estimator_.Dump(data_dumper_.get());
|
||||
@ -291,7 +282,7 @@ void AecState::Update(
|
||||
data_dumper_->DumpRaw("aec3_usable_linear_estimate", UsableLinearEstimate());
|
||||
data_dumper_->DumpRaw("aec3_transparent_mode", TransparentMode());
|
||||
data_dumper_->DumpRaw("aec3_filter_delay",
|
||||
filter_analyzers_[0]->DelayBlocks());
|
||||
filter_analyzer_.MinFilterDelayBlocks());
|
||||
|
||||
data_dumper_->DumpRaw("aec3_any_filter_consistent", any_filter_consistent);
|
||||
data_dumper_->DumpRaw("aec3_initial_state",
|
||||
@ -335,11 +326,13 @@ void AecState::InitialState::InitialState::Update(bool active_render,
|
||||
transition_triggered_ = !initial_state_ && prev_initial_state;
|
||||
}
|
||||
|
||||
AecState::FilterDelay::FilterDelay(const EchoCanceller3Config& config)
|
||||
: delay_headroom_samples_(config.delay.delay_headroom_samples) {}
|
||||
AecState::FilterDelay::FilterDelay(const EchoCanceller3Config& config,
|
||||
size_t num_capture_channels)
|
||||
: delay_headroom_samples_(config.delay.delay_headroom_samples),
|
||||
filter_delays_blocks_(num_capture_channels, 0) {}
|
||||
|
||||
void AecState::FilterDelay::Update(
|
||||
const std::vector<std::unique_ptr<FilterAnalyzer>>& filter_analyzers,
|
||||
rtc::ArrayView<const int> analyzer_filter_delay_estimates_blocks,
|
||||
const absl::optional<DelayEstimate>& external_delay,
|
||||
size_t blocks_with_proper_filter_adaptation) {
|
||||
// Update the delay based on the external delay.
|
||||
@ -354,14 +347,15 @@ void AecState::FilterDelay::Update(
|
||||
const bool delay_estimator_may_not_have_converged =
|
||||
blocks_with_proper_filter_adaptation < 2 * kNumBlocksPerSecond;
|
||||
if (delay_estimator_may_not_have_converged && external_delay_) {
|
||||
filter_delay_blocks_ = delay_headroom_samples_ / kBlockSize;
|
||||
int delay_guess = delay_headroom_samples_ / kBlockSize;
|
||||
std::fill(filter_delays_blocks_.begin(), filter_delays_blocks_.end(),
|
||||
delay_guess);
|
||||
} else {
|
||||
// Conservatively use the min delay among the filters.
|
||||
filter_delay_blocks_ = filter_analyzers[0]->DelayBlocks();
|
||||
for (size_t ch = 1; ch < filter_analyzers.size(); ++ch) {
|
||||
filter_delay_blocks_ =
|
||||
std::min(filter_delay_blocks_, filter_analyzers[ch]->DelayBlocks());
|
||||
}
|
||||
RTC_DCHECK_EQ(filter_delays_blocks_.size(),
|
||||
analyzer_filter_delay_estimates_blocks.size());
|
||||
std::copy(analyzer_filter_delay_estimates_blocks.begin(),
|
||||
analyzer_filter_delay_estimates_blocks.end(),
|
||||
filter_delays_blocks_.begin());
|
||||
}
|
||||
}
|
||||
|
||||
@ -452,10 +446,15 @@ void AecState::TransparentMode::Update(int filter_delay_blocks,
|
||||
}
|
||||
|
||||
AecState::FilteringQualityAnalyzer::FilteringQualityAnalyzer(
|
||||
const EchoCanceller3Config& config) {}
|
||||
const EchoCanceller3Config& config,
|
||||
size_t num_capture_channels)
|
||||
: use_linear_filter_(config.filter.use_linear_filter),
|
||||
usable_linear_filter_estimates_(num_capture_channels, false) {}
|
||||
|
||||
void AecState::FilteringQualityAnalyzer::Reset() {
|
||||
usable_linear_estimate_ = false;
|
||||
std::fill(usable_linear_filter_estimates_.begin(),
|
||||
usable_linear_filter_estimates_.end(), false);
|
||||
overall_usable_linear_estimates_ = false;
|
||||
filter_update_blocks_since_reset_ = 0;
|
||||
}
|
||||
|
||||
@ -482,17 +481,24 @@ void AecState::FilteringQualityAnalyzer::Update(
|
||||
sufficient_data_to_converge_at_startup &&
|
||||
filter_update_blocks_since_reset_ > kNumBlocksPerSecond * 0.2f;
|
||||
|
||||
// The linear filter can only be used it has had time to converge.
|
||||
usable_linear_estimate_ = sufficient_data_to_converge_at_startup &&
|
||||
sufficient_data_to_converge_at_reset;
|
||||
// The linear filter can only be used if it has had time to converge.
|
||||
overall_usable_linear_estimates_ = sufficient_data_to_converge_at_startup &&
|
||||
sufficient_data_to_converge_at_reset;
|
||||
|
||||
// The linear filter can only be used if an external delay or convergence have
|
||||
// been identified
|
||||
usable_linear_estimate_ =
|
||||
usable_linear_estimate_ && (external_delay || convergence_seen_);
|
||||
overall_usable_linear_estimates_ =
|
||||
overall_usable_linear_estimates_ && (external_delay || convergence_seen_);
|
||||
|
||||
// If transparent mode is on, deactivate usign the linear filter.
|
||||
usable_linear_estimate_ = usable_linear_estimate_ && !transparent_mode;
|
||||
overall_usable_linear_estimates_ =
|
||||
overall_usable_linear_estimates_ && !transparent_mode;
|
||||
|
||||
if (use_linear_filter_) {
|
||||
std::fill(usable_linear_filter_estimates_.begin(),
|
||||
usable_linear_filter_estimates_.end(),
|
||||
overall_usable_linear_estimates_);
|
||||
}
|
||||
}
|
||||
|
||||
void AecState::SaturationDetector::Update(
|
||||
|
@ -91,7 +91,9 @@ class AecState {
|
||||
float ErlTimeDomain() const { return erl_estimator_.ErlTimeDomain(); }
|
||||
|
||||
// Returns the delay estimate based on the linear filter.
|
||||
int FilterDelayBlocks() const { return delay_state_.DirectPathFilterDelay(); }
|
||||
int FilterDelayBlocks() const {
|
||||
return delay_state_.DirectPathFilterDelays()[0];
|
||||
}
|
||||
|
||||
// Returns whether the capture signal is saturated.
|
||||
bool SaturatedCapture() const { return capture_signal_saturation_; }
|
||||
@ -130,8 +132,9 @@ class AecState {
|
||||
void Update(
|
||||
const absl::optional<DelayEstimate>& external_delay,
|
||||
rtc::ArrayView<const std::vector<std::array<float, kFftLengthBy2Plus1>>>
|
||||
adaptive_filter_frequency_response,
|
||||
rtc::ArrayView<const std::vector<float>> adaptive_filter_impulse_response,
|
||||
adaptive_filter_frequency_responses,
|
||||
rtc::ArrayView<const std::vector<float>>
|
||||
adaptive_filter_impulse_responses,
|
||||
const RenderBuffer& render_buffer,
|
||||
rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>> E2_main,
|
||||
rtc::ArrayView<const std::array<float, kFftLengthBy2Plus1>> Y2,
|
||||
@ -140,7 +143,7 @@ class AecState {
|
||||
// Returns filter length in blocks.
|
||||
int FilterLengthBlocks() const {
|
||||
// All filters have the same length, so arbitrarily return channel 0 length.
|
||||
return filter_analyzers_[/*channel=*/0]->FilterLengthBlocks();
|
||||
return filter_analyzer_.FilterLengthBlocks();
|
||||
}
|
||||
|
||||
private:
|
||||
@ -178,7 +181,8 @@ class AecState {
|
||||
// AecState.
|
||||
class FilterDelay {
|
||||
public:
|
||||
explicit FilterDelay(const EchoCanceller3Config& config);
|
||||
FilterDelay(const EchoCanceller3Config& config,
|
||||
size_t num_capture_channels);
|
||||
|
||||
// Returns whether an external delay has been reported to the AecState (from
|
||||
// the delay estimator).
|
||||
@ -186,18 +190,20 @@ class AecState {
|
||||
|
||||
// Returns the delay in blocks relative to the beginning of the filter that
|
||||
// corresponds to the direct path of the echo.
|
||||
int DirectPathFilterDelay() const { return filter_delay_blocks_; }
|
||||
rtc::ArrayView<const int> DirectPathFilterDelays() const {
|
||||
return filter_delays_blocks_;
|
||||
}
|
||||
|
||||
// Updates the delay estimates based on new data.
|
||||
void Update(
|
||||
const std::vector<std::unique_ptr<FilterAnalyzer>>& filter_analyzer,
|
||||
rtc::ArrayView<const int> analyzer_filter_delay_estimates_blocks,
|
||||
const absl::optional<DelayEstimate>& external_delay,
|
||||
size_t blocks_with_proper_filter_adaptation);
|
||||
|
||||
private:
|
||||
const int delay_headroom_samples_;
|
||||
bool external_delay_reported_ = false;
|
||||
int filter_delay_blocks_ = 0;
|
||||
std::vector<int> filter_delays_blocks_;
|
||||
absl::optional<DelayEstimate> external_delay_;
|
||||
} delay_state_;
|
||||
|
||||
@ -243,11 +249,18 @@ class AecState {
|
||||
// suppressor.
|
||||
class FilteringQualityAnalyzer {
|
||||
public:
|
||||
FilteringQualityAnalyzer(const EchoCanceller3Config& config);
|
||||
FilteringQualityAnalyzer(const EchoCanceller3Config& config,
|
||||
size_t num_capture_channels);
|
||||
|
||||
// Returns whether the the linear filter can be used for the echo
|
||||
// Returns whether the linear filter can be used for the echo
|
||||
// canceller output.
|
||||
bool LinearFilterUsable() const { return usable_linear_estimate_; }
|
||||
bool LinearFilterUsable() const { return overall_usable_linear_estimates_; }
|
||||
|
||||
// Returns whether an individual filter output can be used for the echo
|
||||
// canceller output.
|
||||
const std::vector<bool>& UsableLinearFilterOutputs() const {
|
||||
return usable_linear_filter_estimates_;
|
||||
}
|
||||
|
||||
// Resets the state of the analyzer.
|
||||
void Reset();
|
||||
@ -260,10 +273,12 @@ class AecState {
|
||||
bool any_filter_converged);
|
||||
|
||||
private:
|
||||
bool usable_linear_estimate_ = false;
|
||||
const bool use_linear_filter_;
|
||||
bool overall_usable_linear_estimates_ = false;
|
||||
size_t filter_update_blocks_since_reset_ = 0;
|
||||
size_t filter_update_blocks_since_start_ = 0;
|
||||
bool convergence_seen_ = false;
|
||||
std::vector<bool> usable_linear_filter_estimates_;
|
||||
} filter_quality_state_;
|
||||
|
||||
// Class for detecting whether the echo is to be considered to be
|
||||
@ -289,7 +304,7 @@ class AecState {
|
||||
size_t strong_not_saturated_render_blocks_ = 0;
|
||||
size_t blocks_with_active_render_ = 0;
|
||||
bool capture_signal_saturation_ = false;
|
||||
std::vector<std::unique_ptr<FilterAnalyzer>> filter_analyzers_;
|
||||
FilterAnalyzer filter_analyzer_;
|
||||
absl::optional<DelayEstimate> external_delay_;
|
||||
EchoAudibility echo_audibility_;
|
||||
ReverbModelEstimator reverb_model_estimator_;
|
||||
|
@ -42,6 +42,14 @@ EchoCanceller3Config AdjustConfig(const EchoCanceller3Config& config) {
|
||||
adjusted_cfg.delay.delay_headroom_samples = kBlockSize * 2;
|
||||
}
|
||||
|
||||
if (field_trial::IsEnabled("WebRTC-Aec3ClampInstQualityToZeroKillSwitch")) {
|
||||
adjusted_cfg.erle.clamp_quality_estimate_to_zero = false;
|
||||
}
|
||||
|
||||
if (field_trial::IsEnabled("WebRTC-Aec3ClampInstQualityToOneKillSwitch")) {
|
||||
adjusted_cfg.erle.clamp_quality_estimate_to_one = false;
|
||||
}
|
||||
|
||||
return adjusted_cfg;
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ ErleEstimator::ErleEstimator(size_t startup_phase_length_blocks_,
|
||||
size_t num_capture_channels)
|
||||
: startup_phase_length_blocks__(startup_phase_length_blocks_),
|
||||
use_signal_dependent_erle_(config.erle.num_sections > 1),
|
||||
fullband_erle_estimator_(config.erle.min, config.erle.max_l),
|
||||
fullband_erle_estimator_(config.erle, num_capture_channels),
|
||||
subband_erle_estimator_(config, num_capture_channels),
|
||||
signal_dependent_erle_estimator_(config, num_capture_channels) {
|
||||
Reset(true);
|
||||
|
@ -69,10 +69,12 @@ class ErleEstimator {
|
||||
|
||||
// Returns an estimation of the current linear filter quality based on the
|
||||
// current and past fullband ERLE estimates. The returned value is a float
|
||||
// between 0 and 1 where 1 indicates that, at this current time instant, the
|
||||
// linear filter is reaching its maximum subtraction performance.
|
||||
absl::optional<float> GetInstLinearQualityEstimate() const {
|
||||
return fullband_erle_estimator_.GetInstLinearQualityEstimate();
|
||||
// vector with content between 0 and 1 where 1 indicates that, at this current
|
||||
// time instant, the linear filter is reaching its maximum subtraction
|
||||
// performance.
|
||||
rtc::ArrayView<const absl::optional<float>> GetInstLinearQualityEstimates()
|
||||
const {
|
||||
return fullband_erle_estimator_.GetInstLinearQualityEstimates();
|
||||
}
|
||||
|
||||
void Dump(const std::unique_ptr<ApmDataDumper>& data_dumper) const;
|
||||
|
@ -47,91 +47,136 @@ size_t FindPeakIndex(rtc::ArrayView<const float> filter_time_domain,
|
||||
|
||||
int FilterAnalyzer::instance_count_ = 0;
|
||||
|
||||
FilterAnalyzer::FilterAnalyzer(const EchoCanceller3Config& config)
|
||||
FilterAnalyzer::FilterAnalyzer(const EchoCanceller3Config& config,
|
||||
size_t num_capture_channels)
|
||||
: data_dumper_(
|
||||
new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
|
||||
bounded_erl_(config.ep_strength.bounded_erl),
|
||||
default_gain_(config.ep_strength.default_gain),
|
||||
h_highpass_(GetTimeDomainLength(config.filter.main.length_blocks), 0.f),
|
||||
filter_length_blocks_(config.filter.main_initial.length_blocks),
|
||||
consistent_filter_detector_(config) {
|
||||
h_highpass_(num_capture_channels,
|
||||
std::vector<float>(
|
||||
GetTimeDomainLength(config.filter.main.length_blocks),
|
||||
0.f)),
|
||||
filter_analysis_states_(num_capture_channels,
|
||||
FilterAnalysisState(config)),
|
||||
filter_delays_blocks_(num_capture_channels, 0) {
|
||||
Reset();
|
||||
}
|
||||
|
||||
FilterAnalyzer::~FilterAnalyzer() = default;
|
||||
|
||||
void FilterAnalyzer::Reset() {
|
||||
delay_blocks_ = 0;
|
||||
blocks_since_reset_ = 0;
|
||||
gain_ = default_gain_;
|
||||
peak_index_ = 0;
|
||||
ResetRegion();
|
||||
consistent_filter_detector_.Reset();
|
||||
for (auto& state : filter_analysis_states_) {
|
||||
state.peak_index = 0;
|
||||
state.gain = default_gain_;
|
||||
state.consistent_filter_detector.Reset();
|
||||
}
|
||||
std::fill(filter_delays_blocks_.begin(), filter_delays_blocks_.end(), 0);
|
||||
}
|
||||
|
||||
void FilterAnalyzer::Update(rtc::ArrayView<const float> filter_time_domain,
|
||||
const RenderBuffer& render_buffer) {
|
||||
SetRegionToAnalyze(filter_time_domain);
|
||||
AnalyzeRegion(filter_time_domain, render_buffer);
|
||||
void FilterAnalyzer::Update(
|
||||
rtc::ArrayView<const std::vector<float>> filters_time_domain,
|
||||
const RenderBuffer& render_buffer,
|
||||
bool* any_filter_consistent,
|
||||
float* max_echo_path_gain) {
|
||||
RTC_DCHECK(any_filter_consistent);
|
||||
RTC_DCHECK(max_echo_path_gain);
|
||||
RTC_DCHECK_EQ(filters_time_domain.size(), filter_analysis_states_.size());
|
||||
RTC_DCHECK_EQ(filters_time_domain.size(), h_highpass_.size());
|
||||
|
||||
++blocks_since_reset_;
|
||||
SetRegionToAnalyze(filters_time_domain[0].size());
|
||||
AnalyzeRegion(filters_time_domain, render_buffer);
|
||||
|
||||
// Aggregate the results for all capture channels.
|
||||
auto& st_ch0 = filter_analysis_states_[0];
|
||||
*any_filter_consistent = st_ch0.consistent_estimate;
|
||||
*max_echo_path_gain = st_ch0.gain;
|
||||
min_filter_delay_blocks_ = filter_delays_blocks_[0];
|
||||
for (size_t ch = 1; ch < filters_time_domain.size(); ++ch) {
|
||||
auto& st_ch = filter_analysis_states_[ch];
|
||||
*any_filter_consistent =
|
||||
*any_filter_consistent || st_ch.consistent_estimate;
|
||||
*max_echo_path_gain = std::max(*max_echo_path_gain, st_ch.gain);
|
||||
min_filter_delay_blocks_ =
|
||||
std::min(min_filter_delay_blocks_, filter_delays_blocks_[ch]);
|
||||
}
|
||||
}
|
||||
|
||||
void FilterAnalyzer::AnalyzeRegion(
|
||||
rtc::ArrayView<const float> filter_time_domain,
|
||||
rtc::ArrayView<const std::vector<float>> filters_time_domain,
|
||||
const RenderBuffer& render_buffer) {
|
||||
RTC_DCHECK_LT(region_.start_sample_, filter_time_domain.size());
|
||||
RTC_DCHECK_LT(peak_index_, filter_time_domain.size());
|
||||
RTC_DCHECK_LT(region_.end_sample_, filter_time_domain.size());
|
||||
|
||||
// Preprocess the filter to avoid issues with low-frequency components in the
|
||||
// filter.
|
||||
PreProcessFilter(filter_time_domain);
|
||||
data_dumper_->DumpRaw("aec3_linear_filter_processed_td", h_highpass_);
|
||||
PreProcessFilters(filters_time_domain);
|
||||
data_dumper_->DumpRaw("aec3_linear_filter_processed_td", h_highpass_[0]);
|
||||
|
||||
RTC_DCHECK_EQ(h_highpass_.size(), filter_time_domain.size());
|
||||
constexpr float kOneByBlockSize = 1.f / kBlockSize;
|
||||
for (size_t ch = 0; ch < filters_time_domain.size(); ++ch) {
|
||||
RTC_DCHECK_LT(region_.start_sample_, filters_time_domain[ch].size());
|
||||
RTC_DCHECK_LT(filter_analysis_states_[ch].peak_index,
|
||||
filters_time_domain[0].size());
|
||||
RTC_DCHECK_LT(region_.end_sample_, filters_time_domain[ch].size());
|
||||
|
||||
peak_index_ = FindPeakIndex(h_highpass_, peak_index_, region_.start_sample_,
|
||||
region_.end_sample_);
|
||||
delay_blocks_ = peak_index_ >> kBlockSizeLog2;
|
||||
UpdateFilterGain(h_highpass_, peak_index_);
|
||||
filter_length_blocks_ = filter_time_domain.size() * (1.f / kBlockSize);
|
||||
auto& st_ch = filter_analysis_states_[ch];
|
||||
RTC_DCHECK_EQ(h_highpass_[ch].size(), filters_time_domain[ch].size());
|
||||
|
||||
consistent_estimate_ = consistent_filter_detector_.Detect(
|
||||
h_highpass_, region_, render_buffer.Block(-delay_blocks_)[0], peak_index_,
|
||||
delay_blocks_);
|
||||
st_ch.peak_index =
|
||||
FindPeakIndex(h_highpass_[ch], st_ch.peak_index, region_.start_sample_,
|
||||
region_.end_sample_);
|
||||
filter_delays_blocks_[ch] = st_ch.peak_index >> kBlockSizeLog2;
|
||||
UpdateFilterGain(h_highpass_[ch], &st_ch);
|
||||
st_ch.filter_length_blocks =
|
||||
filters_time_domain[ch].size() * kOneByBlockSize;
|
||||
|
||||
st_ch.consistent_estimate = st_ch.consistent_filter_detector.Detect(
|
||||
h_highpass_[ch], region_,
|
||||
render_buffer.Block(-filter_delays_blocks_[ch])[0], st_ch.peak_index,
|
||||
filter_delays_blocks_[ch]);
|
||||
}
|
||||
}
|
||||
|
||||
void FilterAnalyzer::UpdateFilterGain(
|
||||
rtc::ArrayView<const float> filter_time_domain,
|
||||
size_t peak_index) {
|
||||
FilterAnalysisState* st) {
|
||||
bool sufficient_time_to_converge =
|
||||
++blocks_since_reset_ > 5 * kNumBlocksPerSecond;
|
||||
blocks_since_reset_ > 5 * kNumBlocksPerSecond;
|
||||
|
||||
if (sufficient_time_to_converge && consistent_estimate_) {
|
||||
gain_ = fabsf(filter_time_domain[peak_index]);
|
||||
if (sufficient_time_to_converge && st->consistent_estimate) {
|
||||
st->gain = fabsf(filter_time_domain[st->peak_index]);
|
||||
} else {
|
||||
if (gain_) {
|
||||
gain_ = std::max(gain_, fabsf(filter_time_domain[peak_index]));
|
||||
// TODO(peah): Verify whether this check against a float is ok.
|
||||
if (st->gain) {
|
||||
st->gain = std::max(st->gain, fabsf(filter_time_domain[st->peak_index]));
|
||||
}
|
||||
}
|
||||
|
||||
if (bounded_erl_ && gain_) {
|
||||
gain_ = std::max(gain_, 0.01f);
|
||||
if (bounded_erl_ && st->gain) {
|
||||
st->gain = std::max(st->gain, 0.01f);
|
||||
}
|
||||
}
|
||||
|
||||
void FilterAnalyzer::PreProcessFilter(
|
||||
rtc::ArrayView<const float> filter_time_domain) {
|
||||
RTC_DCHECK_GE(h_highpass_.capacity(), filter_time_domain.size());
|
||||
h_highpass_.resize(filter_time_domain.size());
|
||||
// Minimum phase high-pass filter with cutoff frequency at about 600 Hz.
|
||||
constexpr std::array<float, 3> h = {{0.7929742f, -0.36072128f, -0.47047766f}};
|
||||
void FilterAnalyzer::PreProcessFilters(
|
||||
rtc::ArrayView<const std::vector<float>> filters_time_domain) {
|
||||
for (size_t ch = 0; ch < filters_time_domain.size(); ++ch) {
|
||||
RTC_DCHECK_LT(region_.start_sample_, filters_time_domain[ch].size());
|
||||
RTC_DCHECK_LT(region_.end_sample_, filters_time_domain[ch].size());
|
||||
|
||||
std::fill(h_highpass_.begin() + region_.start_sample_,
|
||||
h_highpass_.begin() + region_.end_sample_ + 1, 0.f);
|
||||
for (size_t k = std::max(h.size() - 1, region_.start_sample_);
|
||||
k <= region_.end_sample_; ++k) {
|
||||
for (size_t j = 0; j < h.size(); ++j) {
|
||||
h_highpass_[k] += filter_time_domain[k - j] * h[j];
|
||||
RTC_DCHECK_GE(h_highpass_[ch].capacity(), filters_time_domain[ch].size());
|
||||
h_highpass_[ch].resize(filters_time_domain[ch].size());
|
||||
// Minimum phase high-pass filter with cutoff frequency at about 600 Hz.
|
||||
constexpr std::array<float, 3> h = {
|
||||
{0.7929742f, -0.36072128f, -0.47047766f}};
|
||||
|
||||
std::fill(h_highpass_[ch].begin() + region_.start_sample_,
|
||||
h_highpass_[ch].begin() + region_.end_sample_ + 1, 0.f);
|
||||
for (size_t k = std::max(h.size() - 1, region_.start_sample_);
|
||||
k <= region_.end_sample_; ++k) {
|
||||
for (size_t j = 0; j < h.size(); ++j) {
|
||||
h_highpass_[ch][k] += filters_time_domain[ch][k - j] * h[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -141,19 +186,17 @@ void FilterAnalyzer::ResetRegion() {
|
||||
region_.end_sample_ = 0;
|
||||
}
|
||||
|
||||
void FilterAnalyzer::SetRegionToAnalyze(
|
||||
rtc::ArrayView<const float> filter_time_domain) {
|
||||
void FilterAnalyzer::SetRegionToAnalyze(size_t filter_size) {
|
||||
constexpr size_t kNumberBlocksToUpdate = 1;
|
||||
auto& r = region_;
|
||||
r.start_sample_ =
|
||||
r.end_sample_ >= filter_time_domain.size() - 1 ? 0 : r.end_sample_ + 1;
|
||||
r.start_sample_ = r.end_sample_ >= filter_size - 1 ? 0 : r.end_sample_ + 1;
|
||||
r.end_sample_ =
|
||||
std::min(r.start_sample_ + kNumberBlocksToUpdate * kBlockSize - 1,
|
||||
filter_time_domain.size() - 1);
|
||||
filter_size - 1);
|
||||
|
||||
// Check range.
|
||||
RTC_DCHECK_LT(r.start_sample_, filter_time_domain.size());
|
||||
RTC_DCHECK_LT(r.end_sample_, filter_time_domain.size());
|
||||
RTC_DCHECK_LT(r.start_sample_, filter_size);
|
||||
RTC_DCHECK_LT(r.end_sample_, filter_size);
|
||||
RTC_DCHECK_LE(r.start_sample_, r.end_sample_);
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,8 @@ class RenderBuffer;
|
||||
// Class for analyzing the properties of an adaptive filter.
|
||||
class FilterAnalyzer {
|
||||
public:
|
||||
explicit FilterAnalyzer(const EchoCanceller3Config& config);
|
||||
FilterAnalyzer(const EchoCanceller3Config& config,
|
||||
size_t num_capture_channels);
|
||||
~FilterAnalyzer();
|
||||
|
||||
FilterAnalyzer(const FilterAnalyzer&) = delete;
|
||||
@ -40,35 +41,43 @@ class FilterAnalyzer {
|
||||
void Reset();
|
||||
|
||||
// Updates the estimates with new input data.
|
||||
void Update(rtc::ArrayView<const float> filter_time_domain,
|
||||
const RenderBuffer& render_buffer);
|
||||
void Update(rtc::ArrayView<const std::vector<float>> filters_time_domain,
|
||||
const RenderBuffer& render_buffer,
|
||||
bool* any_filter_consistent,
|
||||
float* max_echo_path_gain);
|
||||
|
||||
// Returns the delay of the filter in terms of blocks.
|
||||
int DelayBlocks() const { return delay_blocks_; }
|
||||
// Returns the delay in blocks for each filter.
|
||||
rtc::ArrayView<const int> FilterDelaysBlocks() const {
|
||||
return filter_delays_blocks_;
|
||||
}
|
||||
|
||||
// Returns whether the filter is consistent in the sense that it does not
|
||||
// change much over time.
|
||||
bool Consistent() const { return consistent_estimate_; }
|
||||
|
||||
// Returns the estimated filter gain.
|
||||
float Gain() const { return gain_; }
|
||||
// Returns the minimum delay of all filters in terms of blocks.
|
||||
int MinFilterDelayBlocks() const { return min_filter_delay_blocks_; }
|
||||
|
||||
// Returns the number of blocks for the current used filter.
|
||||
int FilterLengthBlocks() const { return filter_length_blocks_; }
|
||||
int FilterLengthBlocks() const {
|
||||
return filter_analysis_states_[0].filter_length_blocks;
|
||||
}
|
||||
|
||||
// Returns the preprocessed filter.
|
||||
rtc::ArrayView<const float> GetAdjustedFilter() const { return h_highpass_; }
|
||||
rtc::ArrayView<const std::vector<float>> GetAdjustedFilters() const {
|
||||
return h_highpass_;
|
||||
}
|
||||
|
||||
// Public for testing purposes only.
|
||||
void SetRegionToAnalyze(rtc::ArrayView<const float> filter_time_domain);
|
||||
void SetRegionToAnalyze(size_t filter_size);
|
||||
|
||||
private:
|
||||
void AnalyzeRegion(rtc::ArrayView<const float> filter_time_domain,
|
||||
const RenderBuffer& render_buffer);
|
||||
struct FilterAnalysisState;
|
||||
|
||||
void UpdateFilterGain(rtc::ArrayView<const float> filter_time_domain,
|
||||
size_t max_index);
|
||||
void PreProcessFilter(rtc::ArrayView<const float> filter_time_domain);
|
||||
void AnalyzeRegion(
|
||||
rtc::ArrayView<const std::vector<float>> filters_time_domain,
|
||||
const RenderBuffer& render_buffer);
|
||||
|
||||
void UpdateFilterGain(rtc::ArrayView<const float> filters_time_domain,
|
||||
FilterAnalysisState* st);
|
||||
void PreProcessFilters(
|
||||
rtc::ArrayView<const std::vector<float>> filters_time_domain);
|
||||
|
||||
void ResetRegion();
|
||||
|
||||
@ -100,19 +109,30 @@ class FilterAnalyzer {
|
||||
int consistent_delay_reference_ = -10;
|
||||
};
|
||||
|
||||
struct FilterAnalysisState {
|
||||
explicit FilterAnalysisState(const EchoCanceller3Config& config)
|
||||
: filter_length_blocks(config.filter.main_initial.length_blocks),
|
||||
consistent_filter_detector(config) {}
|
||||
float gain;
|
||||
size_t peak_index;
|
||||
int filter_length_blocks;
|
||||
bool consistent_estimate = false;
|
||||
ConsistentFilterDetector consistent_filter_detector;
|
||||
};
|
||||
|
||||
static int instance_count_;
|
||||
std::unique_ptr<ApmDataDumper> data_dumper_;
|
||||
const bool bounded_erl_;
|
||||
const float default_gain_;
|
||||
std::vector<float> h_highpass_;
|
||||
int delay_blocks_ = 0;
|
||||
std::vector<std::vector<float>> h_highpass_;
|
||||
|
||||
size_t blocks_since_reset_ = 0;
|
||||
bool consistent_estimate_ = false;
|
||||
float gain_;
|
||||
size_t peak_index_;
|
||||
int filter_length_blocks_;
|
||||
FilterRegion region_;
|
||||
ConsistentFilterDetector consistent_filter_detector_;
|
||||
|
||||
std::vector<FilterAnalysisState> filter_analysis_states_;
|
||||
std::vector<int> filter_delays_blocks_;
|
||||
|
||||
int min_filter_delay_blocks_ = 0;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
@ -21,11 +21,11 @@ namespace webrtc {
|
||||
TEST(FilterAnalyzer, FilterResize) {
|
||||
EchoCanceller3Config c;
|
||||
std::vector<float> filter(65, 0.f);
|
||||
FilterAnalyzer fa(c);
|
||||
fa.SetRegionToAnalyze(filter);
|
||||
fa.SetRegionToAnalyze(filter);
|
||||
FilterAnalyzer fa(c, 1);
|
||||
fa.SetRegionToAnalyze(filter.size());
|
||||
fa.SetRegionToAnalyze(filter.size());
|
||||
filter.resize(32);
|
||||
fa.SetRegionToAnalyze(filter);
|
||||
fa.SetRegionToAnalyze(filter.size());
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
@ -30,9 +30,13 @@ constexpr int kBlocksToHoldErle = 100;
|
||||
constexpr int kPointsToAccumulate = 6;
|
||||
} // namespace
|
||||
|
||||
FullBandErleEstimator::FullBandErleEstimator(float min_erle, float max_erle_lf)
|
||||
: min_erle_log2_(FastApproxLog2f(min_erle + kEpsilon)),
|
||||
max_erle_lf_log2(FastApproxLog2f(max_erle_lf + kEpsilon)) {
|
||||
FullBandErleEstimator::FullBandErleEstimator(
|
||||
const EchoCanceller3Config::Erle& config,
|
||||
size_t num_capture_channels)
|
||||
: min_erle_log2_(FastApproxLog2f(config.min + kEpsilon)),
|
||||
max_erle_lf_log2(FastApproxLog2f(config.max_l + kEpsilon)),
|
||||
instantaneous_erle_(config),
|
||||
linear_filters_qualities_(num_capture_channels) {
|
||||
Reset();
|
||||
}
|
||||
|
||||
@ -40,6 +44,7 @@ FullBandErleEstimator::~FullBandErleEstimator() = default;
|
||||
|
||||
void FullBandErleEstimator::Reset() {
|
||||
instantaneous_erle_.Reset();
|
||||
UpdateQualityEstimates();
|
||||
erle_time_domain_log2_ = min_erle_log2_;
|
||||
hold_counter_time_domain_ = 0;
|
||||
}
|
||||
@ -72,6 +77,8 @@ void FullBandErleEstimator::Update(rtc::ArrayView<const float> X2,
|
||||
if (hold_counter_time_domain_ == 0) {
|
||||
instantaneous_erle_.ResetAccumulators();
|
||||
}
|
||||
|
||||
UpdateQualityEstimates();
|
||||
}
|
||||
|
||||
void FullBandErleEstimator::Dump(
|
||||
@ -80,7 +87,15 @@ void FullBandErleEstimator::Dump(
|
||||
instantaneous_erle_.Dump(data_dumper);
|
||||
}
|
||||
|
||||
FullBandErleEstimator::ErleInstantaneous::ErleInstantaneous() {
|
||||
void FullBandErleEstimator::UpdateQualityEstimates() {
|
||||
std::fill(linear_filters_qualities_.begin(), linear_filters_qualities_.end(),
|
||||
instantaneous_erle_.GetQualityEstimate());
|
||||
}
|
||||
|
||||
FullBandErleEstimator::ErleInstantaneous::ErleInstantaneous(
|
||||
const EchoCanceller3Config::Erle& config)
|
||||
: clamp_inst_quality_to_zero_(config.clamp_quality_estimate_to_zero),
|
||||
clamp_inst_quality_to_one_(config.clamp_quality_estimate_to_one) {
|
||||
Reset();
|
||||
}
|
||||
|
||||
@ -154,6 +169,8 @@ void FullBandErleEstimator::ErleInstantaneous::UpdateQualityEstimate() {
|
||||
const float alpha = 0.07f;
|
||||
float quality_estimate = 0.f;
|
||||
RTC_DCHECK(erle_log2_);
|
||||
// TODO(peah): Currently, the estimate can become be less than 0; this should
|
||||
// be corrected.
|
||||
if (max_erle_log2_ > min_erle_log2_) {
|
||||
quality_estimate = (erle_log2_.value() - min_erle_log2_) /
|
||||
(max_erle_log2_ - min_erle_log2_);
|
||||
|
@ -12,9 +12,11 @@
|
||||
#define MODULES_AUDIO_PROCESSING_AEC3_FULLBAND_ERLE_ESTIMATOR_H_
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/array_view.h"
|
||||
#include "api/audio/echo_canceller3_config.h"
|
||||
#include "modules/audio_processing/logging/apm_data_dumper.h"
|
||||
|
||||
namespace webrtc {
|
||||
@ -23,7 +25,8 @@ namespace webrtc {
|
||||
// freuquency bands.
|
||||
class FullBandErleEstimator {
|
||||
public:
|
||||
FullBandErleEstimator(float min_erle, float max_erle_lf);
|
||||
FullBandErleEstimator(const EchoCanceller3Config::Erle& config,
|
||||
size_t num_capture_channels);
|
||||
~FullBandErleEstimator();
|
||||
// Resets the ERLE estimator.
|
||||
void Reset();
|
||||
@ -39,16 +42,19 @@ class FullBandErleEstimator {
|
||||
|
||||
// Returns an estimation of the current linear filter quality. It returns a
|
||||
// float number between 0 and 1 mapping 1 to the highest possible quality.
|
||||
absl::optional<float> GetInstLinearQualityEstimate() const {
|
||||
return instantaneous_erle_.GetQualityEstimate();
|
||||
rtc::ArrayView<const absl::optional<float>> GetInstLinearQualityEstimates()
|
||||
const {
|
||||
return linear_filters_qualities_;
|
||||
}
|
||||
|
||||
void Dump(const std::unique_ptr<ApmDataDumper>& data_dumper) const;
|
||||
|
||||
private:
|
||||
void UpdateQualityEstimates();
|
||||
|
||||
class ErleInstantaneous {
|
||||
public:
|
||||
ErleInstantaneous();
|
||||
explicit ErleInstantaneous(const EchoCanceller3Config::Erle& config);
|
||||
~ErleInstantaneous();
|
||||
|
||||
// Updates the estimator with a new point, returns true
|
||||
@ -64,14 +70,25 @@ class FullBandErleEstimator {
|
||||
// Gets an indication between 0 and 1 of the performance of the linear
|
||||
// filter for the current time instant.
|
||||
absl::optional<float> GetQualityEstimate() const {
|
||||
return erle_log2_ ? absl::optional<float>(inst_quality_estimate_)
|
||||
: absl::nullopt;
|
||||
if (erle_log2_) {
|
||||
float value = inst_quality_estimate_;
|
||||
if (clamp_inst_quality_to_zero_) {
|
||||
value = std::max(0.f, value);
|
||||
}
|
||||
if (clamp_inst_quality_to_one_) {
|
||||
value = std::min(1.f, value);
|
||||
}
|
||||
return absl::optional<float>(value);
|
||||
}
|
||||
return absl::nullopt;
|
||||
}
|
||||
void Dump(const std::unique_ptr<ApmDataDumper>& data_dumper) const;
|
||||
|
||||
private:
|
||||
void UpdateMaxMin();
|
||||
void UpdateQualityEstimate();
|
||||
const bool clamp_inst_quality_to_zero_;
|
||||
const bool clamp_inst_quality_to_one_;
|
||||
absl::optional<float> erle_log2_;
|
||||
float inst_quality_estimate_;
|
||||
float max_erle_log2_;
|
||||
@ -86,6 +103,7 @@ class FullBandErleEstimator {
|
||||
const float min_erle_log2_;
|
||||
const float max_erle_lf_log2;
|
||||
ErleInstantaneous instantaneous_erle_;
|
||||
std::vector<absl::optional<float>> linear_filters_qualities_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
@ -12,26 +12,43 @@
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
ReverbModelEstimator::ReverbModelEstimator(const EchoCanceller3Config& config)
|
||||
: reverb_decay_estimator_(config) {}
|
||||
ReverbModelEstimator::ReverbModelEstimator(const EchoCanceller3Config& config,
|
||||
size_t num_capture_channels)
|
||||
: reverb_decay_estimators_(num_capture_channels),
|
||||
reverb_frequency_responses_(num_capture_channels) {
|
||||
for (size_t ch = 0; ch < reverb_decay_estimators_.size(); ++ch) {
|
||||
reverb_decay_estimators_[ch] =
|
||||
std::make_unique<ReverbDecayEstimator>(config);
|
||||
}
|
||||
}
|
||||
|
||||
ReverbModelEstimator::~ReverbModelEstimator() = default;
|
||||
|
||||
void ReverbModelEstimator::Update(
|
||||
rtc::ArrayView<const float> impulse_response,
|
||||
const std::vector<std::array<float, kFftLengthBy2Plus1>>&
|
||||
frequency_response,
|
||||
const absl::optional<float>& linear_filter_quality,
|
||||
int filter_delay_blocks,
|
||||
bool usable_linear_estimate,
|
||||
rtc::ArrayView<const std::vector<float>> impulse_responses,
|
||||
rtc::ArrayView<const std::vector<std::array<float, kFftLengthBy2Plus1>>>
|
||||
frequency_responses,
|
||||
rtc::ArrayView<const absl::optional<float>> linear_filter_qualities,
|
||||
rtc::ArrayView<const int> filter_delays_blocks,
|
||||
const std::vector<bool>& usable_linear_estimates,
|
||||
bool stationary_block) {
|
||||
// Estimate the frequency response for the reverb.
|
||||
reverb_frequency_response_.Update(frequency_response, filter_delay_blocks,
|
||||
linear_filter_quality, stationary_block);
|
||||
const size_t num_capture_channels = reverb_decay_estimators_.size();
|
||||
RTC_DCHECK_EQ(num_capture_channels, impulse_responses.size());
|
||||
RTC_DCHECK_EQ(num_capture_channels, frequency_responses.size());
|
||||
RTC_DCHECK_EQ(num_capture_channels, usable_linear_estimates.size());
|
||||
|
||||
// Estimate the reverb decay,
|
||||
reverb_decay_estimator_.Update(impulse_response, linear_filter_quality,
|
||||
filter_delay_blocks, usable_linear_estimate,
|
||||
stationary_block);
|
||||
for (size_t ch = 0; ch < num_capture_channels; ++ch) {
|
||||
// Estimate the frequency response for the reverb.
|
||||
reverb_frequency_responses_[ch].Update(
|
||||
frequency_responses[ch], filter_delays_blocks[ch],
|
||||
linear_filter_qualities[ch], stationary_block);
|
||||
|
||||
// Estimate the reverb decay,
|
||||
reverb_decay_estimators_[ch]->Update(
|
||||
impulse_responses[ch], linear_filter_qualities[ch],
|
||||
filter_delays_blocks[ch], usable_linear_estimates[ch],
|
||||
stationary_block);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
@ -28,34 +28,38 @@ class ApmDataDumper;
|
||||
// Class for estimating the model parameters for the reverberant echo.
|
||||
class ReverbModelEstimator {
|
||||
public:
|
||||
explicit ReverbModelEstimator(const EchoCanceller3Config& config);
|
||||
ReverbModelEstimator(const EchoCanceller3Config& config,
|
||||
size_t num_capture_channels);
|
||||
~ReverbModelEstimator();
|
||||
|
||||
// Updates the estimates based on new data.
|
||||
void Update(rtc::ArrayView<const float> impulse_response,
|
||||
const std::vector<std::array<float, kFftLengthBy2Plus1>>&
|
||||
frequency_response,
|
||||
const absl::optional<float>& linear_filter_quality,
|
||||
int filter_delay_blocks,
|
||||
bool usable_linear_estimate,
|
||||
bool stationary_block);
|
||||
void Update(
|
||||
rtc::ArrayView<const std::vector<float>> impulse_responses,
|
||||
rtc::ArrayView<const std::vector<std::array<float, kFftLengthBy2Plus1>>>
|
||||
frequency_responses,
|
||||
rtc::ArrayView<const absl::optional<float>> linear_filter_qualities,
|
||||
rtc::ArrayView<const int> filter_delays_blocks,
|
||||
const std::vector<bool>& usable_linear_estimates,
|
||||
bool stationary_block);
|
||||
|
||||
// Returns the exponential decay of the reverberant echo.
|
||||
float ReverbDecay() const { return reverb_decay_estimator_.Decay(); }
|
||||
// TODO(peah): Correct to properly support multiple channels.
|
||||
float ReverbDecay() const { return reverb_decay_estimators_[0]->Decay(); }
|
||||
|
||||
// Return the frequency response of the reverberant echo.
|
||||
// TODO(peah): Correct to properly support multiple channels.
|
||||
rtc::ArrayView<const float> GetReverbFrequencyResponse() const {
|
||||
return reverb_frequency_response_.FrequencyResponse();
|
||||
return reverb_frequency_responses_[0].FrequencyResponse();
|
||||
}
|
||||
|
||||
// Dumps debug data.
|
||||
void Dump(ApmDataDumper* data_dumper) const {
|
||||
reverb_decay_estimator_.Dump(data_dumper);
|
||||
reverb_decay_estimators_[0]->Dump(data_dumper);
|
||||
}
|
||||
|
||||
private:
|
||||
ReverbDecayEstimator reverb_decay_estimator_;
|
||||
ReverbFrequencyResponse reverb_frequency_response_;
|
||||
std::vector<std::unique_ptr<ReverbDecayEstimator>> reverb_decay_estimators_;
|
||||
std::vector<ReverbFrequencyResponse> reverb_frequency_responses_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
@ -11,8 +11,10 @@
|
||||
#include "modules/audio_processing/aec3/reverb_model_estimator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/array_view.h"
|
||||
@ -25,14 +27,32 @@
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
EchoCanceller3Config CreateConfigForTest(float default_decay) {
|
||||
EchoCanceller3Config cfg;
|
||||
cfg.ep_strength.default_len = default_decay;
|
||||
cfg.filter.main.length_blocks = 40;
|
||||
return cfg;
|
||||
}
|
||||
|
||||
constexpr int kFilterDelayBlocks = 2;
|
||||
|
||||
} // namespace
|
||||
|
||||
class ReverbModelEstimatorTest {
|
||||
public:
|
||||
explicit ReverbModelEstimatorTest(float default_decay)
|
||||
: default_decay_(default_decay), estimated_decay_(default_decay) {
|
||||
aec3_config_.ep_strength.default_len = default_decay_;
|
||||
aec3_config_.filter.main.length_blocks = 40;
|
||||
h_.resize(aec3_config_.filter.main.length_blocks * kBlockSize);
|
||||
H2_.resize(aec3_config_.filter.main.length_blocks);
|
||||
ReverbModelEstimatorTest(float default_decay, size_t num_capture_channels)
|
||||
: aec3_config_(CreateConfigForTest(default_decay)),
|
||||
estimated_decay_(default_decay),
|
||||
h_(num_capture_channels,
|
||||
std::vector<float>(
|
||||
aec3_config_.filter.main.length_blocks * kBlockSize,
|
||||
0.f)),
|
||||
H2_(num_capture_channels,
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>>(
|
||||
aec3_config_.filter.main.length_blocks)),
|
||||
quality_linear_(num_capture_channels, 1.0f) {
|
||||
CreateImpulseResponseWithDecay();
|
||||
}
|
||||
void RunEstimator();
|
||||
@ -43,51 +63,63 @@ class ReverbModelEstimatorTest {
|
||||
|
||||
private:
|
||||
void CreateImpulseResponseWithDecay();
|
||||
|
||||
absl::optional<float> quality_linear_ = 1.0f;
|
||||
static constexpr int kFilterDelayBlocks = 2;
|
||||
static constexpr bool kUsableLinearEstimate = true;
|
||||
static constexpr bool kStationaryBlock = false;
|
||||
static constexpr float kTruePowerDecay = 0.5f;
|
||||
EchoCanceller3Config aec3_config_;
|
||||
float default_decay_;
|
||||
const EchoCanceller3Config aec3_config_;
|
||||
float estimated_decay_;
|
||||
float estimated_power_tail_ = 0.f;
|
||||
float true_power_tail_ = 0.f;
|
||||
std::vector<float> h_;
|
||||
std::vector<std::array<float, kFftLengthBy2Plus1>> H2_;
|
||||
std::vector<std::vector<float>> h_;
|
||||
std::vector<std::vector<std::array<float, kFftLengthBy2Plus1>>> H2_;
|
||||
std::vector<absl::optional<float>> quality_linear_;
|
||||
};
|
||||
|
||||
void ReverbModelEstimatorTest::CreateImpulseResponseWithDecay() {
|
||||
const Aec3Fft fft;
|
||||
RTC_DCHECK_EQ(h_.size(), aec3_config_.filter.main.length_blocks * kBlockSize);
|
||||
RTC_DCHECK_EQ(H2_.size(), aec3_config_.filter.main.length_blocks);
|
||||
for (const auto& h_k : h_) {
|
||||
RTC_DCHECK_EQ(h_k.size(),
|
||||
aec3_config_.filter.main.length_blocks * kBlockSize);
|
||||
}
|
||||
for (const auto& H2_k : H2_) {
|
||||
RTC_DCHECK_EQ(H2_k.size(), aec3_config_.filter.main.length_blocks);
|
||||
}
|
||||
RTC_DCHECK_EQ(kFilterDelayBlocks, 2);
|
||||
|
||||
float decay_sample = std::sqrt(powf(kTruePowerDecay, 1.f / kBlockSize));
|
||||
const size_t filter_delay_coefficients = kFilterDelayBlocks * kBlockSize;
|
||||
std::fill(h_.begin(), h_.end(), 0.f);
|
||||
h_[filter_delay_coefficients] = 1.f;
|
||||
for (size_t k = filter_delay_coefficients + 1; k < h_.size(); ++k) {
|
||||
h_[k] = h_[k - 1] * decay_sample;
|
||||
for (auto& h_i : h_) {
|
||||
std::fill(h_i.begin(), h_i.end(), 0.f);
|
||||
h_i[filter_delay_coefficients] = 1.f;
|
||||
for (size_t k = filter_delay_coefficients + 1; k < h_i.size(); ++k) {
|
||||
h_i[k] = h_i[k - 1] * decay_sample;
|
||||
}
|
||||
}
|
||||
|
||||
std::array<float, kFftLength> fft_data;
|
||||
FftData H_j;
|
||||
for (size_t j = 0, k = 0; j < H2_.size(); ++j, k += kBlockSize) {
|
||||
fft_data.fill(0.f);
|
||||
std::copy(h_.begin() + k, h_.begin() + k + kBlockSize, fft_data.begin());
|
||||
fft.Fft(&fft_data, &H_j);
|
||||
H_j.Spectrum(Aec3Optimization::kNone, H2_[j]);
|
||||
for (size_t ch = 0; ch < H2_.size(); ++ch) {
|
||||
for (size_t j = 0, k = 0; j < H2_[ch].size(); ++j, k += kBlockSize) {
|
||||
std::array<float, kFftLength> fft_data;
|
||||
fft_data.fill(0.f);
|
||||
std::copy(h_[ch].begin() + k, h_[ch].begin() + k + kBlockSize,
|
||||
fft_data.begin());
|
||||
FftData H_j;
|
||||
fft.Fft(&fft_data, &H_j);
|
||||
H_j.Spectrum(Aec3Optimization::kNone, H2_[ch][j]);
|
||||
}
|
||||
}
|
||||
rtc::ArrayView<float> H2_tail(H2_[H2_.size() - 1]);
|
||||
rtc::ArrayView<float> H2_tail(H2_[0][H2_[0].size() - 1]);
|
||||
true_power_tail_ = std::accumulate(H2_tail.begin(), H2_tail.end(), 0.f);
|
||||
}
|
||||
void ReverbModelEstimatorTest::RunEstimator() {
|
||||
ReverbModelEstimator estimator(aec3_config_);
|
||||
const size_t num_capture_channels = H2_.size();
|
||||
constexpr bool kUsableLinearEstimate = true;
|
||||
ReverbModelEstimator estimator(aec3_config_, num_capture_channels);
|
||||
std::vector<bool> usable_linear_estimates(num_capture_channels,
|
||||
kUsableLinearEstimate);
|
||||
std::vector<int> filter_delay_blocks(num_capture_channels,
|
||||
kFilterDelayBlocks);
|
||||
for (size_t k = 0; k < 3000; ++k) {
|
||||
estimator.Update(h_, H2_, quality_linear_, kFilterDelayBlocks,
|
||||
kUsableLinearEstimate, kStationaryBlock);
|
||||
estimator.Update(h_, H2_, quality_linear_, filter_delay_blocks,
|
||||
usable_linear_estimates, kStationaryBlock);
|
||||
}
|
||||
estimated_decay_ = estimator.ReverbDecay();
|
||||
auto freq_resp_tail = estimator.GetReverbFrequencyResponse();
|
||||
@ -96,19 +128,23 @@ void ReverbModelEstimatorTest::RunEstimator() {
|
||||
}
|
||||
|
||||
TEST(ReverbModelEstimatorTests, NotChangingDecay) {
|
||||
constexpr float default_decay = 0.9f;
|
||||
ReverbModelEstimatorTest test(default_decay);
|
||||
test.RunEstimator();
|
||||
EXPECT_EQ(test.GetDecay(), default_decay);
|
||||
EXPECT_NEAR(test.GetPowerTailDb(), test.GetTruePowerTailDb(), 5.f);
|
||||
constexpr float kDefaultDecay = 0.9f;
|
||||
for (size_t num_capture_channels : {1, 2, 4, 8}) {
|
||||
ReverbModelEstimatorTest test(kDefaultDecay, num_capture_channels);
|
||||
test.RunEstimator();
|
||||
EXPECT_EQ(test.GetDecay(), kDefaultDecay);
|
||||
EXPECT_NEAR(test.GetPowerTailDb(), test.GetTruePowerTailDb(), 5.f);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ReverbModelEstimatorTests, ChangingDecay) {
|
||||
constexpr float default_decay = -0.9f;
|
||||
ReverbModelEstimatorTest test(default_decay);
|
||||
test.RunEstimator();
|
||||
EXPECT_NEAR(test.GetDecay(), test.GetTrueDecay(), 0.1);
|
||||
EXPECT_NEAR(test.GetPowerTailDb(), test.GetTruePowerTailDb(), 5.f);
|
||||
constexpr float kDefaultDecay = -0.9f;
|
||||
for (size_t num_capture_channels : {1, 2, 4, 8}) {
|
||||
ReverbModelEstimatorTest test(kDefaultDecay, num_capture_channels);
|
||||
test.RunEstimator();
|
||||
EXPECT_NEAR(test.GetDecay(), test.GetTrueDecay(), 0.1);
|
||||
EXPECT_NEAR(test.GetPowerTailDb(), test.GetTruePowerTailDb(), 5.f);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
Reference in New Issue
Block a user