|  | /* | 
|  | *  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 <algorithm> | 
|  | #include <array> | 
|  | #include <memory> | 
|  | #include <tuple> | 
|  |  | 
|  | #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(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<MockEchoControl>()) {} | 
|  | // Returns a pointer to the next MockEchoControl that this factory creates. | 
|  | MockEchoControl* GetNext() const { return next_mock_.get(); } | 
|  | std::unique_ptr<EchoControl> Create(int sample_rate_hz, | 
|  | int num_render_channels, | 
|  | int num_capture_channels) override { | 
|  | std::unique_ptr<EchoControl> mock = std::move(next_mock_); | 
|  | next_mock_ = std::make_unique<MockEchoControl>(); | 
|  | return mock; | 
|  | } | 
|  |  | 
|  | private: | 
|  | std::unique_ptr<MockEchoControl> 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<const float> render_audio) override { | 
|  | last_render_audio_first_sample_ = render_audio[0]; | 
|  | analyze_render_audio_called_ = true; | 
|  | } | 
|  | void AnalyzeCaptureAudio(rtc::ArrayView<const float> 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<float> 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<float, kSampleRateHz / 100> 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<int16_t, kMaxNumChannels * kMaxSampleRateHz / 100> 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<AudioProcessing> 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<int16_t, kNumChannels * kSampleRateHz / 100> 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<AudioProcessing> 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<int16_t, kNumChannels * kSampleRateHz / 100> 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<AudioProcessing> 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<int16_t, kNumChannels * kSampleRateHz / 100> 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<MockEchoControlFactory>(); | 
|  | const MockEchoControlFactory* echo_control_factory_ptr = | 
|  | echo_control_factory.get(); | 
|  |  | 
|  | rtc::scoped_refptr<AudioProcessing> apm = | 
|  | AudioProcessingBuilderForTesting() | 
|  | .SetEchoControlFactory(std::move(echo_control_factory)) | 
|  | .Create(); | 
|  |  | 
|  | constexpr int16_t kAudioLevel = 10000; | 
|  | constexpr int kSampleRateHz = 48000; | 
|  | constexpr int kNumChannels = 2; | 
|  | std::array<int16_t, kNumChannels * kSampleRateHz / 100> 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<MockEchoControlFactory>(); | 
|  | const auto* echo_control_factory_ptr = echo_control_factory.get(); | 
|  |  | 
|  | rtc::scoped_refptr<AudioProcessing> 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<int16_t, kNumChannels * kSampleRateHz / 100> 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<MockEchoControlFactory>(); | 
|  | const auto* echo_control_factory_ptr = echo_control_factory.get(); | 
|  |  | 
|  | rtc::scoped_refptr<AudioProcessing> 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<int16_t, kNumChannels * kSampleRateHz / 100> 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<MockEchoControlFactory>(); | 
|  | const auto* echo_control_factory_ptr = echo_control_factory.get(); | 
|  |  | 
|  | rtc::scoped_refptr<AudioProcessing> 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<int16_t, kNumChannels * kSampleRateHz / 100> 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<float, kSampleRateHz / 100> 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<AudioProcessing> 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<float, kSampleRateHz / 100> 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<MockEchoControlFactory>(); | 
|  | const auto* echo_control_factory_ptr = echo_control_factory.get(); | 
|  |  | 
|  | rtc::scoped_refptr<AudioProcessing> 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<int16_t, kNumChannels * kSampleRateHz / 100> 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<TestEchoDetector>(); | 
|  | std::unique_ptr<CustomProcessing> test_render_pre_processor( | 
|  | new TestRenderPreProcessor()); | 
|  | // Create APM injecting the test echo detector and render pre-processor. | 
|  | rtc::scoped_refptr<AudioProcessing> 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<int16_t, kNumChannels * kSampleRateHz / 100> frame; | 
|  | StreamConfig stream_config(kSampleRateHz, kNumChannels); | 
|  |  | 
|  | constexpr float kAudioLevelFloat = static_cast<float>(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<AudioProcessingImpl>(); | 
|  | 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<AudioProcessing> 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<float, kSampleRateHz / 100> buffer; | 
|  | std::array<float, kSampleRateHz / 100> 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<AudioProcessingImpl>(); | 
|  | 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<AudioProcessingImpl>(); | 
|  | 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<int> {}; | 
|  |  | 
|  | // 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<int, int, AudioProcessing::Config>> { | 
|  | 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<std::vector<float>> channels_; | 
|  | std::vector<float*> 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<AudioProcessing::Config> {}; | 
|  |  | 
|  | 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<float, kSampleRateHz / 100> 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<float, kSampleRateHz / 100> 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<float, kSampleRateHz / 100> 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<float, kSampleRateHz / 100> 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 |