| /* |
| * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| #include "modules/audio_processing/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" |
| #include "api/audio/echo_canceller3_config.h" |
| #include "modules/audio_processing/aec3/aec3_common.h" |
| #include "modules/audio_processing/aec3/aec3_fft.h" |
| #include "modules/audio_processing/aec3/fft_data.h" |
| #include "rtc_base/checks.h" |
| #include "test/gtest.h" |
| |
| namespace webrtc { |
| |
| namespace { |
| |
| EchoCanceller3Config CreateConfigForTest(float default_decay) { |
| EchoCanceller3Config cfg; |
| cfg.ep_strength.default_len = default_decay; |
| cfg.filter.refined.length_blocks = 40; |
| return cfg; |
| } |
| |
| constexpr int kFilterDelayBlocks = 2; |
| |
| } // namespace |
| |
| class ReverbModelEstimatorTest { |
| public: |
| 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.refined.length_blocks * kBlockSize, |
| 0.f)), |
| H2_(num_capture_channels, |
| std::vector<std::array<float, kFftLengthBy2Plus1>>( |
| aec3_config_.filter.refined.length_blocks)), |
| quality_linear_(num_capture_channels, 1.0f) { |
| CreateImpulseResponseWithDecay(); |
| } |
| void RunEstimator(); |
| float GetDecay(bool mild) { |
| return mild ? mild_estimated_decay_ : estimated_decay_; |
| } |
| float GetTrueDecay() { return kTruePowerDecay; } |
| float GetPowerTailDb() { return 10.f * std::log10(estimated_power_tail_); } |
| float GetTruePowerTailDb() { return 10.f * std::log10(true_power_tail_); } |
| |
| private: |
| void CreateImpulseResponseWithDecay(); |
| static constexpr bool kStationaryBlock = false; |
| static constexpr float kTruePowerDecay = 0.5f; |
| const EchoCanceller3Config aec3_config_; |
| float estimated_decay_; |
| float mild_estimated_decay_; |
| float estimated_power_tail_ = 0.f; |
| float true_power_tail_ = 0.f; |
| 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; |
| for (const auto& h_k : h_) { |
| RTC_DCHECK_EQ(h_k.size(), |
| aec3_config_.filter.refined.length_blocks * kBlockSize); |
| } |
| for (const auto& H2_k : H2_) { |
| RTC_DCHECK_EQ(H2_k.size(), aec3_config_.filter.refined.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; |
| 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; |
| } |
| } |
| |
| 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_[0][H2_[0].size() - 1]); |
| true_power_tail_ = std::accumulate(H2_tail.begin(), H2_tail.end(), 0.f); |
| } |
| void ReverbModelEstimatorTest::RunEstimator() { |
| 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_, filter_delay_blocks, |
| usable_linear_estimates, kStationaryBlock); |
| } |
| estimated_decay_ = estimator.ReverbDecay(/*mild=*/false); |
| mild_estimated_decay_ = estimator.ReverbDecay(/*mild=*/true); |
| auto freq_resp_tail = estimator.GetReverbFrequencyResponse(); |
| estimated_power_tail_ = |
| std::accumulate(freq_resp_tail.begin(), freq_resp_tail.end(), 0.f); |
| } |
| |
| TEST(ReverbModelEstimatorTests, NotChangingDecay) { |
| 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(/*mild=*/false), kDefaultDecay); |
| EXPECT_EQ(test.GetDecay(/*mild=*/true), |
| EchoCanceller3Config().ep_strength.nearend_len); |
| EXPECT_NEAR(test.GetPowerTailDb(), test.GetTruePowerTailDb(), 5.f); |
| } |
| } |
| |
| TEST(ReverbModelEstimatorTests, ChangingDecay) { |
| 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(/*mild=*/false), test.GetTrueDecay(), 0.1f); |
| EXPECT_NEAR(test.GetDecay(/*mild=*/true), test.GetTrueDecay(), 0.1f); |
| EXPECT_NEAR(test.GetPowerTailDb(), test.GetTruePowerTailDb(), 5.f); |
| } |
| } |
| |
| } // namespace webrtc |