/* * 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/adaptive_digital_gain_applier.h" #include #include "common_audio/include/audio_util.h" #include "modules/audio_processing/agc2/agc2_common.h" #include "modules/audio_processing/agc2/vector_float_frame.h" #include "modules/audio_processing/logging/apm_data_dumper.h" #include "rtc_base/gunit.h" namespace webrtc { namespace { // Constants used in place of estimated noise levels. constexpr float kNoNoiseDbfs = -90.f; constexpr float kWithNoiseDbfs = -20.f; constexpr VadWithLevel::LevelAndProbability kVadSpeech(1.f, -20.f, 0.f); // Runs gain applier and returns the applied gain in linear scale. float RunOnConstantLevel(int num_iterations, VadWithLevel::LevelAndProbability vad_data, float input_level_dbfs, AdaptiveDigitalGainApplier* gain_applier) { float gain_linear = 0.f; for (int i = 0; i < num_iterations; ++i) { VectorFloatFrame fake_audio(1, 1, 1.f); SignalWithLevels signal_with_levels(fake_audio.float_frame_view()); signal_with_levels.input_level_dbfs = input_level_dbfs; signal_with_levels.input_noise_level_dbfs = kNoNoiseDbfs; signal_with_levels.vad_result = vad_data; signal_with_levels.limiter_audio_level_dbfs = -2.f; signal_with_levels.estimate_is_confident = true; gain_applier->Process(signal_with_levels); gain_linear = fake_audio.float_frame_view().channel(0)[0]; } return gain_linear; } // Returns 'SignalWithLevels' for typical GainApplier behavior. Voice on, no // noise, low limiter, confident level. SignalWithLevels TestSignalWithLevel(AudioFrameView float_frame) { SignalWithLevels result(float_frame); result.input_level_dbfs = -1; result.input_noise_level_dbfs = kNoNoiseDbfs; result.vad_result = kVadSpeech; result.estimate_is_confident = true; result.limiter_audio_level_dbfs = -2.f; return result; } } // namespace TEST(AutomaticGainController2AdaptiveGainApplier, GainApplierShouldNotCrash) { static_assert( std::is_trivially_destructible::value, ""); ApmDataDumper apm_data_dumper(0); AdaptiveDigitalGainApplier gain_applier(&apm_data_dumper); // Make one call with reasonable audio level values and settings. VectorFloatFrame fake_audio(2, 480, 10000.f); auto signal_with_level = TestSignalWithLevel(fake_audio.float_frame_view()); signal_with_level.input_level_dbfs = -5.0; gain_applier.Process(signal_with_level); } // Check that the output is -kHeadroom dBFS. TEST(AutomaticGainController2AdaptiveGainApplier, TargetLevelIsReached) { ApmDataDumper apm_data_dumper(0); AdaptiveDigitalGainApplier gain_applier(&apm_data_dumper); constexpr float initial_level_dbfs = -5.f; const float applied_gain = RunOnConstantLevel(200, kVadSpeech, initial_level_dbfs, &gain_applier); EXPECT_NEAR(applied_gain, DbToRatio(-kHeadroomDbfs - initial_level_dbfs), 0.1f); } // Check that the output is -kHeadroom dBFS TEST(AutomaticGainController2AdaptiveGainApplier, GainApproachesMaxGain) { ApmDataDumper apm_data_dumper(0); AdaptiveDigitalGainApplier gain_applier(&apm_data_dumper); constexpr float initial_level_dbfs = -kHeadroomDbfs - kMaxGainDb - 10.f; // A few extra frames for safety. constexpr int kNumFramesToAdapt = static_cast(kMaxGainDb / kMaxGainChangePerFrameDb) + 10; const float applied_gain = RunOnConstantLevel( kNumFramesToAdapt, kVadSpeech, initial_level_dbfs, &gain_applier); EXPECT_NEAR(applied_gain, DbToRatio(kMaxGainDb), 0.1f); const float applied_gain_db = 20.f * std::log10(applied_gain); EXPECT_NEAR(applied_gain_db, kMaxGainDb, 0.1f); } TEST(AutomaticGainController2AdaptiveGainApplier, GainDoesNotChangeFast) { ApmDataDumper apm_data_dumper(0); AdaptiveDigitalGainApplier gain_applier(&apm_data_dumper); constexpr float initial_level_dbfs = -25.f; // A few extra frames for safety. constexpr int kNumFramesToAdapt = static_cast(initial_level_dbfs / kMaxGainChangePerFrameDb) + 10; const float kMaxChangePerFrameLinear = DbToRatio(kMaxGainChangePerFrameDb); float last_gain_linear = 1.f; for (int i = 0; i < kNumFramesToAdapt; ++i) { SCOPED_TRACE(i); VectorFloatFrame fake_audio(1, 1, 1.f); auto signal_with_level = TestSignalWithLevel(fake_audio.float_frame_view()); signal_with_level.input_level_dbfs = initial_level_dbfs; gain_applier.Process(signal_with_level); float current_gain_linear = fake_audio.float_frame_view().channel(0)[0]; EXPECT_LE(std::abs(current_gain_linear - last_gain_linear), kMaxChangePerFrameLinear); last_gain_linear = current_gain_linear; } // Check that the same is true when gain decreases as well. for (int i = 0; i < kNumFramesToAdapt; ++i) { SCOPED_TRACE(i); VectorFloatFrame fake_audio(1, 1, 1.f); auto signal_with_level = TestSignalWithLevel(fake_audio.float_frame_view()); signal_with_level.input_level_dbfs = 0.f; gain_applier.Process(signal_with_level); float current_gain_linear = fake_audio.float_frame_view().channel(0)[0]; EXPECT_LE(std::abs(current_gain_linear - last_gain_linear), kMaxChangePerFrameLinear); last_gain_linear = current_gain_linear; } } TEST(AutomaticGainController2AdaptiveGainApplier, GainIsRampedInAFrame) { ApmDataDumper apm_data_dumper(0); AdaptiveDigitalGainApplier gain_applier(&apm_data_dumper); constexpr float initial_level_dbfs = -25.f; constexpr int num_samples = 480; VectorFloatFrame fake_audio(1, num_samples, 1.f); auto signal_with_level = TestSignalWithLevel(fake_audio.float_frame_view()); signal_with_level.input_level_dbfs = initial_level_dbfs; gain_applier.Process(signal_with_level); float maximal_difference = 0.f; float current_value = 1.f * DbToRatio(kInitialAdaptiveDigitalGainDb); for (const auto& x : fake_audio.float_frame_view().channel(0)) { const float difference = std::abs(x - current_value); maximal_difference = std::max(maximal_difference, difference); current_value = x; } const float kMaxChangePerFrameLinear = DbToRatio(kMaxGainChangePerFrameDb); const float kMaxChangePerSample = kMaxChangePerFrameLinear / num_samples; EXPECT_LE(maximal_difference, kMaxChangePerSample); } TEST(AutomaticGainController2AdaptiveGainApplier, NoiseLimitsGain) { ApmDataDumper apm_data_dumper(0); AdaptiveDigitalGainApplier gain_applier(&apm_data_dumper); constexpr float initial_level_dbfs = -25.f; constexpr int num_samples = 480; constexpr int num_initial_frames = kInitialAdaptiveDigitalGainDb / kMaxGainChangePerFrameDb; constexpr int num_frames = 50; ASSERT_GT(kWithNoiseDbfs, kMaxNoiseLevelDbfs) << "kWithNoiseDbfs is too low"; for (int i = 0; i < num_initial_frames + num_frames; ++i) { VectorFloatFrame fake_audio(1, num_samples, 1.f); auto signal_with_level = TestSignalWithLevel(fake_audio.float_frame_view()); signal_with_level.input_level_dbfs = initial_level_dbfs; signal_with_level.input_noise_level_dbfs = kWithNoiseDbfs; gain_applier.Process(signal_with_level); // Wait so that the adaptive gain applier has time to lower the gain. if (i > num_initial_frames) { const float maximal_ratio = *std::max_element(fake_audio.float_frame_view().channel(0).begin(), fake_audio.float_frame_view().channel(0).end()); EXPECT_NEAR(maximal_ratio, 1.f, 0.001f); } } } TEST(AutomaticGainController2GainApplier, CanHandlePositiveSpeechLevels) { ApmDataDumper apm_data_dumper(0); AdaptiveDigitalGainApplier gain_applier(&apm_data_dumper); // Make one call with positive audio level values and settings. VectorFloatFrame fake_audio(2, 480, 10000.f); auto signal_with_level = TestSignalWithLevel(fake_audio.float_frame_view()); signal_with_level.input_level_dbfs = 5.0f; gain_applier.Process(signal_with_level); } TEST(AutomaticGainController2GainApplier, AudioLevelLimitsGain) { ApmDataDumper apm_data_dumper(0); AdaptiveDigitalGainApplier gain_applier(&apm_data_dumper); constexpr float initial_level_dbfs = -25.f; constexpr int num_samples = 480; constexpr int num_initial_frames = kInitialAdaptiveDigitalGainDb / kMaxGainChangePerFrameDb; constexpr int num_frames = 50; ASSERT_GT(kWithNoiseDbfs, kMaxNoiseLevelDbfs) << "kWithNoiseDbfs is too low"; for (int i = 0; i < num_initial_frames + num_frames; ++i) { VectorFloatFrame fake_audio(1, num_samples, 1.f); auto signal_with_level = TestSignalWithLevel(fake_audio.float_frame_view()); signal_with_level.input_level_dbfs = initial_level_dbfs; signal_with_level.limiter_audio_level_dbfs = 1.f; signal_with_level.estimate_is_confident = false; gain_applier.Process(signal_with_level); // Wait so that the adaptive gain applier has time to lower the gain. if (i > num_initial_frames) { const float maximal_ratio = *std::max_element(fake_audio.float_frame_view().channel(0).begin(), fake_audio.float_frame_view().channel(0).end()); EXPECT_NEAR(maximal_ratio, 1.f, 0.001f); } } } } // namespace webrtc