/* * Copyright (c) 2014 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/audio_processing_impl.h" #include #include #include #include #include "absl/types/optional.h" #include "api/make_ref_counted.h" #include "api/scoped_refptr.h" #include "modules/audio_processing/include/audio_processing.h" #include "modules/audio_processing/optionally_built_submodule_creators.h" #include "modules/audio_processing/test/audio_processing_builder_for_testing.h" #include "modules/audio_processing/test/echo_canceller_test_tools.h" #include "modules/audio_processing/test/echo_control_mock.h" #include "modules/audio_processing/test/test_utils.h" #include "rtc_base/checks.h" #include "rtc_base/random.h" #include "rtc_base/strings/string_builder.h" #include "test/field_trial.h" #include "test/gmock.h" #include "test/gtest.h" namespace webrtc { namespace { using ::testing::Invoke; using ::testing::NotNull; class MockInitialize : public AudioProcessingImpl { public: MockInitialize() : AudioProcessingImpl() {} MOCK_METHOD(void, InitializeLocked, (), (override)); void RealInitializeLocked() { AssertLockedForTest(); AudioProcessingImpl::InitializeLocked(); } MOCK_METHOD(void, AddRef, (), (const, override)); MOCK_METHOD(rtc::RefCountReleaseStatus, Release, (), (const, override)); }; // Creates MockEchoControl instances and provides a raw pointer access to // the next created one. The raw pointer is meant to be used with gmock. // Returning a pointer of the next created MockEchoControl instance is necessary // for the following reasons: (i) gmock expectations must be set before any call // occurs, (ii) APM is initialized the first time that // AudioProcessingImpl::ProcessStream() is called and the initialization leads // to the creation of a new EchoControl object. class MockEchoControlFactory : public EchoControlFactory { public: MockEchoControlFactory() : next_mock_(std::make_unique()) {} // Returns a pointer to the next MockEchoControl that this factory creates. MockEchoControl* GetNext() const { return next_mock_.get(); } std::unique_ptr Create(int sample_rate_hz, int num_render_channels, int num_capture_channels) override { std::unique_ptr mock = std::move(next_mock_); next_mock_ = std::make_unique(); return mock; } private: std::unique_ptr next_mock_; }; // Mocks EchoDetector and records the first samples of the last analyzed render // stream frame. Used to check what data is read by an EchoDetector // implementation injected into an APM. class TestEchoDetector : public EchoDetector { public: TestEchoDetector() : analyze_render_audio_called_(false), last_render_audio_first_sample_(0.f) {} ~TestEchoDetector() override = default; void AnalyzeRenderAudio(rtc::ArrayView render_audio) override { last_render_audio_first_sample_ = render_audio[0]; analyze_render_audio_called_ = true; } void AnalyzeCaptureAudio(rtc::ArrayView capture_audio) override { } void Initialize(int capture_sample_rate_hz, int num_capture_channels, int render_sample_rate_hz, int num_render_channels) override {} EchoDetector::Metrics GetMetrics() const override { return {}; } // Returns true if AnalyzeRenderAudio() has been called at least once. bool analyze_render_audio_called() const { return analyze_render_audio_called_; } // Returns the first sample of the last analyzed render frame. float last_render_audio_first_sample() const { return last_render_audio_first_sample_; } private: bool analyze_render_audio_called_; float last_render_audio_first_sample_; }; // Mocks CustomProcessing and applies ProcessSample() to all the samples. // Meant to be injected into an APM to modify samples in a known and detectable // way. class TestRenderPreProcessor : public CustomProcessing { public: TestRenderPreProcessor() = default; ~TestRenderPreProcessor() = default; void Initialize(int sample_rate_hz, int num_channels) override {} void Process(AudioBuffer* audio) override { for (size_t k = 0; k < audio->num_channels(); ++k) { rtc::ArrayView channel_view(audio->channels()[k], audio->num_frames()); std::transform(channel_view.begin(), channel_view.end(), channel_view.begin(), ProcessSample); } } std::string ToString() const override { return "TestRenderPreProcessor"; } void SetRuntimeSetting(AudioProcessing::RuntimeSetting setting) override {} // Modifies a sample. This member is used in Process() to modify a frame and // it is publicly visible to enable tests. static constexpr float ProcessSample(float x) { return 2.f * x; } }; // Runs `apm` input processing for volume adjustments for `num_frames` random // frames starting from the volume `initial_volume`. This includes three steps: // 1) Set the input volume 2) Process the stream 3) Set the new recommended // input volume. Returns the new recommended input volume. int ProcessInputVolume(AudioProcessing& apm, int num_frames, int initial_volume) { constexpr int kSampleRateHz = 48000; constexpr int kNumChannels = 1; std::array buffer; float* channel_pointers[] = {buffer.data()}; StreamConfig stream_config(/*sample_rate_hz=*/kSampleRateHz, /*num_channels=*/kNumChannels); int recommended_input_volume = initial_volume; for (int i = 0; i < num_frames; ++i) { Random random_generator(2341U); RandomizeSampleVector(&random_generator, buffer); apm.set_stream_analog_level(recommended_input_volume); apm.ProcessStream(channel_pointers, stream_config, stream_config, channel_pointers); recommended_input_volume = apm.recommended_stream_analog_level(); } return recommended_input_volume; } } // namespace TEST(AudioProcessingImplTest, AudioParameterChangeTriggersInit) { MockInitialize mock; ON_CALL(mock, InitializeLocked) .WillByDefault(Invoke(&mock, &MockInitialize::RealInitializeLocked)); EXPECT_CALL(mock, InitializeLocked).Times(1); mock.Initialize(); constexpr size_t kMaxSampleRateHz = 32000; constexpr size_t kMaxNumChannels = 2; std::array frame; frame.fill(0); StreamConfig config(16000, 1); // Call with the default parameters; there should be an init. EXPECT_CALL(mock, InitializeLocked).Times(0); EXPECT_NOERR(mock.ProcessStream(frame.data(), config, config, frame.data())); EXPECT_NOERR( mock.ProcessReverseStream(frame.data(), config, config, frame.data())); // New sample rate. (Only impacts ProcessStream). config = StreamConfig(32000, 1); EXPECT_CALL(mock, InitializeLocked).Times(1); EXPECT_NOERR(mock.ProcessStream(frame.data(), config, config, frame.data())); // New number of channels. config = StreamConfig(32000, 2); EXPECT_CALL(mock, InitializeLocked).Times(2); EXPECT_NOERR(mock.ProcessStream(frame.data(), config, config, frame.data())); EXPECT_NOERR( mock.ProcessReverseStream(frame.data(), config, config, frame.data())); // A new sample rate passed to ProcessReverseStream should cause an init. config = StreamConfig(16000, 2); EXPECT_CALL(mock, InitializeLocked).Times(1); EXPECT_NOERR( mock.ProcessReverseStream(frame.data(), config, config, frame.data())); } TEST(AudioProcessingImplTest, UpdateCapturePreGainRuntimeSetting) { rtc::scoped_refptr apm = AudioProcessingBuilderForTesting().Create(); webrtc::AudioProcessing::Config apm_config; apm_config.pre_amplifier.enabled = true; apm_config.pre_amplifier.fixed_gain_factor = 1.f; apm->ApplyConfig(apm_config); constexpr int kSampleRateHz = 48000; constexpr int16_t kAudioLevel = 10000; constexpr size_t kNumChannels = 2; std::array frame; StreamConfig config(kSampleRateHz, kNumChannels); frame.fill(kAudioLevel); apm->ProcessStream(frame.data(), config, config, frame.data()); EXPECT_EQ(frame[100], kAudioLevel) << "With factor 1, frame shouldn't be modified."; constexpr float kGainFactor = 2.f; apm->SetRuntimeSetting( AudioProcessing::RuntimeSetting::CreateCapturePreGain(kGainFactor)); // Process for two frames to have time to ramp up gain. for (int i = 0; i < 2; ++i) { frame.fill(kAudioLevel); apm->ProcessStream(frame.data(), config, config, frame.data()); } EXPECT_EQ(frame[100], kGainFactor * kAudioLevel) << "Frame should be amplified."; } TEST(AudioProcessingImplTest, LevelAdjustmentUpdateCapturePreGainRuntimeSetting) { rtc::scoped_refptr apm = AudioProcessingBuilderForTesting().Create(); webrtc::AudioProcessing::Config apm_config; apm_config.capture_level_adjustment.enabled = true; apm_config.capture_level_adjustment.pre_gain_factor = 1.f; apm->ApplyConfig(apm_config); constexpr int kSampleRateHz = 48000; constexpr int16_t kAudioLevel = 10000; constexpr size_t kNumChannels = 2; std::array frame; StreamConfig config(kSampleRateHz, kNumChannels); frame.fill(kAudioLevel); apm->ProcessStream(frame.data(), config, config, frame.data()); EXPECT_EQ(frame[100], kAudioLevel) << "With factor 1, frame shouldn't be modified."; constexpr float kGainFactor = 2.f; apm->SetRuntimeSetting( AudioProcessing::RuntimeSetting::CreateCapturePreGain(kGainFactor)); // Process for two frames to have time to ramp up gain. for (int i = 0; i < 2; ++i) { frame.fill(kAudioLevel); apm->ProcessStream(frame.data(), config, config, frame.data()); } EXPECT_EQ(frame[100], kGainFactor * kAudioLevel) << "Frame should be amplified."; } TEST(AudioProcessingImplTest, LevelAdjustmentUpdateCapturePostGainRuntimeSetting) { rtc::scoped_refptr apm = AudioProcessingBuilderForTesting().Create(); webrtc::AudioProcessing::Config apm_config; apm_config.capture_level_adjustment.enabled = true; apm_config.capture_level_adjustment.post_gain_factor = 1.f; apm->ApplyConfig(apm_config); constexpr int kSampleRateHz = 48000; constexpr int16_t kAudioLevel = 10000; constexpr size_t kNumChannels = 2; std::array frame; StreamConfig config(kSampleRateHz, kNumChannels); frame.fill(kAudioLevel); apm->ProcessStream(frame.data(), config, config, frame.data()); EXPECT_EQ(frame[100], kAudioLevel) << "With factor 1, frame shouldn't be modified."; constexpr float kGainFactor = 2.f; apm->SetRuntimeSetting( AudioProcessing::RuntimeSetting::CreateCapturePostGain(kGainFactor)); // Process for two frames to have time to ramp up gain. for (int i = 0; i < 2; ++i) { frame.fill(kAudioLevel); apm->ProcessStream(frame.data(), config, config, frame.data()); } EXPECT_EQ(frame[100], kGainFactor * kAudioLevel) << "Frame should be amplified."; } TEST(AudioProcessingImplTest, EchoControllerObservesSetCaptureUsageChange) { // Tests that the echo controller observes that the capture usage has been // updated. auto echo_control_factory = std::make_unique(); const MockEchoControlFactory* echo_control_factory_ptr = echo_control_factory.get(); rtc::scoped_refptr apm = AudioProcessingBuilderForTesting() .SetEchoControlFactory(std::move(echo_control_factory)) .Create(); constexpr int16_t kAudioLevel = 10000; constexpr int kSampleRateHz = 48000; constexpr int kNumChannels = 2; std::array frame; StreamConfig config(kSampleRateHz, kNumChannels); frame.fill(kAudioLevel); MockEchoControl* echo_control_mock = echo_control_factory_ptr->GetNext(); // Ensure that SetCaptureOutputUsage is not called when no runtime settings // are passed. EXPECT_CALL(*echo_control_mock, SetCaptureOutputUsage(testing::_)).Times(0); apm->ProcessStream(frame.data(), config, config, frame.data()); // Ensure that SetCaptureOutputUsage is called with the right information when // a runtime setting is passed. EXPECT_CALL(*echo_control_mock, SetCaptureOutputUsage(/*capture_output_used=*/false)) .Times(1); EXPECT_TRUE(apm->PostRuntimeSetting( AudioProcessing::RuntimeSetting::CreateCaptureOutputUsedSetting( /*capture_output_used=*/false))); apm->ProcessStream(frame.data(), config, config, frame.data()); EXPECT_CALL(*echo_control_mock, SetCaptureOutputUsage(/*capture_output_used=*/true)) .Times(1); EXPECT_TRUE(apm->PostRuntimeSetting( AudioProcessing::RuntimeSetting::CreateCaptureOutputUsedSetting( /*capture_output_used=*/true))); apm->ProcessStream(frame.data(), config, config, frame.data()); // The number of positions to place items in the queue is equal to the queue // size minus 1. constexpr int kNumSlotsInQueue = RuntimeSettingQueueSize(); // Ensure that SetCaptureOutputUsage is called with the right information when // many runtime settings are passed. for (int k = 0; k < kNumSlotsInQueue - 1; ++k) { EXPECT_TRUE(apm->PostRuntimeSetting( AudioProcessing::RuntimeSetting::CreateCaptureOutputUsedSetting( /*capture_output_used=*/false))); } EXPECT_CALL(*echo_control_mock, SetCaptureOutputUsage(/*capture_output_used=*/false)) .Times(kNumSlotsInQueue - 1); apm->ProcessStream(frame.data(), config, config, frame.data()); // Ensure that SetCaptureOutputUsage is properly called with the fallback // value when the runtime settings queue becomes full. for (int k = 0; k < kNumSlotsInQueue; ++k) { EXPECT_TRUE(apm->PostRuntimeSetting( AudioProcessing::RuntimeSetting::CreateCaptureOutputUsedSetting( /*capture_output_used=*/false))); } EXPECT_FALSE(apm->PostRuntimeSetting( AudioProcessing::RuntimeSetting::CreateCaptureOutputUsedSetting( /*capture_output_used=*/false))); EXPECT_FALSE(apm->PostRuntimeSetting( AudioProcessing::RuntimeSetting::CreateCaptureOutputUsedSetting( /*capture_output_used=*/false))); EXPECT_CALL(*echo_control_mock, SetCaptureOutputUsage(/*capture_output_used=*/false)) .Times(kNumSlotsInQueue); EXPECT_CALL(*echo_control_mock, SetCaptureOutputUsage(/*capture_output_used=*/true)) .Times(1); apm->ProcessStream(frame.data(), config, config, frame.data()); } TEST(AudioProcessingImplTest, EchoControllerObservesPreAmplifierEchoPathGainChange) { // Tests that the echo controller observes an echo path gain change when the // pre-amplifier submodule changes the gain. auto echo_control_factory = std::make_unique(); const auto* echo_control_factory_ptr = echo_control_factory.get(); rtc::scoped_refptr apm = AudioProcessingBuilderForTesting() .SetEchoControlFactory(std::move(echo_control_factory)) .Create(); // Disable AGC. webrtc::AudioProcessing::Config apm_config; apm_config.gain_controller1.enabled = false; apm_config.gain_controller2.enabled = false; apm_config.pre_amplifier.enabled = true; apm_config.pre_amplifier.fixed_gain_factor = 1.f; apm->ApplyConfig(apm_config); constexpr int16_t kAudioLevel = 10000; constexpr size_t kSampleRateHz = 48000; constexpr size_t kNumChannels = 2; std::array frame; StreamConfig config(kSampleRateHz, kNumChannels); frame.fill(kAudioLevel); MockEchoControl* echo_control_mock = echo_control_factory_ptr->GetNext(); EXPECT_CALL(*echo_control_mock, AnalyzeCapture(testing::_)).Times(1); EXPECT_CALL(*echo_control_mock, ProcessCapture(NotNull(), testing::_, /*echo_path_change=*/false)) .Times(1); apm->ProcessStream(frame.data(), config, config, frame.data()); EXPECT_CALL(*echo_control_mock, AnalyzeCapture(testing::_)).Times(1); EXPECT_CALL(*echo_control_mock, ProcessCapture(NotNull(), testing::_, /*echo_path_change=*/true)) .Times(1); apm->SetRuntimeSetting( AudioProcessing::RuntimeSetting::CreateCapturePreGain(2.f)); apm->ProcessStream(frame.data(), config, config, frame.data()); } TEST(AudioProcessingImplTest, EchoControllerObservesLevelAdjustmentPreGainEchoPathGainChange) { // Tests that the echo controller observes an echo path gain change when the // pre-amplifier submodule changes the gain. auto echo_control_factory = std::make_unique(); const auto* echo_control_factory_ptr = echo_control_factory.get(); rtc::scoped_refptr apm = AudioProcessingBuilderForTesting() .SetEchoControlFactory(std::move(echo_control_factory)) .Create(); // Disable AGC. webrtc::AudioProcessing::Config apm_config; apm_config.gain_controller1.enabled = false; apm_config.gain_controller2.enabled = false; apm_config.capture_level_adjustment.enabled = true; apm_config.capture_level_adjustment.pre_gain_factor = 1.f; apm->ApplyConfig(apm_config); constexpr int16_t kAudioLevel = 10000; constexpr size_t kSampleRateHz = 48000; constexpr size_t kNumChannels = 2; std::array frame; StreamConfig config(kSampleRateHz, kNumChannels); frame.fill(kAudioLevel); MockEchoControl* echo_control_mock = echo_control_factory_ptr->GetNext(); EXPECT_CALL(*echo_control_mock, AnalyzeCapture(testing::_)).Times(1); EXPECT_CALL(*echo_control_mock, ProcessCapture(NotNull(), testing::_, /*echo_path_change=*/false)) .Times(1); apm->ProcessStream(frame.data(), config, config, frame.data()); EXPECT_CALL(*echo_control_mock, AnalyzeCapture(testing::_)).Times(1); EXPECT_CALL(*echo_control_mock, ProcessCapture(NotNull(), testing::_, /*echo_path_change=*/true)) .Times(1); apm->SetRuntimeSetting( AudioProcessing::RuntimeSetting::CreateCapturePreGain(2.f)); apm->ProcessStream(frame.data(), config, config, frame.data()); } TEST(AudioProcessingImplTest, EchoControllerObservesAnalogAgc1EchoPathGainChange) { // Tests that the echo controller observes an echo path gain change when the // AGC1 analog adaptive submodule changes the analog gain. auto echo_control_factory = std::make_unique(); const auto* echo_control_factory_ptr = echo_control_factory.get(); rtc::scoped_refptr apm = AudioProcessingBuilderForTesting() .SetEchoControlFactory(std::move(echo_control_factory)) .Create(); webrtc::AudioProcessing::Config apm_config; // Enable AGC1. apm_config.gain_controller1.enabled = true; apm_config.gain_controller1.analog_gain_controller.enabled = true; apm_config.gain_controller2.enabled = false; apm_config.pre_amplifier.enabled = false; apm->ApplyConfig(apm_config); constexpr int16_t kAudioLevel = 1000; constexpr size_t kSampleRateHz = 48000; constexpr size_t kNumChannels = 2; std::array frame; StreamConfig stream_config(kSampleRateHz, kNumChannels); frame.fill(kAudioLevel); MockEchoControl* echo_control_mock = echo_control_factory_ptr->GetNext(); constexpr int kInitialStreamAnalogLevel = 123; apm->set_stream_analog_level(kInitialStreamAnalogLevel); // When the first fame is processed, no echo path gain change must be // detected. EXPECT_CALL(*echo_control_mock, AnalyzeCapture(testing::_)).Times(1); EXPECT_CALL(*echo_control_mock, ProcessCapture(NotNull(), testing::_, /*echo_path_change=*/false)) .Times(1); apm->ProcessStream(frame.data(), stream_config, stream_config, frame.data()); // Simulate the application of the recommended analog level. int recommended_analog_level = apm->recommended_stream_analog_level(); if (recommended_analog_level == kInitialStreamAnalogLevel) { // Force an analog gain change if it did not happen. recommended_analog_level++; } apm->set_stream_analog_level(recommended_analog_level); // After the first fame and with a stream analog level change, the echo path // gain change must be detected. EXPECT_CALL(*echo_control_mock, AnalyzeCapture(testing::_)).Times(1); EXPECT_CALL(*echo_control_mock, ProcessCapture(NotNull(), testing::_, /*echo_path_change=*/true)) .Times(1); apm->ProcessStream(frame.data(), stream_config, stream_config, frame.data()); } TEST(AudioProcessingImplTest, ProcessWithAgc2AndTransientSuppressorVadModeDefault) { webrtc::test::ScopedFieldTrials field_trials( "WebRTC-Audio-GainController2/Disabled/"); auto apm = AudioProcessingBuilder() .SetConfig({.gain_controller1{.enabled = false}}) .Create(); ASSERT_EQ(apm->Initialize(), AudioProcessing::kNoError); webrtc::AudioProcessing::Config apm_config; apm_config.gain_controller1.enabled = false; apm_config.gain_controller2.enabled = true; apm_config.gain_controller2.adaptive_digital.enabled = true; apm_config.transient_suppression.enabled = true; apm->ApplyConfig(apm_config); constexpr int kSampleRateHz = 48000; constexpr int kNumChannels = 1; std::array buffer; float* channel_pointers[] = {buffer.data()}; StreamConfig stream_config(/*sample_rate_hz=*/kSampleRateHz, /*num_channels=*/kNumChannels); Random random_generator(2341U); constexpr int kFramesToProcess = 10; for (int i = 0; i < kFramesToProcess; ++i) { RandomizeSampleVector(&random_generator, buffer); ASSERT_EQ(apm->ProcessStream(channel_pointers, stream_config, stream_config, channel_pointers), kNoErr); } } TEST(AudioProcessingImplTest, ProcessWithAgc2AndTransientSuppressorVadModeRnnVad) { webrtc::test::ScopedFieldTrials field_trials( "WebRTC-Audio-GainController2/Enabled,switch_to_agc2:true/"); rtc::scoped_refptr apm = AudioProcessingBuilder().Create(); ASSERT_EQ(apm->Initialize(), AudioProcessing::kNoError); webrtc::AudioProcessing::Config apm_config; apm_config.gain_controller1.enabled = false; apm_config.gain_controller2.enabled = true; apm_config.gain_controller2.adaptive_digital.enabled = true; apm_config.transient_suppression.enabled = true; apm->ApplyConfig(apm_config); constexpr int kSampleRateHz = 48000; constexpr int kNumChannels = 1; std::array buffer; float* channel_pointers[] = {buffer.data()}; StreamConfig stream_config(/*sample_rate_hz=*/kSampleRateHz, /*num_channels=*/kNumChannels); Random random_generator(2341U); constexpr int kFramesToProcess = 10; for (int i = 0; i < kFramesToProcess; ++i) { RandomizeSampleVector(&random_generator, buffer); ASSERT_EQ(apm->ProcessStream(channel_pointers, stream_config, stream_config, channel_pointers), kNoErr); } } TEST(AudioProcessingImplTest, EchoControllerObservesPlayoutVolumeChange) { // Tests that the echo controller observes an echo path gain change when a // playout volume change is reported. auto echo_control_factory = std::make_unique(); const auto* echo_control_factory_ptr = echo_control_factory.get(); rtc::scoped_refptr apm = AudioProcessingBuilderForTesting() .SetEchoControlFactory(std::move(echo_control_factory)) .Create(); // Disable AGC. webrtc::AudioProcessing::Config apm_config; apm_config.gain_controller1.enabled = false; apm_config.gain_controller2.enabled = false; apm->ApplyConfig(apm_config); constexpr int16_t kAudioLevel = 10000; constexpr size_t kSampleRateHz = 48000; constexpr size_t kNumChannels = 2; std::array frame; StreamConfig stream_config(kSampleRateHz, kNumChannels); frame.fill(kAudioLevel); MockEchoControl* echo_control_mock = echo_control_factory_ptr->GetNext(); EXPECT_CALL(*echo_control_mock, AnalyzeCapture(testing::_)).Times(1); EXPECT_CALL(*echo_control_mock, ProcessCapture(NotNull(), testing::_, /*echo_path_change=*/false)) .Times(1); apm->ProcessStream(frame.data(), stream_config, stream_config, frame.data()); EXPECT_CALL(*echo_control_mock, AnalyzeCapture(testing::_)).Times(1); EXPECT_CALL(*echo_control_mock, ProcessCapture(NotNull(), testing::_, /*echo_path_change=*/false)) .Times(1); apm->SetRuntimeSetting( AudioProcessing::RuntimeSetting::CreatePlayoutVolumeChange(50)); apm->ProcessStream(frame.data(), stream_config, stream_config, frame.data()); EXPECT_CALL(*echo_control_mock, AnalyzeCapture(testing::_)).Times(1); EXPECT_CALL(*echo_control_mock, ProcessCapture(NotNull(), testing::_, /*echo_path_change=*/false)) .Times(1); apm->SetRuntimeSetting( AudioProcessing::RuntimeSetting::CreatePlayoutVolumeChange(50)); apm->ProcessStream(frame.data(), stream_config, stream_config, frame.data()); EXPECT_CALL(*echo_control_mock, AnalyzeCapture(testing::_)).Times(1); EXPECT_CALL(*echo_control_mock, ProcessCapture(NotNull(), testing::_, /*echo_path_change=*/true)) .Times(1); apm->SetRuntimeSetting( AudioProcessing::RuntimeSetting::CreatePlayoutVolumeChange(100)); apm->ProcessStream(frame.data(), stream_config, stream_config, frame.data()); } TEST(AudioProcessingImplTest, RenderPreProcessorBeforeEchoDetector) { // Make sure that signal changes caused by a render pre-processing sub-module // take place before any echo detector analysis. auto test_echo_detector = rtc::make_ref_counted(); std::unique_ptr test_render_pre_processor( new TestRenderPreProcessor()); // Create APM injecting the test echo detector and render pre-processor. rtc::scoped_refptr apm = AudioProcessingBuilderForTesting() .SetEchoDetector(test_echo_detector) .SetRenderPreProcessing(std::move(test_render_pre_processor)) .Create(); webrtc::AudioProcessing::Config apm_config; apm_config.pre_amplifier.enabled = true; apm->ApplyConfig(apm_config); constexpr int16_t kAudioLevel = 1000; constexpr int kSampleRateHz = 16000; constexpr size_t kNumChannels = 1; // Explicitly initialize APM to ensure no render frames are discarded. const ProcessingConfig processing_config = {{ {kSampleRateHz, kNumChannels}, {kSampleRateHz, kNumChannels}, {kSampleRateHz, kNumChannels}, {kSampleRateHz, kNumChannels}, }}; apm->Initialize(processing_config); std::array frame; StreamConfig stream_config(kSampleRateHz, kNumChannels); constexpr float kAudioLevelFloat = static_cast(kAudioLevel); constexpr float kExpectedPreprocessedAudioLevel = TestRenderPreProcessor::ProcessSample(kAudioLevelFloat); ASSERT_NE(kAudioLevelFloat, kExpectedPreprocessedAudioLevel); // Analyze a render stream frame. frame.fill(kAudioLevel); ASSERT_EQ(AudioProcessing::Error::kNoError, apm->ProcessReverseStream(frame.data(), stream_config, stream_config, frame.data())); // Trigger a call to in EchoDetector::AnalyzeRenderAudio() via // ProcessStream(). frame.fill(kAudioLevel); ASSERT_EQ(AudioProcessing::Error::kNoError, apm->ProcessStream(frame.data(), stream_config, stream_config, frame.data())); // Regardless of how the call to in EchoDetector::AnalyzeRenderAudio() is // triggered, the line below checks that the call has occurred. If not, the // APM implementation may have changed and this test might need to be adapted. ASSERT_TRUE(test_echo_detector->analyze_render_audio_called()); // Check that the data read in EchoDetector::AnalyzeRenderAudio() is that // produced by the render pre-processor. EXPECT_EQ(kExpectedPreprocessedAudioLevel, test_echo_detector->last_render_audio_first_sample()); } // Disabling build-optional submodules and trying to enable them via the APM // config should be bit-exact with running APM with said submodules disabled. // This mainly tests that SetCreateOptionalSubmodulesForTesting has an effect. TEST(ApmWithSubmodulesExcludedTest, BitexactWithDisabledModules) { auto apm = rtc::make_ref_counted(); ASSERT_EQ(apm->Initialize(), AudioProcessing::kNoError); ApmSubmoduleCreationOverrides overrides; overrides.transient_suppression = true; apm->OverrideSubmoduleCreationForTesting(overrides); AudioProcessing::Config apm_config = apm->GetConfig(); apm_config.transient_suppression.enabled = true; apm->ApplyConfig(apm_config); rtc::scoped_refptr apm_reference = AudioProcessingBuilder().Create(); apm_config = apm_reference->GetConfig(); apm_config.transient_suppression.enabled = false; apm_reference->ApplyConfig(apm_config); constexpr int kSampleRateHz = 16000; constexpr int kNumChannels = 1; std::array buffer; std::array buffer_reference; float* channel_pointers[] = {buffer.data()}; float* channel_pointers_reference[] = {buffer_reference.data()}; StreamConfig stream_config(/*sample_rate_hz=*/kSampleRateHz, /*num_channels=*/kNumChannels); Random random_generator(2341U); constexpr int kFramesToProcessPerConfiguration = 10; for (int i = 0; i < kFramesToProcessPerConfiguration; ++i) { RandomizeSampleVector(&random_generator, buffer); std::copy(buffer.begin(), buffer.end(), buffer_reference.begin()); ASSERT_EQ(apm->ProcessStream(channel_pointers, stream_config, stream_config, channel_pointers), kNoErr); ASSERT_EQ( apm_reference->ProcessStream(channel_pointers_reference, stream_config, stream_config, channel_pointers_reference), kNoErr); for (int j = 0; j < kSampleRateHz / 100; ++j) { EXPECT_EQ(buffer[j], buffer_reference[j]); } } } // Disable transient suppressor creation and run APM in ways that should trigger // calls to the transient suppressor API. TEST(ApmWithSubmodulesExcludedTest, ReinitializeTransientSuppressor) { auto apm = rtc::make_ref_counted(); ASSERT_EQ(apm->Initialize(), kNoErr); ApmSubmoduleCreationOverrides overrides; overrides.transient_suppression = true; apm->OverrideSubmoduleCreationForTesting(overrides); AudioProcessing::Config config = apm->GetConfig(); config.transient_suppression.enabled = true; apm->ApplyConfig(config); // 960 samples per frame: 10 ms of <= 48 kHz audio with <= 2 channels. float buffer[960]; float* channel_pointers[] = {&buffer[0], &buffer[480]}; Random random_generator(2341U); constexpr int kFramesToProcessPerConfiguration = 3; StreamConfig initial_stream_config(/*sample_rate_hz=*/16000, /*num_channels=*/1); for (int i = 0; i < kFramesToProcessPerConfiguration; ++i) { RandomizeSampleVector(&random_generator, buffer); EXPECT_EQ(apm->ProcessStream(channel_pointers, initial_stream_config, initial_stream_config, channel_pointers), kNoErr); } StreamConfig stereo_stream_config(/*sample_rate_hz=*/16000, /*num_channels=*/2); for (int i = 0; i < kFramesToProcessPerConfiguration; ++i) { RandomizeSampleVector(&random_generator, buffer); EXPECT_EQ(apm->ProcessStream(channel_pointers, stereo_stream_config, stereo_stream_config, channel_pointers), kNoErr); } StreamConfig high_sample_rate_stream_config(/*sample_rate_hz=*/48000, /*num_channels=*/2); for (int i = 0; i < kFramesToProcessPerConfiguration; ++i) { RandomizeSampleVector(&random_generator, buffer); EXPECT_EQ( apm->ProcessStream(channel_pointers, high_sample_rate_stream_config, high_sample_rate_stream_config, channel_pointers), kNoErr); } } // Disable transient suppressor creation and run APM in ways that should trigger // calls to the transient suppressor API. TEST(ApmWithSubmodulesExcludedTest, ToggleTransientSuppressor) { auto apm = rtc::make_ref_counted(); ASSERT_EQ(apm->Initialize(), AudioProcessing::kNoError); ApmSubmoduleCreationOverrides overrides; overrides.transient_suppression = true; apm->OverrideSubmoduleCreationForTesting(overrides); // 960 samples per frame: 10 ms of <= 48 kHz audio with <= 2 channels. float buffer[960]; float* channel_pointers[] = {&buffer[0], &buffer[480]}; Random random_generator(2341U); constexpr int kFramesToProcessPerConfiguration = 3; StreamConfig stream_config(/*sample_rate_hz=*/16000, /*num_channels=*/1); AudioProcessing::Config config = apm->GetConfig(); config.transient_suppression.enabled = true; apm->ApplyConfig(config); for (int i = 0; i < kFramesToProcessPerConfiguration; ++i) { RandomizeSampleVector(&random_generator, buffer); EXPECT_EQ(apm->ProcessStream(channel_pointers, stream_config, stream_config, channel_pointers), kNoErr); } config = apm->GetConfig(); config.transient_suppression.enabled = false; apm->ApplyConfig(config); for (int i = 0; i < kFramesToProcessPerConfiguration; ++i) { RandomizeSampleVector(&random_generator, buffer); EXPECT_EQ(apm->ProcessStream(channel_pointers, stream_config, stream_config, channel_pointers), kNoErr); } config = apm->GetConfig(); config.transient_suppression.enabled = true; apm->ApplyConfig(config); for (int i = 0; i < kFramesToProcessPerConfiguration; ++i) { RandomizeSampleVector(&random_generator, buffer); EXPECT_EQ(apm->ProcessStream(channel_pointers, stream_config, stream_config, channel_pointers), kNoErr); } } class StartupInputVolumeParameterizedTest : public ::testing::TestWithParam {}; // Tests that, when no input volume controller is used, the startup input volume // is never modified. TEST_P(StartupInputVolumeParameterizedTest, WithNoInputVolumeControllerStartupVolumeNotModified) { webrtc::AudioProcessing::Config config; config.gain_controller1.enabled = false; config.gain_controller2.enabled = false; auto apm = AudioProcessingBuilder().SetConfig(config).Create(); int startup_volume = GetParam(); int recommended_volume = ProcessInputVolume( *apm, /*num_frames=*/1, /*initial_volume=*/startup_volume); EXPECT_EQ(recommended_volume, startup_volume); } INSTANTIATE_TEST_SUITE_P(AudioProcessingImplTest, StartupInputVolumeParameterizedTest, ::testing::Values(0, 5, 15, 50, 100)); // Tests that, when no input volume controller is used, the recommended input // volume always matches the applied one. TEST(AudioProcessingImplTest, WithNoInputVolumeControllerAppliedAndRecommendedVolumesMatch) { webrtc::AudioProcessing::Config config; config.gain_controller1.enabled = false; config.gain_controller2.enabled = false; auto apm = AudioProcessingBuilder().SetConfig(config).Create(); Random rand_gen(42); for (int i = 0; i < 32; ++i) { SCOPED_TRACE(i); int32_t applied_volume = rand_gen.Rand(/*low=*/0, /*high=*/255); int recommended_volume = ProcessInputVolume(*apm, /*num_frames=*/1, applied_volume); EXPECT_EQ(recommended_volume, applied_volume); } } class ApmInputVolumeControllerParametrizedTest : public ::testing::TestWithParam< std::tuple> { protected: ApmInputVolumeControllerParametrizedTest() : sample_rate_hz_(std::get<0>(GetParam())), num_channels_(std::get<1>(GetParam())), channels_(num_channels_), channel_pointers_(num_channels_) { const int frame_size = sample_rate_hz_ / 100; for (int c = 0; c < num_channels_; ++c) { channels_[c].resize(frame_size); channel_pointers_[c] = channels_[c].data(); std::fill(channels_[c].begin(), channels_[c].end(), 0.0f); } } int sample_rate_hz() const { return sample_rate_hz_; } int num_channels() const { return num_channels_; } AudioProcessing::Config GetConfig() const { return std::get<2>(GetParam()); } float* const* channel_pointers() { return channel_pointers_.data(); } private: const int sample_rate_hz_; const int num_channels_; std::vector> channels_; std::vector channel_pointers_; }; TEST_P(ApmInputVolumeControllerParametrizedTest, EnforceMinInputVolumeAtStartupWithZeroVolume) { const StreamConfig stream_config(sample_rate_hz(), num_channels()); auto apm = AudioProcessingBuilder().SetConfig(GetConfig()).Create(); apm->set_stream_analog_level(0); apm->ProcessStream(channel_pointers(), stream_config, stream_config, channel_pointers()); EXPECT_GT(apm->recommended_stream_analog_level(), 0); } TEST_P(ApmInputVolumeControllerParametrizedTest, EnforceMinInputVolumeAtStartupWithNonZeroVolume) { const StreamConfig stream_config(sample_rate_hz(), num_channels()); auto apm = AudioProcessingBuilder().SetConfig(GetConfig()).Create(); constexpr int kStartupVolume = 3; apm->set_stream_analog_level(kStartupVolume); apm->ProcessStream(channel_pointers(), stream_config, stream_config, channel_pointers()); EXPECT_GT(apm->recommended_stream_analog_level(), kStartupVolume); } TEST_P(ApmInputVolumeControllerParametrizedTest, EnforceMinInputVolumeAfterManualVolumeAdjustment) { const auto config = GetConfig(); if (config.gain_controller1.enabled) { // After a downward manual adjustment, AGC1 slowly converges to the minimum // input volume. GTEST_SKIP() << "Does not apply to AGC1"; } const StreamConfig stream_config(sample_rate_hz(), num_channels()); auto apm = AudioProcessingBuilder().SetConfig(GetConfig()).Create(); apm->set_stream_analog_level(20); apm->ProcessStream(channel_pointers(), stream_config, stream_config, channel_pointers()); constexpr int kManuallyAdjustedVolume = 3; apm->set_stream_analog_level(kManuallyAdjustedVolume); apm->ProcessStream(channel_pointers(), stream_config, stream_config, channel_pointers()); EXPECT_GT(apm->recommended_stream_analog_level(), kManuallyAdjustedVolume); } TEST_P(ApmInputVolumeControllerParametrizedTest, DoNotEnforceMinInputVolumeAtStartupWithHighVolume) { const StreamConfig stream_config(sample_rate_hz(), num_channels()); auto apm = AudioProcessingBuilder().SetConfig(GetConfig()).Create(); constexpr int kStartupVolume = 200; apm->set_stream_analog_level(kStartupVolume); apm->ProcessStream(channel_pointers(), stream_config, stream_config, channel_pointers()); EXPECT_EQ(apm->recommended_stream_analog_level(), kStartupVolume); } TEST_P(ApmInputVolumeControllerParametrizedTest, DoNotEnforceMinInputVolumeAfterManualVolumeAdjustmentToZero) { const StreamConfig stream_config(sample_rate_hz(), num_channels()); auto apm = AudioProcessingBuilder().SetConfig(GetConfig()).Create(); apm->set_stream_analog_level(100); apm->ProcessStream(channel_pointers(), stream_config, stream_config, channel_pointers()); apm->set_stream_analog_level(0); apm->ProcessStream(channel_pointers(), stream_config, stream_config, channel_pointers()); EXPECT_EQ(apm->recommended_stream_analog_level(), 0); } INSTANTIATE_TEST_SUITE_P( AudioProcessingImplTest, ApmInputVolumeControllerParametrizedTest, ::testing::Combine( ::testing::Values(8000, 16000, 32000, 48000), // Sample rates. ::testing::Values(1, 2), // Number of channels. ::testing::Values( // Full AGC1. AudioProcessing::Config{ .gain_controller1 = {.enabled = true, .analog_gain_controller = {.enabled = true, .enable_digital_adaptive = true}}, .gain_controller2 = {.enabled = false}}, // Hybrid AGC. AudioProcessing::Config{ .gain_controller1 = {.enabled = true, .analog_gain_controller = {.enabled = true, .enable_digital_adaptive = false}}, .gain_controller2 = {.enabled = true, .adaptive_digital = {.enabled = true}}}))); // When the input volume is not emulated and no input volume controller is // active, the recommended volume must always be the applied volume. TEST(AudioProcessingImplTest, RecommendAppliedInputVolumeWithNoAgcWithNoEmulation) { auto apm = AudioProcessingBuilder() .SetConfig({.capture_level_adjustment = {.enabled = false}, .gain_controller1 = {.enabled = false}}) .Create(); constexpr int kOneFrame = 1; EXPECT_EQ(ProcessInputVolume(*apm, kOneFrame, /*initial_volume=*/123), 123); EXPECT_EQ(ProcessInputVolume(*apm, kOneFrame, /*initial_volume=*/59), 59); EXPECT_EQ(ProcessInputVolume(*apm, kOneFrame, /*initial_volume=*/135), 135); } // When the input volume is emulated, the recommended volume must always be the // applied volume and at any time it must not be that set in the input volume // emulator. // TODO(bugs.webrtc.org/14581): Enable when APM fixed to let this test pass. TEST(AudioProcessingImplTest, DISABLED_RecommendAppliedInputVolumeWithNoAgcWithEmulation) { auto apm = AudioProcessingBuilder() .SetConfig({.capture_level_adjustment = {.enabled = true, .analog_mic_gain_emulation{ .enabled = true, .initial_level = 255}}, .gain_controller1 = {.enabled = false}}) .Create(); constexpr int kOneFrame = 1; EXPECT_EQ(ProcessInputVolume(*apm, kOneFrame, /*initial_volume=*/123), 123); EXPECT_EQ(ProcessInputVolume(*apm, kOneFrame, /*initial_volume=*/59), 59); EXPECT_EQ(ProcessInputVolume(*apm, kOneFrame, /*initial_volume=*/135), 135); } // Even if there is an enabled input volume controller, when the input volume is // emulated, the recommended volume is always the applied volume because the // active controller must only adjust the internally emulated volume and leave // the externally applied volume unchanged. // TODO(bugs.webrtc.org/14581): Enable when APM fixed to let this test pass. TEST(AudioProcessingImplTest, DISABLED_RecommendAppliedInputVolumeWithAgcWithEmulation) { auto apm = AudioProcessingBuilder() .SetConfig({.capture_level_adjustment = {.enabled = true, .analog_mic_gain_emulation{ .enabled = true}}, .gain_controller1 = {.enabled = true, .analog_gain_controller{ .enabled = true, }}}) .Create(); constexpr int kOneFrame = 1; EXPECT_EQ(ProcessInputVolume(*apm, kOneFrame, /*initial_volume=*/123), 123); EXPECT_EQ(ProcessInputVolume(*apm, kOneFrame, /*initial_volume=*/59), 59); EXPECT_EQ(ProcessInputVolume(*apm, kOneFrame, /*initial_volume=*/135), 135); } TEST(AudioProcessingImplTest, Agc2FieldTrialDoNotSwitchToFullAgc2WhenNoAgcIsActive) { constexpr AudioProcessing::Config kOriginal{ .gain_controller1{.enabled = false}, .gain_controller2{.enabled = false}, }; webrtc::test::ScopedFieldTrials field_trials( "WebRTC-Audio-GainController2/Enabled,switch_to_agc2:true/"); // Test config application via `AudioProcessing` ctor. auto adjusted = AudioProcessingBuilder().SetConfig(kOriginal).Create()->GetConfig(); EXPECT_EQ(adjusted.gain_controller1, kOriginal.gain_controller1); EXPECT_EQ(adjusted.gain_controller2, kOriginal.gain_controller2); // Test config application via `AudioProcessing::ApplyConfig()`. auto apm = AudioProcessingBuilder().Create(); apm->ApplyConfig(kOriginal); adjusted = apm->GetConfig(); EXPECT_EQ(adjusted.gain_controller1, kOriginal.gain_controller1); EXPECT_EQ(adjusted.gain_controller2, kOriginal.gain_controller2); } TEST(AudioProcessingImplTest, Agc2FieldTrialDoNotSwitchToFullAgc2WithAgc1Agc2InputVolumeControllers) { constexpr AudioProcessing::Config kOriginal{ .gain_controller1{.enabled = true, .analog_gain_controller{.enabled = true}}, .gain_controller2{.enabled = true, .input_volume_controller{.enabled = true}}, }; webrtc::test::ScopedFieldTrials field_trials( "WebRTC-Audio-GainController2/Enabled,switch_to_agc2:true/"); // Test config application via `AudioProcessing` ctor. auto adjusted = AudioProcessingBuilder().SetConfig(kOriginal).Create()->GetConfig(); EXPECT_EQ(adjusted.gain_controller1, kOriginal.gain_controller1); EXPECT_EQ(adjusted.gain_controller2, kOriginal.gain_controller2); // Test config application via `AudioProcessing::ApplyConfig()`. auto apm = AudioProcessingBuilder().Create(); apm->ApplyConfig(kOriginal); adjusted = apm->GetConfig(); EXPECT_EQ(adjusted.gain_controller1, kOriginal.gain_controller1); EXPECT_EQ(adjusted.gain_controller2, kOriginal.gain_controller2); } class Agc2FieldTrialParametrizedTest : public ::testing::TestWithParam {}; TEST_P(Agc2FieldTrialParametrizedTest, DoNotChangeConfigIfDisabled) { const AudioProcessing::Config original = GetParam(); webrtc::test::ScopedFieldTrials field_trials( "WebRTC-Audio-GainController2/Disabled/"); // Test config application via `AudioProcessing` ctor. auto adjusted = AudioProcessingBuilder().SetConfig(original).Create()->GetConfig(); EXPECT_EQ(adjusted.gain_controller1, original.gain_controller1); EXPECT_EQ(adjusted.gain_controller2, original.gain_controller2); // Test config application via `AudioProcessing::ApplyConfig()`. auto apm = AudioProcessingBuilder().Create(); apm->ApplyConfig(original); adjusted = apm->GetConfig(); EXPECT_EQ(adjusted.gain_controller1, original.gain_controller1); EXPECT_EQ(adjusted.gain_controller2, original.gain_controller2); } TEST_P(Agc2FieldTrialParametrizedTest, DoNotChangeConfigIfNoOverride) { const AudioProcessing::Config original = GetParam(); webrtc::test::ScopedFieldTrials field_trials( "WebRTC-Audio-GainController2/Enabled," "switch_to_agc2:false," "disallow_transient_suppressor_usage:false/"); // Test config application via `AudioProcessing` ctor. auto adjusted = AudioProcessingBuilder().SetConfig(original).Create()->GetConfig(); EXPECT_EQ(adjusted.gain_controller1, original.gain_controller1); EXPECT_EQ(adjusted.gain_controller2, original.gain_controller2); // Test config application via `AudioProcessing::ApplyConfig()`. auto apm = AudioProcessingBuilder().Create(); apm->ApplyConfig(original); adjusted = apm->GetConfig(); EXPECT_EQ(adjusted.gain_controller1, original.gain_controller1); EXPECT_EQ(adjusted.gain_controller2, original.gain_controller2); } TEST_P(Agc2FieldTrialParametrizedTest, DoNotSwitchToFullAgc2) { const AudioProcessing::Config original = GetParam(); webrtc::test::ScopedFieldTrials field_trials( "WebRTC-Audio-GainController2/Enabled,switch_to_agc2:false/"); // Test config application via `AudioProcessing` ctor. auto adjusted = AudioProcessingBuilder().SetConfig(original).Create()->GetConfig(); EXPECT_EQ(adjusted.gain_controller1, original.gain_controller1); EXPECT_EQ(adjusted.gain_controller2, original.gain_controller2); // Test config application via `AudioProcessing::ApplyConfig()`. auto apm = AudioProcessingBuilder().Create(); apm->ApplyConfig(original); adjusted = apm->GetConfig(); EXPECT_EQ(adjusted.gain_controller1, original.gain_controller1); EXPECT_EQ(adjusted.gain_controller2, original.gain_controller2); } TEST_P(Agc2FieldTrialParametrizedTest, SwitchToFullAgc2) { const AudioProcessing::Config original = GetParam(); webrtc::test::ScopedFieldTrials field_trials( "WebRTC-Audio-GainController2/Enabled,switch_to_agc2:true/"); // Test config application via `AudioProcessing` ctor. auto adjusted = AudioProcessingBuilder().SetConfig(original).Create()->GetConfig(); EXPECT_FALSE(adjusted.gain_controller1.enabled); EXPECT_TRUE(adjusted.gain_controller2.enabled); EXPECT_TRUE(adjusted.gain_controller2.input_volume_controller.enabled); EXPECT_TRUE(adjusted.gain_controller2.adaptive_digital.enabled); // Test config application via `AudioProcessing::ApplyConfig()`. auto apm = AudioProcessingBuilder().Create(); apm->ApplyConfig(original); adjusted = apm->GetConfig(); EXPECT_FALSE(adjusted.gain_controller1.enabled); EXPECT_TRUE(adjusted.gain_controller2.enabled); EXPECT_TRUE(adjusted.gain_controller2.input_volume_controller.enabled); EXPECT_TRUE(adjusted.gain_controller2.adaptive_digital.enabled); } TEST_P(Agc2FieldTrialParametrizedTest, SwitchToFullAgc2AndOverrideInputVolumeControllerParameters) { const AudioProcessing::Config original = GetParam(); webrtc::test::ScopedFieldTrials field_trials( "WebRTC-Audio-GainController2/Enabled,switch_to_agc2:true," "min_input_volume:123," "clipped_level_min:20," "clipped_level_step:30," "clipped_ratio_threshold:0.4," "clipped_wait_frames:50," "enable_clipping_predictor:true," "target_range_max_dbfs:-6," "target_range_min_dbfs:-70," "update_input_volume_wait_frames:80," "speech_probability_threshold:0.9," "speech_ratio_threshold:1.0/"); // Test config application via `AudioProcessing` ctor. auto adjusted = AudioProcessingBuilder().SetConfig(original).Create()->GetConfig(); EXPECT_FALSE(adjusted.gain_controller1.enabled); EXPECT_TRUE(adjusted.gain_controller2.enabled); EXPECT_TRUE(adjusted.gain_controller2.input_volume_controller.enabled); EXPECT_TRUE(adjusted.gain_controller2.adaptive_digital.enabled); // Test config application via `AudioProcessing::ApplyConfig()`. auto apm = AudioProcessingBuilder().Create(); apm->ApplyConfig(original); adjusted = apm->GetConfig(); EXPECT_FALSE(adjusted.gain_controller1.enabled); EXPECT_TRUE(adjusted.gain_controller2.enabled); EXPECT_TRUE(adjusted.gain_controller2.input_volume_controller.enabled); EXPECT_TRUE(adjusted.gain_controller2.adaptive_digital.enabled); } TEST_P(Agc2FieldTrialParametrizedTest, SwitchToFullAgc2AndOverrideAdaptiveDigitalControllerParameters) { const AudioProcessing::Config original = GetParam(); webrtc::test::ScopedFieldTrials field_trials( "WebRTC-Audio-GainController2/Enabled,switch_to_agc2:true," "headroom_db:10," "max_gain_db:20," "initial_gain_db:7," "max_gain_change_db_per_second:5," "max_output_noise_level_dbfs:-40/"); // Test config application via `AudioProcessing` ctor. auto adjusted = AudioProcessingBuilder().SetConfig(original).Create()->GetConfig(); EXPECT_FALSE(adjusted.gain_controller1.enabled); EXPECT_TRUE(adjusted.gain_controller2.enabled); EXPECT_TRUE(adjusted.gain_controller2.input_volume_controller.enabled); EXPECT_TRUE(adjusted.gain_controller2.adaptive_digital.enabled); ASSERT_NE(adjusted.gain_controller2.adaptive_digital, original.gain_controller2.adaptive_digital); EXPECT_EQ(adjusted.gain_controller2.adaptive_digital.headroom_db, 10); EXPECT_EQ(adjusted.gain_controller2.adaptive_digital.max_gain_db, 20); EXPECT_EQ(adjusted.gain_controller2.adaptive_digital.initial_gain_db, 7); EXPECT_EQ( adjusted.gain_controller2.adaptive_digital.max_gain_change_db_per_second, 5); EXPECT_EQ( adjusted.gain_controller2.adaptive_digital.max_output_noise_level_dbfs, -40); // Test config application via `AudioProcessing::ApplyConfig()`. auto apm = AudioProcessingBuilder().Create(); apm->ApplyConfig(original); adjusted = apm->GetConfig(); EXPECT_FALSE(adjusted.gain_controller1.enabled); EXPECT_TRUE(adjusted.gain_controller2.enabled); EXPECT_TRUE(adjusted.gain_controller2.input_volume_controller.enabled); EXPECT_TRUE(adjusted.gain_controller2.adaptive_digital.enabled); ASSERT_NE(adjusted.gain_controller2.adaptive_digital, original.gain_controller2.adaptive_digital); EXPECT_EQ(adjusted.gain_controller2.adaptive_digital.headroom_db, 10); EXPECT_EQ(adjusted.gain_controller2.adaptive_digital.max_gain_db, 20); EXPECT_EQ(adjusted.gain_controller2.adaptive_digital.initial_gain_db, 7); EXPECT_EQ( adjusted.gain_controller2.adaptive_digital.max_gain_change_db_per_second, 5); EXPECT_EQ( adjusted.gain_controller2.adaptive_digital.max_output_noise_level_dbfs, -40); } TEST_P(Agc2FieldTrialParametrizedTest, ProcessSucceedsWithTs) { AudioProcessing::Config config = GetParam(); if (!config.transient_suppression.enabled) { GTEST_SKIP() << "TS is disabled, skip."; } webrtc::test::ScopedFieldTrials field_trials( "WebRTC-Audio-GainController2/Disabled/"); auto apm = AudioProcessingBuilder().SetConfig(config).Create(); constexpr int kSampleRateHz = 48000; constexpr int kNumChannels = 1; std::array buffer; float* channel_pointers[] = {buffer.data()}; StreamConfig stream_config(kSampleRateHz, kNumChannels); Random random_generator(2341U); constexpr int kFramesToProcess = 10; int volume = 100; for (int i = 0; i < kFramesToProcess; ++i) { SCOPED_TRACE(i); RandomizeSampleVector(&random_generator, buffer); apm->set_stream_analog_level(volume); ASSERT_EQ(apm->ProcessStream(channel_pointers, stream_config, stream_config, channel_pointers), kNoErr); volume = apm->recommended_stream_analog_level(); } } TEST_P(Agc2FieldTrialParametrizedTest, ProcessSucceedsWithoutTs) { webrtc::test::ScopedFieldTrials field_trials( "WebRTC-Audio-GainController2/Enabled," "switch_to_agc2:false," "disallow_transient_suppressor_usage:true/"); auto apm = AudioProcessingBuilder().SetConfig(GetParam()).Create(); constexpr int kSampleRateHz = 48000; constexpr int kNumChannels = 1; std::array buffer; float* channel_pointers[] = {buffer.data()}; StreamConfig stream_config(kSampleRateHz, kNumChannels); Random random_generator(2341U); constexpr int kFramesToProcess = 10; int volume = 100; for (int i = 0; i < kFramesToProcess; ++i) { SCOPED_TRACE(i); RandomizeSampleVector(&random_generator, buffer); apm->set_stream_analog_level(volume); ASSERT_EQ(apm->ProcessStream(channel_pointers, stream_config, stream_config, channel_pointers), kNoErr); volume = apm->recommended_stream_analog_level(); } } TEST_P(Agc2FieldTrialParametrizedTest, ProcessSucceedsWhenSwitchToFullAgc2WithTs) { AudioProcessing::Config config = GetParam(); if (!config.transient_suppression.enabled) { GTEST_SKIP() << "TS is disabled, skip."; } webrtc::test::ScopedFieldTrials field_trials( "WebRTC-Audio-GainController2/Enabled," "switch_to_agc2:true," "disallow_transient_suppressor_usage:false/"); auto apm = AudioProcessingBuilder().SetConfig(config).Create(); constexpr int kSampleRateHz = 48000; constexpr int kNumChannels = 1; std::array buffer; float* channel_pointers[] = {buffer.data()}; StreamConfig stream_config(kSampleRateHz, kNumChannels); Random random_generator(2341U); constexpr int kFramesToProcess = 10; int volume = 100; for (int i = 0; i < kFramesToProcess; ++i) { SCOPED_TRACE(i); RandomizeSampleVector(&random_generator, buffer); apm->set_stream_analog_level(volume); ASSERT_EQ(apm->ProcessStream(channel_pointers, stream_config, stream_config, channel_pointers), kNoErr); volume = apm->recommended_stream_analog_level(); } } TEST_P(Agc2FieldTrialParametrizedTest, ProcessSucceedsWhenSwitchToFullAgc2WithoutTs) { webrtc::test::ScopedFieldTrials field_trials( "WebRTC-Audio-GainController2/Enabled," "switch_to_agc2:true," "disallow_transient_suppressor_usage:true/"); auto apm = AudioProcessingBuilder().SetConfig(GetParam()).Create(); constexpr int kSampleRateHz = 48000; constexpr int kNumChannels = 1; std::array buffer; float* channel_pointers[] = {buffer.data()}; StreamConfig stream_config(kSampleRateHz, kNumChannels); Random random_generator(2341U); constexpr int kFramesToProcess = 10; int volume = 100; for (int i = 0; i < kFramesToProcess; ++i) { SCOPED_TRACE(i); RandomizeSampleVector(&random_generator, buffer); apm->set_stream_analog_level(volume); ASSERT_EQ(apm->ProcessStream(channel_pointers, stream_config, stream_config, channel_pointers), kNoErr); volume = apm->recommended_stream_analog_level(); } } INSTANTIATE_TEST_SUITE_P( AudioProcessingImplTest, Agc2FieldTrialParametrizedTest, ::testing::Values( // Full AGC1, TS disabled. AudioProcessing::Config{ .transient_suppression = {.enabled = false}, .gain_controller1 = {.enabled = true, .analog_gain_controller = {.enabled = true, .enable_digital_adaptive = true}}, .gain_controller2 = {.enabled = false}}, // Full AGC1, TS enabled. AudioProcessing::Config{ .transient_suppression = {.enabled = true}, .gain_controller1 = {.enabled = true, .analog_gain_controller = {.enabled = true, .enable_digital_adaptive = true}}, .gain_controller2 = {.enabled = false}}, // Hybrid AGC, TS disabled. AudioProcessing::Config{ .transient_suppression = {.enabled = false}, .gain_controller1 = {.enabled = true, .analog_gain_controller = {.enabled = true, .enable_digital_adaptive = false}}, .gain_controller2 = {.enabled = true, .adaptive_digital = {.enabled = true}}}, // Hybrid AGC, TS enabled. AudioProcessing::Config{ .transient_suppression = {.enabled = true}, .gain_controller1 = {.enabled = true, .analog_gain_controller = {.enabled = true, .enable_digital_adaptive = false}}, .gain_controller2 = {.enabled = true, .adaptive_digital = {.enabled = true}}})); TEST(AudioProcessingImplTest, CanDisableTransientSuppressor) { constexpr AudioProcessing::Config kOriginal = { .transient_suppression = {.enabled = false}}; // Test config application via `AudioProcessing` ctor. auto adjusted = AudioProcessingBuilder().SetConfig(kOriginal).Create()->GetConfig(); EXPECT_FALSE(adjusted.transient_suppression.enabled); // Test config application via `AudioProcessing::ApplyConfig()`. auto apm = AudioProcessingBuilder().Create(); apm->ApplyConfig(kOriginal); adjusted = apm->GetConfig(); EXPECT_FALSE(apm->GetConfig().transient_suppression.enabled); } TEST(AudioProcessingImplTest, CanEnableTs) { constexpr AudioProcessing::Config kOriginal = { .transient_suppression = {.enabled = true}}; // Test config application via `AudioProcessing` ctor. auto adjusted = AudioProcessingBuilder().SetConfig(kOriginal).Create()->GetConfig(); EXPECT_TRUE(adjusted.transient_suppression.enabled); // Test config application via `AudioProcessing::ApplyConfig()`. auto apm = AudioProcessingBuilder().Create(); apm->ApplyConfig(kOriginal); adjusted = apm->GetConfig(); EXPECT_TRUE(adjusted.transient_suppression.enabled); } TEST(AudioProcessingImplTest, CanDisableTsWithAgc2FieldTrialDisabled) { constexpr AudioProcessing::Config kOriginal = { .transient_suppression = {.enabled = false}}; webrtc::test::ScopedFieldTrials field_trials( "WebRTC-Audio-GainController2/Disabled/"); // Test config application via `AudioProcessing` ctor. auto adjusted = AudioProcessingBuilder().SetConfig(kOriginal).Create()->GetConfig(); EXPECT_FALSE(adjusted.transient_suppression.enabled); // Test config application via `AudioProcessing::ApplyConfig()`. auto apm = AudioProcessingBuilder().Create(); apm->ApplyConfig(kOriginal); adjusted = apm->GetConfig(); EXPECT_FALSE(apm->GetConfig().transient_suppression.enabled); } TEST(AudioProcessingImplTest, CanEnableTsWithAgc2FieldTrialDisabled) { constexpr AudioProcessing::Config kOriginal = { .transient_suppression = {.enabled = true}}; webrtc::test::ScopedFieldTrials field_trials( "WebRTC-Audio-GainController2/Disabled/"); // Test config application via `AudioProcessing` ctor. auto adjusted = AudioProcessingBuilder().SetConfig(kOriginal).Create()->GetConfig(); EXPECT_TRUE(adjusted.transient_suppression.enabled); // Test config application via `AudioProcessing::ApplyConfig()`. auto apm = AudioProcessingBuilder().Create(); apm->ApplyConfig(kOriginal); adjusted = apm->GetConfig(); EXPECT_TRUE(adjusted.transient_suppression.enabled); } TEST(AudioProcessingImplTest, CanDisableTsWithAgc2FieldTrialEnabledAndUsageAllowed) { constexpr AudioProcessing::Config kOriginal = { .transient_suppression = {.enabled = false}}; webrtc::test::ScopedFieldTrials field_trials( "WebRTC-Audio-GainController2/Enabled," "disallow_transient_suppressor_usage:false/"); // Test config application via `AudioProcessing` ctor. auto adjusted = AudioProcessingBuilder().SetConfig(kOriginal).Create()->GetConfig(); EXPECT_FALSE(adjusted.transient_suppression.enabled); // Test config application via `AudioProcessing::ApplyConfig()`. auto apm = AudioProcessingBuilder().Create(); apm->ApplyConfig(kOriginal); adjusted = apm->GetConfig(); EXPECT_FALSE(adjusted.transient_suppression.enabled); } TEST(AudioProcessingImplTest, CanEnableTsWithAgc2FieldTrialEnabledAndUsageAllowed) { constexpr AudioProcessing::Config kOriginal = { .transient_suppression = {.enabled = true}}; webrtc::test::ScopedFieldTrials field_trials( "WebRTC-Audio-GainController2/Enabled," "disallow_transient_suppressor_usage:false/"); // Test config application via `AudioProcessing` ctor. auto adjusted = AudioProcessingBuilder().SetConfig(kOriginal).Create()->GetConfig(); EXPECT_TRUE(adjusted.transient_suppression.enabled); // Test config application via `AudioProcessing::ApplyConfig()`. auto apm = AudioProcessingBuilder().Create(); apm->ApplyConfig(kOriginal); adjusted = apm->GetConfig(); EXPECT_TRUE(adjusted.transient_suppression.enabled); } TEST(AudioProcessingImplTest, CannotEnableTsWithAgc2FieldTrialEnabledAndUsageDisallowed) { constexpr AudioProcessing::Config kOriginal = { .transient_suppression = {.enabled = true}}; webrtc::test::ScopedFieldTrials field_trials( "WebRTC-Audio-GainController2/Enabled," "disallow_transient_suppressor_usage:true/"); // Test config application via `AudioProcessing` ctor. auto adjusted = AudioProcessingBuilder().SetConfig(kOriginal).Create()->GetConfig(); EXPECT_FALSE(adjusted.transient_suppression.enabled); // Test config application via `AudioProcessing::ApplyConfig()`. auto apm = AudioProcessingBuilder().Create(); apm->ApplyConfig(kOriginal); adjusted = apm->GetConfig(); EXPECT_FALSE(apm->GetConfig().transient_suppression.enabled); } } // namespace webrtc