| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "services/audio/input_controller.h" |
| |
| #include <memory> |
| #include <string_view> |
| #include <utility> |
| |
| #include "base/run_loop.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/task_environment.h" |
| #include "base/time/time.h" |
| #include "input_controller.h" |
| #include "media/audio/aecdump_recording_manager.h" |
| #include "media/audio/audio_io.h" |
| #include "media/audio/audio_manager.h" |
| #include "media/audio/fake_audio_input_stream.h" |
| #include "media/audio/fake_audio_log_factory.h" |
| #include "media/audio/fake_audio_manager.h" |
| #include "media/audio/mock_audio_manager.h" |
| #include "media/audio/test_audio_thread.h" |
| #include "media/base/audio_bus.h" |
| #include "media/base/audio_glitch_info.h" |
| #include "media/base/audio_processing.h" |
| #include "media/base/media_switches.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "services/audio/audio_processor_handler.h" |
| #include "services/audio/loopback_signal_provider.h" |
| #include "services/audio/processing_audio_fifo.h" |
| #include "services/audio/reference_output.h" |
| #include "services/audio/reference_signal_provider.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::testing::_; |
| using ::testing::AtLeast; |
| using ::testing::Exactly; |
| using ::testing::InvokeWithoutArgs; |
| using ::testing::NiceMock; |
| using ::testing::NotNull; |
| using ::testing::Return; |
| using ::testing::StrictMock; |
| |
| namespace audio { |
| |
| namespace { |
| |
| const int kSampleRate = media::AudioParameters::kAudioCDSampleRate; |
| const media::ChannelLayoutConfig kChannelLayoutConfig = |
| media::ChannelLayoutConfig::Stereo(); |
| const int kSamplesPerPacket = kSampleRate / 100; |
| |
| // InputController will poll once every second, so wait at most a bit |
| // more than that for the callbacks. |
| constexpr base::TimeDelta kOnMutePollInterval = base::Milliseconds(1000); |
| |
| using ReferenceOpenOutcome = ReferenceSignalProvider::ReferenceOpenOutcome; |
| |
| // Struct to hold the parameters for UMA delay tests. |
| struct DelayUmaTestData { |
| ReferenceSignalProvider::Type provider_type; |
| const char* expected_uma_name; |
| }; |
| |
| std::unique_ptr<LoopbackMixin> DoNotCreateLoopbackMixin( |
| std::string_view device_id, |
| const media::AudioParameters& params, |
| LoopbackMixin::OnDataCallback on_data_callback) { |
| return nullptr; |
| } |
| } // namespace |
| |
| class MockInputControllerEventHandler : public InputController::EventHandler { |
| public: |
| MockInputControllerEventHandler() = default; |
| |
| MockInputControllerEventHandler(const MockInputControllerEventHandler&) = |
| delete; |
| MockInputControllerEventHandler& operator=( |
| const MockInputControllerEventHandler&) = delete; |
| |
| void OnLog(std::string_view) override {} |
| |
| MOCK_METHOD1(OnCreated, void(bool initially_muted)); |
| MOCK_METHOD1(OnError, void(InputController::ErrorCode error_code)); |
| MOCK_METHOD1(OnMuted, void(bool is_muted)); |
| }; |
| |
| class MockSyncWriter : public InputController::SyncWriter { |
| public: |
| MockSyncWriter() = default; |
| |
| MOCK_METHOD4(Write, |
| void(const media::AudioBus* data, |
| double volume, |
| base::TimeTicks capture_time, |
| const media::AudioGlitchInfo& audio_glitch_info)); |
| MOCK_METHOD0(Close, void()); |
| }; |
| |
| class MockAudioInputStream : public media::AudioInputStream { |
| public: |
| MOCK_METHOD0(Open, OpenOutcome()); |
| MOCK_METHOD0(Stop, void()); |
| MOCK_METHOD0(Close, void()); |
| MOCK_METHOD0(GetMaxVolume, double()); |
| MOCK_METHOD1(SetVolume, void(double volume)); |
| MOCK_METHOD0(GetVolume, double()); |
| MOCK_METHOD1(SetAutomaticGainControl, bool(bool enabled)); |
| MOCK_METHOD0(GetAutomaticGainControl, bool()); |
| MOCK_METHOD0(IsMuted, bool()); |
| MOCK_METHOD1(SetOutputDeviceForAec, |
| void(const std::string& output_device_id)); |
| |
| void Start(AudioInputCallback* callback) override { |
| captured_callback_ = callback; |
| } |
| |
| std::optional<AudioInputCallback*> captured_callback_; |
| }; |
| |
| class FakeLoopbackSignalProvider : public LoopbackSignalProviderInterface { |
| public: |
| FakeLoopbackSignalProvider(const media::AudioParameters& params, |
| LoopbackSignalProviderInterface* callback_receiver) |
| : params_(params), callback_receiver_(callback_receiver) {} |
| |
| ~FakeLoopbackSignalProvider() override = default; |
| |
| void Start() override { callback_receiver_->Start(); } |
| |
| base::TimeTicks PullLoopbackData(media::AudioBus* audio_bus, |
| base::TimeTicks capture_time, |
| double volume) override { |
| EXPECT_EQ(audio_bus->frames(), params_.frames_per_buffer()); |
| EXPECT_EQ(audio_bus->channels(), params_.channels()); |
| return callback_receiver_->PullLoopbackData(audio_bus, capture_time, |
| volume); |
| } |
| |
| private: |
| const media::AudioParameters params_; |
| raw_ptr<LoopbackSignalProviderInterface> const callback_receiver_; |
| }; |
| |
| class LoopbackMixinVerifier : public LoopbackSignalProviderInterface { |
| public: |
| LoopbackMixinVerifier() = default; |
| ~LoopbackMixinVerifier() override = default; |
| |
| // LoopbackSignalProviderInterface |
| MOCK_METHOD(void, Start, (), (override)); |
| MOCK_METHOD(base::TimeTicks, |
| PullLoopbackData, |
| (media::AudioBus * audio_bus, |
| base::TimeTicks capture_time, |
| double volume), |
| (override)); |
| |
| MOCK_METHOD(void, MaybeCreateMixinCalled, (std::string_view device_id)); |
| |
| std::unique_ptr<LoopbackMixin> MaybeCreateMixin( |
| std::string_view device_id, |
| const media::AudioParameters& params, |
| LoopbackMixin::OnDataCallback on_data_callback) { |
| MaybeCreateMixinCalled(device_id); |
| |
| if (device_id != media::AudioDeviceDescription::kLoopbackWithoutChromeId) { |
| return nullptr; |
| } |
| |
| return std::make_unique<LoopbackMixinUnderTest>( |
| std::make_unique<FakeLoopbackSignalProvider>(params, this), params, |
| std::move(on_data_callback)); |
| } |
| |
| private: |
| // Test class to access the protected constructor of LoopbackMixin. |
| class LoopbackMixinUnderTest : public LoopbackMixin { |
| public: |
| LoopbackMixinUnderTest( |
| std::unique_ptr<LoopbackSignalProviderInterface> signal_provider, |
| const media::AudioParameters& params, |
| OnDataCallback on_data_callback) |
| : LoopbackMixin(std::move(signal_provider), |
| params, |
| std::move(on_data_callback)) {} |
| }; |
| }; |
| |
| enum class AudioManagerType { MOCK, FAKE }; |
| |
| template <base::test::TaskEnvironment::TimeSource TimeSource = |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME, |
| AudioManagerType audio_manager_type = AudioManagerType::FAKE> |
| class TimeSourceInputControllerTest : public ::testing::Test { |
| public: |
| TimeSourceInputControllerTest() |
| : task_environment_(TimeSource), |
| audio_manager_( |
| audio_manager_type == AudioManagerType::FAKE |
| ? static_cast<std::unique_ptr<media::AudioManager>>( |
| std::make_unique<media::FakeAudioManager>( |
| std::make_unique<media::TestAudioThread>(false), |
| &log_factory_)) |
| : static_cast<std::unique_ptr<media::AudioManager>>( |
| std::make_unique<media::MockAudioManager>( |
| std::make_unique<media::TestAudioThread>(false)))), |
| aecdump_recording_manager_(audio_manager_->GetTaskRunner()), |
| params_(media::AudioParameters::AUDIO_FAKE, |
| kChannelLayoutConfig, |
| kSampleRate, |
| kSamplesPerPacket) {} |
| |
| TimeSourceInputControllerTest(const TimeSourceInputControllerTest&) = delete; |
| TimeSourceInputControllerTest& operator=( |
| const TimeSourceInputControllerTest&) = delete; |
| |
| ~TimeSourceInputControllerTest() override { |
| audio_manager_->Shutdown(); |
| task_environment_.RunUntilIdle(); |
| } |
| |
| protected: |
| void CreateAudioControllerWithMixin(const std::string& device_id) { |
| EXPECT_CALL(mixin_verifier_, MaybeCreateMixinCalled(device_id)); |
| controller_ = InputController::Create( |
| audio_manager_.get(), &event_handler_, &sync_writer_, |
| /*device_output_listener =*/nullptr, &aecdump_recording_manager_, |
| /*ml_model_manager=*/nullptr, |
| /*processing_config =*/nullptr, |
| // base::Unretained is safe: `mixin_verifier_` outlives `controller_` |
| base::BindOnce(&LoopbackMixinVerifier::MaybeCreateMixin, |
| base::Unretained(&mixin_verifier_)), |
| params_, device_id, false); |
| } |
| |
| virtual void CreateAudioController() { |
| controller_ = InputController::Create( |
| audio_manager_.get(), &event_handler_, &sync_writer_, |
| /*device_output_listener =*/nullptr, &aecdump_recording_manager_, |
| /*ml_model_manager=*/nullptr, |
| /*processing_config =*/nullptr, |
| base::BindOnce(&DoNotCreateLoopbackMixin), params_, |
| media::AudioDeviceDescription::kDefaultDeviceId, false); |
| } |
| |
| base::test::TaskEnvironment task_environment_; |
| |
| StrictMock<LoopbackMixinVerifier> mixin_verifier_; |
| std::unique_ptr<media::AudioManager> audio_manager_; |
| media::AecdumpRecordingManager aecdump_recording_manager_; |
| std::unique_ptr<InputController> controller_; |
| media::FakeAudioLogFactory log_factory_; |
| MockInputControllerEventHandler event_handler_; |
| MockSyncWriter sync_writer_; |
| media::AudioParameters params_; |
| }; |
| |
| using SystemTimeInputControllerTest = TimeSourceInputControllerTest< |
| base::test::TaskEnvironment::TimeSource::SYSTEM_TIME>; |
| using InputControllerTest = TimeSourceInputControllerTest<>; |
| using InputControllerTestWithMockAudioManager = TimeSourceInputControllerTest< |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME, |
| AudioManagerType::MOCK>; |
| |
| TEST_F(InputControllerTest, CreateAndCloseWithoutRecording) { |
| EXPECT_CALL(event_handler_, OnCreated(_)); |
| CreateAudioController(); |
| task_environment_.RunUntilIdle(); |
| ASSERT_TRUE(controller_.get()); |
| |
| EXPECT_CALL(sync_writer_, Close()); |
| controller_->Close(); |
| } |
| |
| // Test a normal call sequence of create, record and close. |
| // Note: Must use system time as MOCK_TIME does not support the threads created |
| // by the FakeAudioInputStream. The callbacks to sync_writer_.Write() are on |
| // that thread, and thus we must use SYSTEM_TIME. Also verifies that the |
| // NoAudioServiceAEC UMA is logged when InputController is created without a |
| // ReferenceSignalProvider. |
| TEST_F(SystemTimeInputControllerTest, CreateRecordAndClose) { |
| EXPECT_CALL(event_handler_, OnCreated(_)); |
| CreateAudioController(); |
| ASSERT_TRUE(controller_.get()); |
| |
| base::HistogramTester histogram_tester; |
| base::RunLoop loop; |
| |
| { |
| // Wait for Write() to be called ten times. |
| testing::InSequence s; |
| EXPECT_CALL(sync_writer_, Write(NotNull(), _, _, _)).Times(Exactly(9)); |
| EXPECT_CALL(sync_writer_, Write(NotNull(), _, _, _)) |
| .Times(AtLeast(1)) |
| .WillOnce(InvokeWithoutArgs([&]() { loop.Quit(); })); |
| } |
| controller_->Record(); |
| loop.Run(); |
| |
| testing::Mock::VerifyAndClearExpectations(&sync_writer_); |
| |
| EXPECT_CALL(sync_writer_, Close()); |
| controller_->Close(); |
| histogram_tester.ExpectTotalCount( |
| "Media.Audio.InputController.Delay.NoAudioServiceAEC", 10); |
| |
| task_environment_.RunUntilIdle(); |
| } |
| |
| TEST_F(InputControllerTestWithMockAudioManager, LoopbackMixinIsEngaged) { |
| MockAudioInputStream mock_stream; |
| static_cast<media::MockAudioManager*>(audio_manager_.get()) |
| ->SetMakeInputStreamCB(base::BindRepeating( |
| [](media::AudioInputStream* stream, |
| const media::AudioParameters& params, |
| const std::string& device_id) { return stream; }, |
| &mock_stream)); |
| auto audio_bus = media::AudioBus::Create(params_); |
| |
| CreateAudioControllerWithMixin( |
| media::AudioDeviceDescription::kLoopbackWithoutChromeId); |
| ASSERT_TRUE(controller_.get()); |
| |
| EXPECT_CALL(mixin_verifier_, Start()); |
| controller_->Record(); |
| |
| ASSERT_TRUE(mock_stream.captured_callback_); |
| media::AudioInputStream::AudioInputCallback* callback = |
| *mock_stream.captured_callback_; |
| |
| // Loopbackmixin::OnData() is called. |
| EXPECT_CALL(mixin_verifier_, PullLoopbackData(_, _, _)); |
| // Loopback passed data back to InputController. |
| EXPECT_CALL(sync_writer_, Write(NotNull(), _, _, _)); |
| callback->OnData(audio_bus.get(), base::TimeTicks(), 1, {}); |
| |
| EXPECT_CALL(sync_writer_, Close()); |
| controller_->Close(); |
| } |
| |
| TEST_F(InputControllerTestWithMockAudioManager, PropagatesGlitchInfo) { |
| MockAudioInputStream mock_stream; |
| static_cast<media::MockAudioManager*>(audio_manager_.get()) |
| ->SetMakeInputStreamCB(base::BindRepeating( |
| [](media::AudioInputStream* stream, |
| const media::AudioParameters& params, |
| const std::string& device_id) { return stream; }, |
| &mock_stream)); |
| auto audio_bus = media::AudioBus::Create(params_); |
| |
| CreateAudioController(); |
| ASSERT_TRUE(controller_.get()); |
| controller_->Record(); |
| |
| ASSERT_TRUE(mock_stream.captured_callback_); |
| media::AudioInputStream::AudioInputCallback* callback = |
| *mock_stream.captured_callback_; |
| |
| for (int i = 0; i < 5; i++) { |
| media::AudioGlitchInfo audio_glitch_info{ |
| .duration = base::Milliseconds(123 + i), .count = 5}; |
| EXPECT_CALL(sync_writer_, Write(NotNull(), _, _, audio_glitch_info)); |
| callback->OnData(audio_bus.get(), base::TimeTicks(), 1, audio_glitch_info); |
| testing::Mock::VerifyAndClearExpectations(&sync_writer_); |
| } |
| |
| EXPECT_CALL(sync_writer_, Close()); |
| controller_->Close(); |
| } |
| |
| TEST_F(InputControllerTest, RecordTwice) { |
| EXPECT_CALL(event_handler_, OnCreated(_)); |
| CreateAudioController(); |
| ASSERT_TRUE(controller_.get()); |
| |
| controller_->Record(); |
| controller_->Record(); |
| |
| EXPECT_CALL(sync_writer_, Close()); |
| controller_->Close(); |
| } |
| |
| TEST_F(InputControllerTest, CloseTwice) { |
| EXPECT_CALL(event_handler_, OnCreated(_)); |
| CreateAudioController(); |
| ASSERT_TRUE(controller_.get()); |
| |
| controller_->Record(); |
| |
| EXPECT_CALL(sync_writer_, Close()); |
| controller_->Close(); |
| |
| controller_->Close(); |
| } |
| |
| // Test that InputController sends OnMute callbacks properly. |
| TEST_F(InputControllerTest, TestOnmutedCallbackInitiallyUnmuted) { |
| EXPECT_CALL(event_handler_, OnCreated(false)); |
| EXPECT_CALL(sync_writer_, Close()); |
| |
| media::FakeAudioInputStream::SetGlobalMutedState(false); |
| CreateAudioController(); |
| ASSERT_TRUE(controller_.get()); |
| task_environment_.FastForwardBy(kOnMutePollInterval); |
| |
| testing::Mock::VerifyAndClearExpectations(&event_handler_); |
| EXPECT_CALL(event_handler_, OnMuted(true)); |
| media::FakeAudioInputStream::SetGlobalMutedState(true); |
| task_environment_.FastForwardBy(kOnMutePollInterval); |
| |
| testing::Mock::VerifyAndClearExpectations(&event_handler_); |
| EXPECT_CALL(event_handler_, OnMuted(false)); |
| media::FakeAudioInputStream::SetGlobalMutedState(false); |
| task_environment_.FastForwardBy(kOnMutePollInterval); |
| |
| controller_->Close(); |
| } |
| |
| TEST_F(InputControllerTest, TestOnmutedCallbackInitiallyMuted) { |
| EXPECT_CALL(event_handler_, OnCreated(true)); |
| EXPECT_CALL(sync_writer_, Close()); |
| |
| media::FakeAudioInputStream::SetGlobalMutedState(true); |
| CreateAudioController(); |
| ASSERT_TRUE(controller_.get()); |
| task_environment_.FastForwardBy(kOnMutePollInterval); |
| |
| testing::Mock::VerifyAndClearExpectations(&event_handler_); |
| |
| EXPECT_CALL(event_handler_, OnMuted(false)); |
| media::FakeAudioInputStream::SetGlobalMutedState(false); |
| task_environment_.FastForwardBy(kOnMutePollInterval); |
| |
| controller_->Close(); |
| } |
| |
| #if BUILDFLAG(CHROME_WIDE_ECHO_CANCELLATION) |
| class InputControllerTestHelper { |
| public: |
| explicit InputControllerTestHelper(InputController* controller) |
| : controller_(controller) {} |
| |
| bool IsUsingProcessingThread() { |
| return !!controller_->processing_fifo_.get(); |
| } |
| |
| // Adds a callback that will be run immediately after processing is done, in |
| // the same sequence as the processing callback. |
| // Should be called before starting the processing thread. |
| void AttachOnProcessedCallback(base::RepeatingClosure on_processed_callback) { |
| controller_->processing_fifo_->AttachOnProcessedCallbackForTesting( |
| std::move(on_processed_callback)); |
| } |
| |
| int FifoSize() { |
| CHECK(IsUsingProcessingThread()); |
| return controller_->processing_fifo_->fifo_size(); |
| } |
| |
| // Simulates the AudioProcessorHandler receiving an error. |
| void CallOnReferenceStreamError() { |
| // Cast to ReferenceOutput::Listener* to get public access to |
| // OnReferenceStreamError. |
| static_cast<ReferenceOutput::Listener*>( |
| controller_->audio_processor_handler_.get()) |
| ->OnReferenceStreamError(); |
| } |
| |
| private: |
| raw_ptr<InputController> controller_; |
| }; |
| |
| class MockReferenceSignalProvider : public ReferenceSignalProvider { |
| public: |
| MockReferenceSignalProvider() = default; |
| ~MockReferenceSignalProvider() override = default; |
| |
| MOCK_METHOD(Type, GetType, (), (const, override)); |
| MOCK_METHOD(ReferenceOpenOutcome, |
| StartListening, |
| (ReferenceOutput::Listener*, const std::string&), |
| (override)); |
| MOCK_METHOD(void, StopListening, (ReferenceOutput::Listener*), (override)); |
| }; |
| |
| template <base::test::TaskEnvironment::TimeSource TimeSource = |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME> |
| class TimeSourceInputControllerTestWithReferenceSignalProvider |
| : public TimeSourceInputControllerTest<TimeSource> { |
| protected: |
| void CreateAudioController() final { |
| // Must use |this| to access template base class members: |
| // https://stackoverflow.com/q/4643074 |
| this->controller_ = InputController::Create( |
| this->audio_manager_.get(), &this->event_handler_, &this->sync_writer_, |
| std::move(reference_signal_provider_unique_), |
| &this->aecdump_recording_manager_, /*ml_model_manager=*/nullptr, |
| std::move(processing_config_), |
| base::BindOnce(&DoNotCreateLoopbackMixin), this->params_, |
| media::AudioDeviceDescription::kDefaultDeviceId, false); |
| |
| helper_ = |
| std::make_unique<InputControllerTestHelper>(this->controller_.get()); |
| } |
| |
| enum class AudioProcessingType { |
| // No effects, audio does not need to be modified. |
| kNone, |
| // Effects that modify audio but do not require a playout reference signal. |
| kWithoutPlayoutReference, |
| // Effects that require a playout reference signal. |
| kWithPlayoutReference |
| }; |
| |
| void SetupProcessingConfig(AudioProcessingType audio_processing_type) { |
| media::AudioProcessingSettings settings; |
| settings.echo_cancellation = false; |
| settings.noise_suppression = false; |
| settings.automatic_gain_control = false; |
| settings.multi_channel_capture_processing = false; |
| switch (audio_processing_type) { |
| case AudioProcessingType::kNone: |
| break; |
| case AudioProcessingType::kWithoutPlayoutReference: |
| settings.noise_suppression = true; |
| break; |
| case AudioProcessingType::kWithPlayoutReference: |
| settings.echo_cancellation = true; |
| break; |
| } |
| processing_config_ = media::mojom::AudioProcessingConfig::New( |
| remote_controls_.BindNewPipeAndPassReceiver(), settings); |
| } |
| |
| // Used for testing that a specific OpenOutcome is translated to a specific |
| // ErrorCode. |
| void TestReferenceOpenError(ReferenceOpenOutcome reference_open_outcome, |
| InputController::ErrorCode expected_error_code); |
| |
| // This may or may not be moved into the input controller on creation, |
| // depending on if the InputController is going to do echo cancellation. |
| std::unique_ptr<NiceMock<MockReferenceSignalProvider>> |
| reference_signal_provider_unique_ = |
| std::make_unique<NiceMock<MockReferenceSignalProvider>>(); |
| // The MockReferenceSignalProvider will be destroyed automatically when the |
| // InputController is destroyed. We retain a pointer to it to be able to |
| // expect mock calls. It will be dangling between the destruction of the |
| // InputController and the destruction of the test suite, so we disable |
| // dangling pointer detection. |
| raw_ptr<NiceMock<MockReferenceSignalProvider>, DisableDanglingPtrDetection> |
| reference_signal_provider_ = reference_signal_provider_unique_.get(); |
| media::mojom::AudioProcessingConfigPtr processing_config_; |
| mojo::Remote<media::mojom::AudioProcessorControls> remote_controls_; |
| std::unique_ptr<InputControllerTestHelper> helper_; |
| }; |
| |
| using SystemTimeInputControllerTestWithReferenceSignalProvider = |
| TimeSourceInputControllerTestWithReferenceSignalProvider< |
| base::test::TaskEnvironment::TimeSource::SYSTEM_TIME>; |
| using InputControllerTestWithReferenceSignalProvider = |
| TimeSourceInputControllerTestWithReferenceSignalProvider<>; |
| |
| TEST_F(InputControllerTestWithReferenceSignalProvider, |
| CreateWithAudioProcessingConfig_WithSomeEffectsEnabled) { |
| SetupProcessingConfig(AudioProcessingType::kWithoutPlayoutReference); |
| |
| CreateAudioController(); |
| |
| ASSERT_TRUE(controller_.get()); |
| |
| base::RunLoop loop; |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE, |
| loop.QuitClosure()); |
| loop.Run(); |
| |
| // |controller_| should have bound the pending AudioProcessorControls |
| // receiver it received through its ctor. |
| EXPECT_TRUE(remote_controls_.is_connected()); |
| |
| // InputController shouldn't offload processing work when there is no playout |
| // reference. |
| EXPECT_FALSE(helper_->IsUsingProcessingThread()); |
| |
| // Test cleanup. |
| controller_->Close(); |
| } |
| |
| TEST_F(InputControllerTestWithReferenceSignalProvider, |
| CreateWithAudioProcessingConfig_WithoutEnablingEffects) { |
| SetupProcessingConfig(AudioProcessingType::kNone); |
| |
| CreateAudioController(); |
| |
| ASSERT_TRUE(controller_.get()); |
| |
| base::RunLoop loop; |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE, |
| loop.QuitClosure()); |
| loop.Run(); |
| |
| // When all forms of audio processing are disabled, |controller_| should |
| // ignore the pending AudioProcessorControls Receiver it received in its |
| // ctor. |
| EXPECT_FALSE(remote_controls_.is_connected()); |
| |
| // InputController shouldn't spin up a processing thread if it's not needed. |
| EXPECT_FALSE(helper_->IsUsingProcessingThread()); |
| |
| // Test cleanup. |
| controller_->Close(); |
| } |
| |
| TEST_F(InputControllerTestWithReferenceSignalProvider, |
| CreateWithAudioProcessingConfig_VerifyFifoUsage) { |
| SetupProcessingConfig(AudioProcessingType::kWithPlayoutReference); |
| |
| CreateAudioController(); |
| |
| ASSERT_TRUE(controller_.get()); |
| |
| EXPECT_TRUE(helper_->IsUsingProcessingThread()); |
| |
| // Test cleanup. |
| controller_->Close(); |
| } |
| |
| TEST_F( |
| InputControllerTestWithReferenceSignalProvider, |
| CreateWithAudioProcessingConfig_DoesNotListenForPlayoutReferenceIfNotRequired) { |
| const std::string kOutputDeviceId = "0x123"; |
| |
| EXPECT_CALL(*reference_signal_provider_, StartListening(_, _)).Times(0); |
| EXPECT_CALL(*reference_signal_provider_, StopListening(_)).Times(0); |
| |
| SetupProcessingConfig(AudioProcessingType::kWithoutPlayoutReference); |
| CreateAudioController(); |
| |
| ASSERT_TRUE(controller_.get()); |
| |
| controller_->Record(); |
| controller_->SetOutputDeviceForAec(kOutputDeviceId); |
| |
| // InputController spin up a processing thread if it's not needed. |
| EXPECT_FALSE(helper_->IsUsingProcessingThread()); |
| |
| controller_->Close(); |
| |
| EXPECT_FALSE(helper_->IsUsingProcessingThread()); |
| } |
| |
| TEST_F(InputControllerTestWithReferenceSignalProvider, |
| RecordBeforeSetOutputForAec) { |
| const std::string kOutputDeviceId = "0x123"; |
| |
| // Calling Record() will start listening to the "" device by default. |
| EXPECT_CALL(*reference_signal_provider_, StartListening(_, "")) |
| .Times(1) |
| .WillOnce(Return(ReferenceOpenOutcome::SUCCESS)); |
| EXPECT_CALL(*reference_signal_provider_, StartListening(_, kOutputDeviceId)) |
| .Times(1) |
| .WillOnce(Return(ReferenceOpenOutcome::SUCCESS)); |
| EXPECT_CALL(*reference_signal_provider_, StopListening(_)).Times(1); |
| |
| SetupProcessingConfig(AudioProcessingType::kWithPlayoutReference); |
| |
| CreateAudioController(); |
| |
| ASSERT_TRUE(controller_.get()); |
| |
| controller_->Record(); |
| controller_->SetOutputDeviceForAec(kOutputDeviceId); |
| |
| // InputController should offload processing to its own thread. |
| EXPECT_TRUE(helper_->IsUsingProcessingThread()); |
| |
| controller_->Close(); |
| |
| // The processing thread should be stopped after controller has closed. |
| EXPECT_FALSE(helper_->IsUsingProcessingThread()); |
| } |
| |
| TEST_F(InputControllerTestWithReferenceSignalProvider, |
| RecordAfterSetOutputForAec) { |
| const std::string kOutputDeviceId = "0x123"; |
| |
| EXPECT_CALL(*reference_signal_provider_, StartListening(_, kOutputDeviceId)) |
| .Times(1) |
| .WillOnce(Return(ReferenceOpenOutcome::SUCCESS)); |
| EXPECT_CALL(*reference_signal_provider_, StopListening(_)).Times(1); |
| |
| SetupProcessingConfig(AudioProcessingType::kWithPlayoutReference); |
| CreateAudioController(); |
| |
| ASSERT_TRUE(controller_.get()); |
| |
| controller_->SetOutputDeviceForAec(kOutputDeviceId); |
| controller_->Record(); |
| |
| // InputController should offload processing to its own thread. |
| EXPECT_TRUE(helper_->IsUsingProcessingThread()); |
| |
| controller_->Close(); |
| |
| // The processing thread should be stopped after controller has closed. |
| EXPECT_FALSE(helper_->IsUsingProcessingThread()); |
| } |
| |
| TEST_F(InputControllerTestWithReferenceSignalProvider, FifoSize) { |
| const std::string kOutputDeviceId = "0x123"; |
| EXPECT_CALL(*reference_signal_provider_, StartListening(_, kOutputDeviceId)) |
| .Times(1) |
| .WillOnce(Return(ReferenceOpenOutcome::SUCCESS)); |
| EXPECT_CALL(*reference_signal_provider_, StopListening(_)).Times(1); |
| |
| SetupProcessingConfig(AudioProcessingType::kWithPlayoutReference); |
| CreateAudioController(); |
| |
| ASSERT_TRUE(controller_.get()); |
| |
| controller_->SetOutputDeviceForAec(kOutputDeviceId); |
| controller_->Record(); |
| |
| EXPECT_TRUE(helper_->IsUsingProcessingThread()); |
| EXPECT_EQ(helper_->FifoSize(), InputController::kProcessingFifoSize); |
| |
| // InputController should offload processing to its own thread. |
| EXPECT_TRUE(helper_->IsUsingProcessingThread()); |
| |
| controller_->Close(); |
| EXPECT_FALSE(helper_->IsUsingProcessingThread()); |
| } |
| |
| TEST_F(InputControllerTestWithReferenceSignalProvider, ChangeOutputForAec) { |
| const std::string kOutputDeviceId = "0x123"; |
| const std::string kOtherOutputDeviceId = "0x987"; |
| |
| // Each output ID should receive one call to StartListening(). |
| EXPECT_CALL(*reference_signal_provider_, StartListening(_, kOutputDeviceId)) |
| .Times(1) |
| .WillOnce(Return(ReferenceOpenOutcome::SUCCESS)); |
| EXPECT_CALL(*reference_signal_provider_, |
| StartListening(_, kOtherOutputDeviceId)) |
| .Times(1) |
| .WillOnce(Return(ReferenceOpenOutcome::SUCCESS)); |
| |
| // StopListening() should be called once, regardless of how many ID changes. |
| EXPECT_CALL(*reference_signal_provider_, StopListening(_)).Times(1); |
| |
| SetupProcessingConfig(AudioProcessingType::kWithPlayoutReference); |
| CreateAudioController(); |
| |
| ASSERT_TRUE(controller_.get()); |
| |
| controller_->SetOutputDeviceForAec(kOutputDeviceId); |
| controller_->Record(); |
| controller_->SetOutputDeviceForAec(kOtherOutputDeviceId); |
| controller_->Close(); |
| } |
| |
| // Test a normal call sequence of create, record and close when audio processing |
| // is enabled. |
| // Note: Must use system time as MOCK_TIME does not support the threads created |
| // by the FakeAudioInputStream. The callbacks to sync_writer_.Write() are on |
| // that thread, and thus we must use SYSTEM_TIME. |
| TEST_F(SystemTimeInputControllerTestWithReferenceSignalProvider, |
| CreateRecordAndClose) { |
| EXPECT_CALL(event_handler_, OnCreated(_)); |
| SetupProcessingConfig(AudioProcessingType::kWithPlayoutReference); |
| CreateAudioController(); |
| |
| bool data_processed_by_fifo = false; |
| |
| // Test that the fifo is enabled. |
| auto main_sequence = base::SequencedTaskRunner::GetCurrentDefault(); |
| auto verify_data_processed = [&data_processed_by_fifo, main_sequence]() { |
| // Data should be processed on its own thread. |
| EXPECT_FALSE(main_sequence->RunsTasksInCurrentSequence()); |
| |
| data_processed_by_fifo = true; |
| }; |
| |
| helper_->AttachOnProcessedCallback( |
| base::BindLambdaForTesting(verify_data_processed)); |
| |
| ASSERT_TRUE(controller_.get()); |
| |
| base::RunLoop loop; |
| |
| { |
| // Wait for Write() to be called ten times. |
| testing::InSequence s; |
| EXPECT_CALL(sync_writer_, Write(NotNull(), _, _, _)).Times(Exactly(9)); |
| EXPECT_CALL(sync_writer_, Write(NotNull(), _, _, _)) |
| .Times(AtLeast(1)) |
| .WillOnce(InvokeWithoutArgs([&]() { loop.Quit(); })); |
| } |
| controller_->Record(); |
| |
| // InputController should offload processing to its own thread if the |
| // processing FIFO is enabled. |
| EXPECT_TRUE(helper_->IsUsingProcessingThread()); |
| |
| loop.Run(); |
| |
| testing::Mock::VerifyAndClearExpectations(&sync_writer_); |
| |
| EXPECT_CALL(sync_writer_, Close()); |
| controller_->Close(); |
| |
| // The processing thread should be stopped after controller has closed. |
| EXPECT_FALSE(helper_->IsUsingProcessingThread()); |
| |
| task_environment_.RunUntilIdle(); |
| |
| EXPECT_TRUE(data_processed_by_fifo); |
| } |
| |
| TEST_F(InputControllerTestWithReferenceSignalProvider, ReferenceStreamError) { |
| const std::string kOutputDeviceId = "0x123"; |
| EXPECT_CALL(*reference_signal_provider_, StartListening(_, kOutputDeviceId)) |
| .Times(1) |
| .WillOnce(Return(ReferenceOpenOutcome::SUCCESS)); |
| EXPECT_CALL(*reference_signal_provider_, StopListening(_)).Times(1); |
| |
| SetupProcessingConfig(AudioProcessingType::kWithPlayoutReference); |
| CreateAudioController(); |
| |
| ASSERT_TRUE(controller_.get()); |
| |
| controller_->SetOutputDeviceForAec(kOutputDeviceId); |
| controller_->Record(); |
| EXPECT_TRUE(helper_->IsUsingProcessingThread()); |
| |
| // Sending a ReferenceStreamError should result in an error being sent to the |
| // EventHandler. |
| EXPECT_CALL(event_handler_, OnError(InputController::REFERENCE_STREAM_ERROR)); |
| helper_->CallOnReferenceStreamError(); |
| |
| controller_->Close(); |
| } |
| |
| class ParameterizedInputControllerUmaDelayTest |
| : public SystemTimeInputControllerTestWithReferenceSignalProvider, |
| public ::testing::WithParamInterface<DelayUmaTestData> {}; |
| |
| // Test a normal call sequence of create, record and close when audio processing |
| // is enabled but also verify that capture delays are recorded correctly using |
| // two different UMA names where the name depends on the type returned by the |
| // ReferenceSignalProvider. |
| // Based on |
| // SystemTimeInputControllerTestWithReferenceSignalProvider.CreateRecordAndClose. |
| TEST_P(ParameterizedInputControllerUmaDelayTest, CreateRecordAndClose) { |
| const DelayUmaTestData& param = GetParam(); |
| |
| EXPECT_CALL(event_handler_, OnCreated(_)); |
| // Use the provider_type from the parameter. |
| EXPECT_CALL(*reference_signal_provider_, GetType()) |
| .WillOnce(testing::Return(param.provider_type)); |
| EXPECT_CALL(*reference_signal_provider_, StartListening(_, _)).Times(1); |
| EXPECT_CALL(*reference_signal_provider_, StopListening(_)).Times(1); |
| SetupProcessingConfig(AudioProcessingType::kWithPlayoutReference); |
| CreateAudioController(); |
| |
| bool data_processed_by_fifo = false; |
| |
| // Test that the fifo is enabled. |
| auto main_sequence = base::SequencedTaskRunner::GetCurrentDefault(); |
| auto verify_data_processed = [&data_processed_by_fifo, main_sequence]() { |
| // Data should be processed on its own thread. |
| EXPECT_FALSE(main_sequence->RunsTasksInCurrentSequence()); |
| |
| data_processed_by_fifo = true; |
| }; |
| |
| helper_->AttachOnProcessedCallback( |
| base::BindLambdaForTesting(verify_data_processed)); |
| |
| ASSERT_TRUE(controller_.get()); |
| |
| base::HistogramTester histogram_tester; |
| base::RunLoop loop; |
| |
| { |
| // Wait for Write() to be called ten times. |
| testing::InSequence s; |
| EXPECT_CALL(sync_writer_, Write(NotNull(), _, _, _)).Times(Exactly(9)); |
| EXPECT_CALL(sync_writer_, Write(NotNull(), _, _, _)) |
| .Times(AtLeast(1)) |
| .WillOnce(InvokeWithoutArgs([&]() { loop.Quit(); })); |
| } |
| controller_->Record(); |
| |
| // InputController should offload processing to its own thread if the |
| // processing FIFO is enabled. |
| EXPECT_TRUE(helper_->IsUsingProcessingThread()); |
| |
| loop.Run(); |
| |
| testing::Mock::VerifyAndClearExpectations(&sync_writer_); |
| |
| EXPECT_CALL(sync_writer_, Close()); |
| controller_->Close(); |
| |
| // Use the expected_uma_name from the parameter. |
| histogram_tester.ExpectTotalCount(param.expected_uma_name, 10); |
| |
| // The processing thread should be stopped after controller has closed. |
| EXPECT_FALSE(helper_->IsUsingProcessingThread()); |
| |
| EXPECT_TRUE(data_processed_by_fifo); |
| } |
| |
| // Instantiate the UMA test suite with the two scenarios. |
| INSTANTIATE_TEST_SUITE_P( |
| AECTypeDelayUMAs, |
| ParameterizedInputControllerUmaDelayTest, |
| ::testing::Values( |
| DelayUmaTestData{ReferenceSignalProvider::Type::kOutputDeviceMixer, |
| "Media.Audio.InputController.Delay.ChromeWideAEC"}, |
| DelayUmaTestData{ReferenceSignalProvider::Type::kLoopbackReference, |
| "Media.Audio.InputController.Delay.LoopbackAEC"}), |
| // Provide a human-readable name for each test instance. |
| [](const testing::TestParamInfo< |
| ParameterizedInputControllerUmaDelayTest::ParamType>& info) { |
| switch (info.param.provider_type) { |
| case ReferenceSignalProvider::Type::kOutputDeviceMixer: |
| return "ChromeWideAEC"; |
| case ReferenceSignalProvider::Type::kLoopbackReference: |
| return "LoopbackAEC"; |
| default: |
| return "UnknownAEC"; |
| } |
| }); |
| |
| template <> |
| void InputControllerTestWithReferenceSignalProvider::TestReferenceOpenError( |
| ReferenceOpenOutcome reference_open_outcome, |
| InputController::ErrorCode expected_error_code) { |
| const std::string kOutputDeviceId = "0x123"; |
| // Make StartListening return an error. |
| EXPECT_CALL(*reference_signal_provider_, StartListening(_, kOutputDeviceId)) |
| .Times(1) |
| .WillOnce(Return(reference_open_outcome)); |
| EXPECT_CALL(*reference_signal_provider_, StopListening(_)).Times(1); |
| |
| SetupProcessingConfig(AudioProcessingType::kWithPlayoutReference); |
| CreateAudioController(); |
| |
| ASSERT_TRUE(controller_.get()); |
| |
| controller_->SetOutputDeviceForAec(kOutputDeviceId); |
| |
| // Since StartListening will fail with an error, we should get an error on |
| // Record(). |
| EXPECT_CALL(event_handler_, OnError(expected_error_code)); |
| controller_->Record(); |
| controller_->Close(); |
| } |
| |
| TEST_F(InputControllerTestWithReferenceSignalProvider, |
| ReferenceStreamOpenError) { |
| TestReferenceOpenError(ReferenceOpenOutcome::STREAM_OPEN_ERROR, |
| InputController::REFERENCE_STREAM_OPEN_ERROR); |
| } |
| |
| TEST_F(InputControllerTestWithReferenceSignalProvider, |
| ReferenceStreamPreviousError) { |
| TestReferenceOpenError(ReferenceOpenOutcome::STREAM_PREVIOUS_ERROR, |
| InputController::REFERENCE_STREAM_ERROR); |
| } |
| |
| TEST_F(InputControllerTestWithReferenceSignalProvider, |
| ReferenceStreamCreateError) { |
| TestReferenceOpenError(ReferenceOpenOutcome::STREAM_CREATE_ERROR, |
| InputController::REFERENCE_STREAM_CREATE_ERROR); |
| } |
| |
| TEST_F(InputControllerTestWithReferenceSignalProvider, |
| ReferenceStreamOpenDeviceInUseError) { |
| TestReferenceOpenError( |
| ReferenceOpenOutcome::STREAM_OPEN_DEVICE_IN_USE_ERROR, |
| InputController::REFERENCE_STREAM_OPEN_DEVICE_IN_USE_ERROR); |
| } |
| |
| TEST_F(InputControllerTestWithReferenceSignalProvider, |
| ReferenceStreamOpenSystemPermissionsError) { |
| TestReferenceOpenError( |
| ReferenceOpenOutcome::STREAM_OPEN_SYSTEM_PERMISSIONS_ERROR, |
| InputController::REFERENCE_STREAM_OPEN_SYSTEM_PERMISSIONS_ERROR); |
| } |
| |
| TEST_F(InputControllerTestWithReferenceSignalProvider, |
| CreateWithoutProcessingConfig_DoesNotUseFifo) { |
| // This test simulates disabling ChromeWideEchoCancellation, in which case |
| // both the AudioProcessingConfig and the ReferenceSignalProvider are null. |
| |
| // Destroy the ReferenceSignalProvider before moving it into the |
| // InputController. |
| reference_signal_provider_unique_.reset(); |
| // Additionally, we intentionally do not call SetupProcessingConfig(), leaving |
| // the AudioProcessingConfig as null. |
| CreateAudioController(); |
| |
| ASSERT_TRUE(controller_.get()); |
| controller_->Record(); |
| |
| // We are not doing echo cancellation, so we are not using the fifo. |
| EXPECT_FALSE(helper_->IsUsingProcessingThread()); |
| |
| // Test cleanup. |
| controller_->Close(); |
| } |
| |
| #endif // BUILDFLAG(CHROME_WIDE_ECHO_CANCELLATION) |
| |
| } // namespace audio |