/* * 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 #include "modules/audio_processing/agc2/agc2_common.h" #include "modules/audio_processing/logging/apm_data_dumper.h" #include "rtc_base/gunit.h" namespace webrtc { namespace { float RunOnConstantLevel(int num_iterations, VadWithLevel::LevelAndProbability vad_data, float estimated_level_dbfs, SaturationProtector* saturation_protector) { float last_margin = saturation_protector->LastMargin(); float max_difference = 0.f; for (int i = 0; i < num_iterations; ++i) { saturation_protector->UpdateMargin(vad_data, estimated_level_dbfs); const float new_margin = saturation_protector->LastMargin(); max_difference = std::max(max_difference, std::abs(new_margin - last_margin)); last_margin = new_margin; saturation_protector->DebugDumpEstimate(); } return max_difference; } } // namespace TEST(AutomaticGainController2SaturationProtector, ProtectorShouldNotCrash) { ApmDataDumper apm_data_dumper(0); SaturationProtector saturation_protector(&apm_data_dumper); VadWithLevel::LevelAndProbability vad_data(1.f, -20.f, -10.f); saturation_protector.UpdateMargin(vad_data, -20.f); static_cast(saturation_protector.LastMargin()); saturation_protector.DebugDumpEstimate(); } // Check that the estimate converges to the ratio between peaks and // level estimator values after a while. TEST(AutomaticGainController2SaturationProtector, ProtectorEstimatesCrestRatio) { ApmDataDumper apm_data_dumper(0); SaturationProtector saturation_protector(&apm_data_dumper); constexpr float kPeakLevel = -20.f; const float kCrestFactor = GetInitialSaturationMarginDb() + 1.f; const float kSpeechLevel = kPeakLevel - kCrestFactor; const float kMaxDifference = 0.5 * std::abs(GetInitialSaturationMarginDb() - kCrestFactor); static_cast(RunOnConstantLevel( 2000, VadWithLevel::LevelAndProbability(1.f, -90.f, kPeakLevel), kSpeechLevel, &saturation_protector)); EXPECT_NEAR( saturation_protector.LastMargin() - GetExtraSaturationMarginOffsetDb(), kCrestFactor, kMaxDifference); } TEST(AutomaticGainController2SaturationProtector, ProtectorChangesSlowly) { ApmDataDumper apm_data_dumper(0); SaturationProtector saturation_protector(&apm_data_dumper); constexpr float kPeakLevel = -20.f; const float kCrestFactor = GetInitialSaturationMarginDb() - 5.f; const float kOtherCrestFactor = GetInitialSaturationMarginDb(); const float kSpeechLevel = kPeakLevel - kCrestFactor; const float kOtherSpeechLevel = kPeakLevel - kOtherCrestFactor; constexpr int kNumIterations = 1000; float max_difference = RunOnConstantLevel( kNumIterations, VadWithLevel::LevelAndProbability(1.f, -90.f, kPeakLevel), kSpeechLevel, &saturation_protector); max_difference = std::max(RunOnConstantLevel( kNumIterations, VadWithLevel::LevelAndProbability(1.f, -90.f, kPeakLevel), kOtherSpeechLevel, &saturation_protector), max_difference); constexpr float kMaxChangeSpeedDbPerSecond = 0.5; // 1 db / 2 seconds. EXPECT_LE(max_difference, kMaxChangeSpeedDbPerSecond / 1000 * kFrameDurationMs); } TEST(AutomaticGainController2SaturationProtector, ProtectorAdaptsToDelayedChanges) { ApmDataDumper apm_data_dumper(0); SaturationProtector saturation_protector(&apm_data_dumper); constexpr int kDelayIterations = kFullBufferSizeMs / kFrameDurationMs; constexpr float kInitialSpeechLevelDbfs = -30; constexpr float kLaterSpeechLevelDbfs = -15; // First run on initial level. float max_difference = RunOnConstantLevel( kDelayIterations, VadWithLevel::LevelAndProbability( 1.f, -90.f, kInitialSpeechLevelDbfs + GetInitialSaturationMarginDb()), kInitialSpeechLevelDbfs, &saturation_protector); // Then peak changes, but not RMS. max_difference = std::max(RunOnConstantLevel( kDelayIterations, VadWithLevel::LevelAndProbability( 1.f, -90.f, kLaterSpeechLevelDbfs + GetInitialSaturationMarginDb()), kInitialSpeechLevelDbfs, &saturation_protector), max_difference); // Then both change. max_difference = std::max(RunOnConstantLevel( kDelayIterations, VadWithLevel::LevelAndProbability( 1.f, -90.f, kLaterSpeechLevelDbfs + GetInitialSaturationMarginDb()), kLaterSpeechLevelDbfs, &saturation_protector), max_difference); // The saturation protector expects that the RMS changes roughly // 'kFullBufferSizeMs' after peaks change. This is to account for // delay introduces by the level estimator. Therefore, the input // above is 'normal' and 'expected', and shouldn't influence the // margin by much. const float total_difference = std::abs(saturation_protector.LastMargin() - GetExtraSaturationMarginOffsetDb() - GetInitialSaturationMarginDb()); EXPECT_LE(total_difference, 0.05f); EXPECT_LE(max_difference, 0.01f); } } // namespace webrtc