| /* | |
| * 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/agc2/saturation_protector.h" | |
| #include <algorithm> | |
| #include <cmath> | |
| #include "modules/audio_processing/agc2/agc2_common.h" | |
| #include "modules/audio_processing/logging/apm_data_dumper.h" | |
| #include "test/gtest.h" | |
| namespace webrtc { | |
| namespace { | |
| constexpr float kInitialHeadroomDb = 20.0f; | |
| constexpr int kNoAdjacentSpeechFramesRequired = 1; | |
| constexpr float kMaxSpeechProbability = 1.0f; | |
| // Calls `Analyze(speech_probability, peak_dbfs, speech_level_dbfs)` | |
| // `num_iterations` times on `saturation_protector` and return the largest | |
| // headroom difference between two consecutive calls. | |
| float RunOnConstantLevel(int num_iterations, | |
| float speech_probability, | |
| float peak_dbfs, | |
| float speech_level_dbfs, | |
| SaturationProtector& saturation_protector) { | |
| float last_headroom = saturation_protector.HeadroomDb(); | |
| float max_difference = 0.0f; | |
| for (int i = 0; i < num_iterations; ++i) { | |
| saturation_protector.Analyze(speech_probability, peak_dbfs, | |
| speech_level_dbfs); | |
| const float new_headroom = saturation_protector.HeadroomDb(); | |
| max_difference = | |
| std::max(max_difference, std::fabs(new_headroom - last_headroom)); | |
| last_headroom = new_headroom; | |
| } | |
| return max_difference; | |
| } | |
| // Checks that the returned headroom value is correctly reset. | |
| TEST(GainController2SaturationProtector, Reset) { | |
| ApmDataDumper apm_data_dumper(0); | |
| auto saturation_protector = CreateSaturationProtector( | |
| kInitialHeadroomDb, kNoAdjacentSpeechFramesRequired, &apm_data_dumper); | |
| const float initial_headroom_db = saturation_protector->HeadroomDb(); | |
| RunOnConstantLevel(/*num_iterations=*/10, kMaxSpeechProbability, | |
| /*peak_dbfs=*/0.0f, | |
| /*speech_level_dbfs=*/-10.0f, *saturation_protector); | |
| // Make sure that there are side-effects. | |
| ASSERT_NE(initial_headroom_db, saturation_protector->HeadroomDb()); | |
| saturation_protector->Reset(); | |
| EXPECT_EQ(initial_headroom_db, saturation_protector->HeadroomDb()); | |
| } | |
| // Checks that the estimate converges to the ratio between peaks and level | |
| // estimator values after a while. | |
| TEST(GainController2SaturationProtector, EstimatesCrestRatio) { | |
| constexpr int kNumIterations = 2000; | |
| constexpr float kPeakLevelDbfs = -20.0f; | |
| constexpr float kCrestFactorDb = kInitialHeadroomDb + 1.0f; | |
| constexpr float kSpeechLevelDbfs = kPeakLevelDbfs - kCrestFactorDb; | |
| const float kMaxDifferenceDb = | |
| 0.5f * std::fabs(kInitialHeadroomDb - kCrestFactorDb); | |
| ApmDataDumper apm_data_dumper(0); | |
| auto saturation_protector = CreateSaturationProtector( | |
| kInitialHeadroomDb, kNoAdjacentSpeechFramesRequired, &apm_data_dumper); | |
| RunOnConstantLevel(kNumIterations, kMaxSpeechProbability, kPeakLevelDbfs, | |
| kSpeechLevelDbfs, *saturation_protector); | |
| EXPECT_NEAR(saturation_protector->HeadroomDb(), kCrestFactorDb, | |
| kMaxDifferenceDb); | |
| } | |
| // Checks that the headroom does not change too quickly. | |
| TEST(GainController2SaturationProtector, ChangeSlowly) { | |
| constexpr int kNumIterations = 1000; | |
| constexpr float kPeakLevelDbfs = -20.f; | |
| constexpr float kCrestFactorDb = kInitialHeadroomDb - 5.f; | |
| constexpr float kOtherCrestFactorDb = kInitialHeadroomDb; | |
| constexpr float kSpeechLevelDbfs = kPeakLevelDbfs - kCrestFactorDb; | |
| constexpr float kOtherSpeechLevelDbfs = kPeakLevelDbfs - kOtherCrestFactorDb; | |
| ApmDataDumper apm_data_dumper(0); | |
| auto saturation_protector = CreateSaturationProtector( | |
| kInitialHeadroomDb, kNoAdjacentSpeechFramesRequired, &apm_data_dumper); | |
| float max_difference_db = | |
| RunOnConstantLevel(kNumIterations, kMaxSpeechProbability, kPeakLevelDbfs, | |
| kSpeechLevelDbfs, *saturation_protector); | |
| max_difference_db = std::max( | |
| RunOnConstantLevel(kNumIterations, kMaxSpeechProbability, kPeakLevelDbfs, | |
| kOtherSpeechLevelDbfs, *saturation_protector), | |
| max_difference_db); | |
| constexpr float kMaxChangeSpeedDbPerSecond = 0.5f; // 1 db / 2 seconds. | |
| EXPECT_LE(max_difference_db, | |
| kMaxChangeSpeedDbPerSecond / 1000 * kFrameDurationMs); | |
| } | |
| class SaturationProtectorParametrization | |
| : public ::testing::TestWithParam<int> { | |
| protected: | |
| int adjacent_speech_frames_threshold() const { return GetParam(); } | |
| }; | |
| TEST_P(SaturationProtectorParametrization, DoNotAdaptToShortSpeechSegments) { | |
| ApmDataDumper apm_data_dumper(0); | |
| auto saturation_protector = CreateSaturationProtector( | |
| kInitialHeadroomDb, adjacent_speech_frames_threshold(), &apm_data_dumper); | |
| const float initial_headroom_db = saturation_protector->HeadroomDb(); | |
| RunOnConstantLevel(/*num_iterations=*/adjacent_speech_frames_threshold() - 1, | |
| kMaxSpeechProbability, | |
| /*peak_dbfs=*/0.0f, | |
| /*speech_level_dbfs=*/-10.0f, *saturation_protector); | |
| // No adaptation expected. | |
| EXPECT_EQ(initial_headroom_db, saturation_protector->HeadroomDb()); | |
| } | |
| TEST_P(SaturationProtectorParametrization, AdaptToEnoughSpeechSegments) { | |
| ApmDataDumper apm_data_dumper(0); | |
| auto saturation_protector = CreateSaturationProtector( | |
| kInitialHeadroomDb, adjacent_speech_frames_threshold(), &apm_data_dumper); | |
| const float initial_headroom_db = saturation_protector->HeadroomDb(); | |
| RunOnConstantLevel(/*num_iterations=*/adjacent_speech_frames_threshold() + 1, | |
| kMaxSpeechProbability, | |
| /*peak_dbfs=*/0.0f, | |
| /*speech_level_dbfs=*/-10.0f, *saturation_protector); | |
| // Adaptation expected. | |
| EXPECT_NE(initial_headroom_db, saturation_protector->HeadroomDb()); | |
| } | |
| INSTANTIATE_TEST_SUITE_P(GainController2, | |
| SaturationProtectorParametrization, | |
| ::testing::Values(2, 9, 17)); | |
| } // namespace | |
| } // namespace webrtc |