| // Copyright 2025 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/loopback_reference_manager.h" |
| |
| #include <memory> |
| |
| #include "base/synchronization/waitable_event.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/task/thread_pool.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/task_environment.h" |
| #include "input_controller.h" |
| #include "input_stream.h" |
| #include "media/audio/audio_device_description.h" |
| #include "media/audio/audio_io.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_parameters.h" |
| #include "testing/gmock/include/gmock/gmock-matchers.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::testing::_; |
| using ::testing::Eq; |
| using ::testing::NiceMock; |
| using ::testing::Return; |
| using ::testing::StrictMock; |
| |
| namespace audio { |
| namespace { |
| |
| using ReferenceOpenOutcome = ReferenceSignalProvider::ReferenceOpenOutcome; |
| using OpenOutcome = media::AudioInputStream::OpenOutcome; |
| using AudioInputCallback = media::AudioInputStream::AudioInputCallback; |
| |
| class MockAudioLog : public media::AudioLog { |
| public: |
| MockAudioLog() {} |
| MOCK_METHOD2(OnCreated, |
| void(const media::AudioParameters& params, |
| const std::string& device_id)); |
| |
| MOCK_METHOD0(OnStarted, void()); |
| MOCK_METHOD0(OnStopped, void()); |
| MOCK_METHOD0(OnClosed, void()); |
| MOCK_METHOD0(OnError, void()); |
| MOCK_METHOD1(OnSetVolume, void(double)); |
| MOCK_METHOD1(OnProcessingStateChanged, void(const std::string&)); |
| void OnLogMessage(const std::string& message) override { |
| logged_messages += message; |
| } |
| std::string logged_messages = ""; |
| }; |
| 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 LocalMockAudioManager : public media::MockAudioManager { |
| public: |
| LocalMockAudioManager() |
| : media::MockAudioManager( |
| std::make_unique<media::TestAudioThread>(false)) {} |
| ~LocalMockAudioManager() override = default; |
| |
| MOCK_METHOD(media::AudioParameters, |
| GetInputStreamParameters, |
| (const std::string& device_id), |
| (override)); |
| MOCK_METHOD(media::AudioInputStream*, |
| MakeAudioInputStream, |
| (const media::AudioParameters& params, |
| const std::string& device_id, |
| const LogCallback& log_callback), |
| (override)); |
| MOCK_METHOD(std::unique_ptr<media::AudioLog>, |
| CreateAudioLog, |
| (media::AudioLogFactory::AudioComponent component, |
| int component_id), |
| (override)); |
| }; |
| |
| class MockListener : public ReferenceOutput::Listener { |
| public: |
| MockListener() = default; |
| ~MockListener() override = default; |
| |
| MOCK_METHOD(void, |
| OnPlayoutData, |
| (const media::AudioBus&, int, base::TimeDelta), |
| (override)); |
| MOCK_METHOD(void, OnReferenceStreamError, (), (override)); |
| }; |
| |
| class LoopbackReferenceManagerTest : public ::testing::Test { |
| public: |
| LoopbackReferenceManagerTest() |
| : loopback_reference_manager_( |
| std::make_unique<LoopbackReferenceManager>(&audio_manager_)) {} |
| |
| LoopbackReferenceManagerTest(const LoopbackReferenceManagerTest&) = delete; |
| LoopbackReferenceManagerTest& operator=(const LoopbackReferenceManagerTest&) = |
| delete; |
| |
| ~LoopbackReferenceManagerTest() override { |
| // Ensure the loopback manager is destroyed before audio_manager_ |
| loopback_reference_manager_.reset(); |
| audio_manager_.Shutdown(); |
| } |
| |
| protected: |
| // Helper to quickly setup the mock expectations for creating a new loopback |
| // stream. |
| std::unique_ptr<StrictMock<MockAudioInputStream>> ExpectCreateLoopbackStream( |
| int component_id); |
| |
| // Used for testing that a specific OpenOutcome is translated to a |
| // ReferenceOpenOutcome. |
| void TestStreamOpenError( |
| OpenOutcome loopback_open_outcome, |
| ReferenceOpenOutcome expected_reference_open_outcome); |
| |
| base::test::TaskEnvironment task_environment_{ |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_ = |
| base::SingleThreadTaskRunner::GetCurrentDefault(); |
| scoped_refptr<base::SingleThreadTaskRunner> audio_thread_task_runner_ = |
| base::ThreadPool::CreateSingleThreadTaskRunner( |
| {}, |
| base::SingleThreadTaskRunnerThreadMode::DEDICATED); |
| StrictMock<LocalMockAudioManager> audio_manager_; |
| std::unique_ptr<LoopbackReferenceManager> loopback_reference_manager_; |
| |
| const media::AudioParameters loopback_params_ = |
| media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY, |
| media::ChannelLayoutConfig::Stereo(), |
| 48000, |
| 480); |
| const std::string loopback_device_id_ = |
| media::AudioDeviceDescription::kLoopbackAllDevicesId; |
| const std::string output_device_id_ = |
| media::AudioDeviceDescription::kDefaultDeviceId; |
| }; |
| |
| // Simple matcher for verifying AudioBus data. |
| MATCHER_P(FirstSampleEquals, sample_value, "") { |
| return arg.channel(0)[0] == sample_value; |
| } |
| |
| // There is no equality operator for AudioParameters, so we need to create a |
| // matcher instead. |
| MATCHER_P(AudioParamsMatches, expected, "") { |
| return !(arg < expected) && !(expected < arg); |
| } |
| |
| // Matcher which sends a test message on a log callback. |
| MATCHER_P(RunLogCallback, test_message, "") { |
| arg.Run(test_message); |
| return true; |
| } |
| |
| std::unique_ptr<StrictMock<MockAudioInputStream>> |
| LoopbackReferenceManagerTest::ExpectCreateLoopbackStream(int component_id) { |
| auto mock_input_stream = std::make_unique<StrictMock<MockAudioInputStream>>(); |
| EXPECT_CALL(audio_manager_, GetInputStreamParameters(loopback_device_id_)) |
| .WillOnce(Return(loopback_params_)); |
| EXPECT_CALL(audio_manager_, |
| CreateAudioLog( |
| media::AudioLogFactory::AudioComponent::kAudioInputController, |
| component_id)) |
| .WillOnce(Return(std::make_unique<NiceMock<MockAudioLog>>())); |
| EXPECT_CALL(audio_manager_, |
| MakeAudioInputStream(AudioParamsMatches(loopback_params_), |
| loopback_device_id_, _)) |
| .WillOnce(Return(mock_input_stream.get())); |
| EXPECT_CALL(*mock_input_stream, Open()) |
| .WillOnce(Return(media::AudioInputStream::OpenOutcome::kSuccess)); |
| EXPECT_CALL(*mock_input_stream, Stop()); |
| EXPECT_CALL(*mock_input_stream, Close()); |
| return mock_input_stream; |
| } |
| |
| TEST_F(LoopbackReferenceManagerTest, DistributesAudioToListenersSameProvider) { |
| StrictMock<MockAudioInputStream> mock_input_stream; |
| std::unique_ptr<StrictMock<MockAudioLog>> mock_audio_log = |
| std::make_unique<StrictMock<MockAudioLog>>(); |
| // Retain a pointer to the mock audio log to expect calls to it. |
| StrictMock<MockAudioLog>* mock_audio_log_raw_ptr = mock_audio_log.get(); |
| std::unique_ptr<media::AudioBus> audio_bus = |
| media::AudioBus::Create(loopback_params_); |
| std::unique_ptr<ReferenceSignalProvider> reference_signal_provider = |
| loopback_reference_manager_->GetReferenceSignalProvider(); |
| |
| // Setup the expectations for starting the loopback stream. |
| EXPECT_CALL(audio_manager_, GetInputStreamParameters(loopback_device_id_)) |
| .WillOnce(Return(loopback_params_)); |
| EXPECT_CALL(audio_manager_, |
| CreateAudioLog( |
| media::AudioLogFactory::AudioComponent::kAudioInputController, |
| 1000000)) |
| .WillOnce(Return(std::move(mock_audio_log))); |
| EXPECT_CALL(audio_manager_, |
| MakeAudioInputStream(AudioParamsMatches(loopback_params_), |
| loopback_device_id_, |
| RunLogCallback("LOG CALLBACK TEST MESSAGE"))) |
| .WillOnce(Return(&mock_input_stream)); |
| EXPECT_CALL(mock_input_stream, Open()) |
| .WillOnce(Return(media::AudioInputStream::OpenOutcome::kSuccess)); |
| EXPECT_CALL( |
| *mock_audio_log_raw_ptr, |
| OnCreated(AudioParamsMatches(loopback_params_), loopback_device_id_)); |
| EXPECT_CALL(*mock_audio_log_raw_ptr, OnStarted()); |
| // MockAudioInputStream::Start() is not a mocked function, it is implemented |
| // to capture the callback, so we do not need to EXPECT it. |
| |
| // Add the first listener. This should create the stream. |
| StrictMock<MockListener> mock_listener_1; |
| reference_signal_provider->StartListening(&mock_listener_1, |
| output_device_id_); |
| EXPECT_THAT(mock_audio_log_raw_ptr->logged_messages, |
| ::testing::HasSubstr("LOG CALLBACK TEST MESSAGE")); |
| |
| CHECK(mock_input_stream.captured_callback_); |
| AudioInputCallback* audio_callback = *(mock_input_stream.captured_callback_); |
| |
| // Send some data, which should be delivered to the first listener. |
| EXPECT_CALL(mock_listener_1, |
| OnPlayoutData(FirstSampleEquals(111), |
| loopback_params_.sample_rate(), base::TimeDelta())); |
| audio_bus->channel(0)[0] = 111; |
| audio_callback->OnData(audio_bus.get(), base::TimeTicks::Now(), 0, {}); |
| |
| // Add another listener. This shouldn't create a new stream. |
| StrictMock<MockListener> mock_listener_2; |
| reference_signal_provider->StartListening(&mock_listener_2, |
| output_device_id_); |
| |
| // Send some more data, which should be delivered to both listeners |
| EXPECT_CALL(mock_listener_1, |
| OnPlayoutData(FirstSampleEquals(222), |
| loopback_params_.sample_rate(), base::TimeDelta())); |
| EXPECT_CALL(mock_listener_2, |
| OnPlayoutData(FirstSampleEquals(222), |
| loopback_params_.sample_rate(), base::TimeDelta())); |
| audio_bus->channel(0)[0] = 222; |
| audio_callback->OnData(audio_bus.get(), base::TimeTicks::Now(), 0, {}); |
| |
| // Remove the first listener, which should not stop the loopback stream. |
| reference_signal_provider->StopListening(&mock_listener_1); |
| |
| // Send some more data, which should only be delivered to the second listener. |
| EXPECT_CALL(mock_listener_2, |
| OnPlayoutData(FirstSampleEquals(333), |
| loopback_params_.sample_rate(), base::TimeDelta())); |
| audio_bus->channel(0)[0] = 333; |
| audio_callback->OnData(audio_bus.get(), base::TimeTicks::Now(), 0, {}); |
| |
| // Remove the second listener, this will stop the loopback stream. |
| EXPECT_CALL(mock_input_stream, Stop()); |
| EXPECT_CALL(*mock_audio_log_raw_ptr, OnStopped()); |
| EXPECT_CALL(mock_input_stream, Close()); |
| EXPECT_CALL(*mock_audio_log_raw_ptr, OnClosed()); |
| reference_signal_provider->StopListening(&mock_listener_2); |
| // Superfluous calls to StopListening should be no-ops. |
| reference_signal_provider->StopListening(&mock_listener_2); |
| } |
| |
| TEST_F(LoopbackReferenceManagerTest, |
| DistributesAudioToListenersSeparateProviders) { |
| std::unique_ptr<media::AudioBus> audio_bus = |
| media::AudioBus::Create(loopback_params_); |
| |
| // These should use he same underlying stream. |
| std::unique_ptr<ReferenceSignalProvider> reference_signal_provider_1 = |
| loopback_reference_manager_->GetReferenceSignalProvider(); |
| std::unique_ptr<ReferenceSignalProvider> reference_signal_provider_2 = |
| loopback_reference_manager_->GetReferenceSignalProvider(); |
| |
| // Setup the expectations for starting the loopback stream. This should only |
| // happen once, even with two providers. |
| std::unique_ptr<StrictMock<MockAudioInputStream>> mock_input_stream = |
| ExpectCreateLoopbackStream(1000000); |
| |
| // Add the first listener to the first provider. This should create the |
| // stream. |
| StrictMock<MockListener> mock_listener_1; |
| reference_signal_provider_1->StartListening(&mock_listener_1, |
| output_device_id_); |
| CHECK(mock_input_stream->captured_callback_); |
| AudioInputCallback* audio_callback = *(mock_input_stream->captured_callback_); |
| |
| // Send some data, which should be delivered to the first listener. |
| EXPECT_CALL(mock_listener_1, |
| OnPlayoutData(FirstSampleEquals(111), |
| loopback_params_.sample_rate(), base::TimeDelta())); |
| audio_bus->channel(0)[0] = 111; |
| audio_callback->OnData(audio_bus.get(), base::TimeTicks::Now(), 0, {}); |
| |
| // Add a listener to the second provider. This shouldn't create a new stream. |
| StrictMock<MockListener> mock_listener_2; |
| reference_signal_provider_2->StartListening(&mock_listener_2, |
| output_device_id_); |
| |
| // Send some more data, which should be delivered to both listeners. |
| EXPECT_CALL(mock_listener_1, |
| OnPlayoutData(FirstSampleEquals(222), |
| loopback_params_.sample_rate(), base::TimeDelta())); |
| EXPECT_CALL(mock_listener_2, |
| OnPlayoutData(FirstSampleEquals(222), |
| loopback_params_.sample_rate(), base::TimeDelta())); |
| audio_bus->channel(0)[0] = 222; |
| audio_callback->OnData(audio_bus.get(), base::TimeTicks::Now(), 0, {}); |
| |
| // Remove the listener from the first provider. The stream should not stop. |
| reference_signal_provider_1->StopListening(&mock_listener_1); |
| |
| // Send some more data, which should only be delivered to the listener on the |
| // second provider. |
| EXPECT_CALL(mock_listener_2, |
| OnPlayoutData(FirstSampleEquals(333), |
| loopback_params_.sample_rate(), base::TimeDelta())); |
| audio_bus->channel(0)[0] = 333; |
| audio_callback->OnData(audio_bus.get(), base::TimeTicks::Now(), 0, {}); |
| |
| // Remove the listener from the second provider. This should stop the loopback |
| // stream. |
| reference_signal_provider_2->StopListening(&mock_listener_2); |
| } |
| |
| TEST_F(LoopbackReferenceManagerTest, |
| OpensAndClosesStreamWithMultipleListenCycles) { |
| std::unique_ptr<media::AudioBus> audio_bus = |
| media::AudioBus::Create(loopback_params_); |
| std::unique_ptr<ReferenceSignalProvider> reference_signal_provider = |
| loopback_reference_manager_->GetReferenceSignalProvider(); |
| StrictMock<MockListener> mock_listener; |
| |
| // --- First listen cycle --- |
| std::unique_ptr<StrictMock<MockAudioInputStream>> mock_input_stream_1 = |
| ExpectCreateLoopbackStream(1000000); |
| |
| reference_signal_provider->StartListening(&mock_listener, output_device_id_); |
| CHECK(mock_input_stream_1->captured_callback_); |
| AudioInputCallback* audio_callback_1 = |
| *(mock_input_stream_1->captured_callback_); |
| |
| // Send some data to the listener. |
| EXPECT_CALL(mock_listener, |
| OnPlayoutData(FirstSampleEquals(111), |
| loopback_params_.sample_rate(), base::TimeDelta())); |
| audio_bus->channel(0)[0] = 111; |
| audio_callback_1->OnData(audio_bus.get(), base::TimeTicks::Now(), 0, {}); |
| |
| // Remove the listener, which closes the new stream. |
| reference_signal_provider->StopListening(&mock_listener); |
| |
| // --- Second listen cycle --- |
| std::unique_ptr<StrictMock<MockAudioInputStream>> mock_input_stream_2 = |
| ExpectCreateLoopbackStream(1000001); |
| |
| reference_signal_provider->StartListening(&mock_listener, output_device_id_); |
| CHECK(mock_input_stream_2->captured_callback_); |
| AudioInputCallback* audio_callback_2 = |
| *(mock_input_stream_2->captured_callback_); |
| |
| // Send some data to the listener again, via the new stream. |
| EXPECT_CALL(mock_listener, |
| OnPlayoutData(FirstSampleEquals(222), |
| loopback_params_.sample_rate(), base::TimeDelta())); |
| audio_bus->channel(0)[0] = 222; |
| audio_callback_2->OnData(audio_bus.get(), base::TimeTicks::Now(), 0, {}); |
| |
| // Remove the listener again, which closes the new stream. |
| reference_signal_provider->StopListening(&mock_listener); |
| } |
| |
| TEST_F(LoopbackReferenceManagerTest, StreamCreateError) { |
| std::unique_ptr<ReferenceSignalProvider> reference_signal_provider = |
| loopback_reference_manager_->GetReferenceSignalProvider(); |
| base::HistogramTester histogram_tester; |
| |
| EXPECT_CALL(audio_manager_, GetInputStreamParameters(loopback_device_id_)) |
| .WillOnce(Return(loopback_params_)); |
| EXPECT_CALL(audio_manager_, |
| CreateAudioLog( |
| media::AudioLogFactory::AudioComponent::kAudioInputController, |
| 1000000)) |
| .WillOnce(Return(std::make_unique<NiceMock<MockAudioLog>>())); |
| // Fail to create the loopback stream |
| EXPECT_CALL(audio_manager_, |
| MakeAudioInputStream(AudioParamsMatches(loopback_params_), |
| loopback_device_id_, _)) |
| .WillOnce(Return(nullptr)); |
| |
| // Attempt to start the stream but since MakeAudioInputStream returns nullptr, |
| // StartListening should return an error. |
| StrictMock<MockListener> mock_listener; |
| ReferenceOpenOutcome outcome = reference_signal_provider->StartListening( |
| &mock_listener, output_device_id_); |
| EXPECT_EQ(outcome, ReferenceOpenOutcome::STREAM_CREATE_ERROR); |
| histogram_tester.ExpectUniqueSample( |
| "Media.Audio.LoopbackReference.OpenResult", |
| static_cast<int>(ReferenceOpenOutcome::STREAM_CREATE_ERROR), 1); |
| histogram_tester.ExpectTotalCount( |
| "Media.Audio.LoopbackReference.HadRuntimeError", 0); |
| // Destroy the loopback manager explicitly to trigger the destructor which |
| // should generate a histogram. |
| loopback_reference_manager_.reset(); |
| histogram_tester.ExpectBucketCount( |
| "Media.Audio.LoopbackReference.HadRuntimeError", false, 1); |
| } |
| |
| void LoopbackReferenceManagerTest::TestStreamOpenError( |
| OpenOutcome loopback_open_outcome, |
| ReferenceOpenOutcome expected_reference_open_outcome) { |
| StrictMock<MockAudioInputStream> mock_input_stream; |
| std::unique_ptr<ReferenceSignalProvider> reference_signal_provider = |
| loopback_reference_manager_->GetReferenceSignalProvider(); |
| base::HistogramTester histogram_tester; |
| |
| EXPECT_CALL(audio_manager_, GetInputStreamParameters(loopback_device_id_)) |
| .WillOnce(Return(loopback_params_)); |
| EXPECT_CALL(audio_manager_, |
| CreateAudioLog( |
| media::AudioLogFactory::AudioComponent::kAudioInputController, |
| 1000000)) |
| .WillOnce(Return(std::make_unique<NiceMock<MockAudioLog>>())); |
| EXPECT_CALL(audio_manager_, |
| MakeAudioInputStream(AudioParamsMatches(loopback_params_), |
| loopback_device_id_, _)) |
| .WillOnce(Return(&mock_input_stream)); |
| EXPECT_CALL(mock_input_stream, Open()) |
| .WillOnce(Return(loopback_open_outcome)); |
| EXPECT_CALL(mock_input_stream, Close()); |
| |
| // Attempt to start the stream but since mock_input_strean.Open() returns an |
| // error, StartListening should return an error and call |
| // mock_input_strean.Close(). |
| StrictMock<MockListener> mock_listener; |
| ReferenceOpenOutcome outcome = reference_signal_provider->StartListening( |
| &mock_listener, output_device_id_); |
| EXPECT_EQ(outcome, expected_reference_open_outcome); |
| histogram_tester.ExpectUniqueSample( |
| "Media.Audio.LoopbackReference.OpenResult", |
| static_cast<int>(expected_reference_open_outcome), 1); |
| histogram_tester.ExpectTotalCount( |
| "Media.Audio.LoopbackReference.HadRuntimeError", 0); |
| } |
| |
| TEST_F(LoopbackReferenceManagerTest, StreamOpenError) { |
| TestStreamOpenError(OpenOutcome::kFailed, |
| ReferenceOpenOutcome::STREAM_OPEN_ERROR); |
| } |
| |
| TEST_F(LoopbackReferenceManagerTest, StreamOpenSystemPermissionsError) { |
| TestStreamOpenError( |
| OpenOutcome::kFailedSystemPermissions, |
| ReferenceOpenOutcome::STREAM_OPEN_SYSTEM_PERMISSIONS_ERROR); |
| } |
| |
| TEST_F(LoopbackReferenceManagerTest, StreamOpenDeviceInUseError) { |
| TestStreamOpenError(OpenOutcome::kFailedInUse, |
| ReferenceOpenOutcome::STREAM_OPEN_DEVICE_IN_USE_ERROR); |
| } |
| |
| TEST_F(LoopbackReferenceManagerTest, OnReferenceStreamError) { |
| std::unique_ptr<media::AudioBus> audio_bus = |
| media::AudioBus::Create(loopback_params_); |
| base::HistogramTester histogram_tester; |
| |
| // These should use he same underlying core. |
| std::unique_ptr<ReferenceSignalProvider> reference_signal_provider_1 = |
| loopback_reference_manager_->GetReferenceSignalProvider(); |
| std::unique_ptr<ReferenceSignalProvider> reference_signal_provider_2 = |
| loopback_reference_manager_->GetReferenceSignalProvider(); |
| |
| // Setup the expectations for starting the loopback stream. |
| std::unique_ptr<StrictMock<MockAudioInputStream>> mock_input_stream_1 = |
| ExpectCreateLoopbackStream(1000000); |
| |
| // Add the first listener to the first provider. This should create the |
| // stream. |
| StrictMock<MockListener> mock_listener_1; |
| EXPECT_EQ(ReferenceOpenOutcome::SUCCESS, |
| reference_signal_provider_1->StartListening(&mock_listener_1, |
| output_device_id_)); |
| CHECK(mock_input_stream_1->captured_callback_); |
| AudioInputCallback* audio_callback = |
| *(mock_input_stream_1->captured_callback_); |
| |
| // Report an error, causing the core to be invalidated, and scheduled to be |
| // deleted. Note that this will not normally be called on the main thread, but |
| // we do so in this test to check the various cases in which the scheduled |
| // deletion interacts with StartListening() and GetReferenceSignalProvider(). |
| audio_callback->OnError(); |
| |
| // We have had an error but destruction of the core has not yet occurred, so |
| // listening will be successful. |
| StrictMock<MockListener> mock_listener_2; |
| EXPECT_EQ(ReferenceOpenOutcome::SUCCESS, |
| reference_signal_provider_2->StartListening(&mock_listener_2, |
| output_device_id_)); |
| |
| // Fast-forwarding will run the scheduled deletion on the main thread, which |
| // should send the error to the listeners. |
| EXPECT_CALL(mock_listener_1, OnReferenceStreamError()); |
| EXPECT_CALL(mock_listener_2, OnReferenceStreamError()); |
| task_environment_.FastForwardBy(base::TimeDelta()); |
| |
| { |
| // Trying to listen to the providers with broken cores will now fail. |
| StrictMock<MockListener> temp_mock_listener; |
| EXPECT_EQ(ReferenceOpenOutcome::STREAM_PREVIOUS_ERROR, |
| reference_signal_provider_1->StartListening(&temp_mock_listener, |
| output_device_id_)); |
| } |
| |
| // Get a new ReferenceSignalProvider after the error has been processed. |
| std::unique_ptr<ReferenceSignalProvider> reference_signal_provider_3 = |
| loopback_reference_manager_->GetReferenceSignalProvider(); |
| |
| std::unique_ptr<StrictMock<MockAudioInputStream>> mock_input_stream_2 = |
| ExpectCreateLoopbackStream(1000001); |
| { |
| // reference_signal_provider_3 was gotten after the error, so it should have |
| // a new core. |
| StrictMock<MockListener> temp_mock_listener; |
| EXPECT_EQ(ReferenceOpenOutcome::SUCCESS, |
| reference_signal_provider_3->StartListening(&temp_mock_listener, |
| output_device_id_)); |
| reference_signal_provider_3->StopListening(&temp_mock_listener); |
| } |
| |
| histogram_tester.ExpectBucketCount( |
| "Media.Audio.LoopbackReference.OpenResult", |
| static_cast<int>(ReferenceOpenOutcome::SUCCESS), 3); |
| histogram_tester.ExpectBucketCount( |
| "Media.Audio.LoopbackReference.OpenResult", |
| static_cast<int>(ReferenceOpenOutcome::STREAM_PREVIOUS_ERROR), 1); |
| histogram_tester.ExpectUniqueSample( |
| "Media.Audio.LoopbackReference.HadRuntimeError", true, 1); |
| } |
| |
| TEST_F(LoopbackReferenceManagerTest, StopListeningAfterOnError) { |
| std::unique_ptr<media::AudioBus> audio_bus = |
| media::AudioBus::Create(loopback_params_); |
| |
| // These should use he same underlying core. |
| std::unique_ptr<ReferenceSignalProvider> reference_signal_provider = |
| loopback_reference_manager_->GetReferenceSignalProvider(); |
| |
| // Setup the expectations for starting the loopback stream. |
| std::unique_ptr<StrictMock<MockAudioInputStream>> mock_input_stream = |
| ExpectCreateLoopbackStream(1000000); |
| |
| // Add the first listener to the first provider. This should create the |
| // stream. |
| StrictMock<MockListener> mock_listener; |
| EXPECT_EQ(ReferenceOpenOutcome::SUCCESS, |
| reference_signal_provider->StartListening(&mock_listener, |
| output_device_id_)); |
| CHECK(mock_input_stream->captured_callback_); |
| AudioInputCallback* audio_callback = *(mock_input_stream->captured_callback_); |
| |
| // Send an error. |
| audio_callback->OnError(); |
| |
| // Stop listening to the provider before the error has been processed. |
| reference_signal_provider->StopListening(&mock_listener); |
| |
| // mock_listener will not get an error, because it has already stopped |
| // listening. |
| task_environment_.FastForwardBy(base::TimeDelta()); |
| } |
| |
| // Implementation of ReferenceOutput::Listener that expects OnPlayoutData and |
| // OnReferenceStreamError to be called on specific threads. |
| class ThreadedReferenceListener : public ReferenceOutput::Listener { |
| public: |
| ThreadedReferenceListener( |
| scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, |
| scoped_refptr<base::SingleThreadTaskRunner> audio_thread_task_runner, |
| base::WaitableEvent* on_playout_data_event) |
| : main_task_runner_(main_task_runner), |
| audio_thread_task_runner_(audio_thread_task_runner), |
| on_playout_data_event_(on_playout_data_event) {} |
| ~ThreadedReferenceListener() override = default; |
| |
| MOCK_METHOD(void, |
| MockOnPlayoutData, |
| (const media::AudioBus&, int, base::TimeDelta), |
| ()); |
| MOCK_METHOD(void, MockOnReferenceStreamError, (), ()); |
| |
| void OnPlayoutData(const media::AudioBus& audio_bus, |
| int sample_rate, |
| base::TimeDelta audio_delay) final { |
| // If this is called on the main thread, the test might deadlock. |
| CHECK(audio_thread_task_runner_->BelongsToCurrentThread()); |
| MockOnPlayoutData(audio_bus, sample_rate, audio_delay); |
| on_playout_data_event_->Signal(); |
| } |
| |
| void OnReferenceStreamError() final { |
| CHECK(main_task_runner_->BelongsToCurrentThread()); |
| MockOnReferenceStreamError(); |
| } |
| |
| private: |
| scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; |
| scoped_refptr<base::SingleThreadTaskRunner> audio_thread_task_runner_; |
| raw_ptr<base::WaitableEvent> on_playout_data_event_; |
| }; |
| |
| TEST_F(LoopbackReferenceManagerTest, DeliversAudioOnAudioThread) { |
| std::unique_ptr<media::AudioBus> audio_bus = |
| media::AudioBus::Create(loopback_params_); |
| |
| std::unique_ptr<ReferenceSignalProvider> reference_signal_provider = |
| loopback_reference_manager_->GetReferenceSignalProvider(); |
| |
| // Setup the expectations for starting the loopback stream. |
| std::unique_ptr<StrictMock<MockAudioInputStream>> mock_input_stream = |
| ExpectCreateLoopbackStream(1000000); |
| |
| // Create a listener which checks that the methods are called on the right |
| // threads. |
| base::WaitableEvent on_playout_data_event; |
| StrictMock<ThreadedReferenceListener> threaded_listener( |
| main_task_runner_, audio_thread_task_runner_, &on_playout_data_event); |
| |
| // Add the first listener to the first provider. This should create the |
| // stream. |
| reference_signal_provider->StartListening(&threaded_listener, |
| output_device_id_); |
| CHECK(mock_input_stream->captured_callback_); |
| AudioInputCallback* audio_callback = *(mock_input_stream->captured_callback_); |
| |
| audio_bus->channel(0)[0] = 111; |
| EXPECT_CALL( |
| threaded_listener, |
| MockOnPlayoutData(FirstSampleEquals(111), loopback_params_.sample_rate(), |
| base::TimeDelta())); |
| audio_thread_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&AudioInputCallback::OnData, |
| base::Unretained(audio_callback), audio_bus.get(), |
| base::TimeTicks::Now(), 0, media::AudioGlitchInfo())); |
| |
| // on_playout_data_event will be signaled when OnPlayoutData is called. |
| // TimedWait returns true when the signal is called, and false on timeout. |
| EXPECT_TRUE(on_playout_data_event.TimedWait(base::Seconds(1))); |
| |
| reference_signal_provider->StopListening(&threaded_listener); |
| } |
| |
| TEST_F(LoopbackReferenceManagerTest, DeliversErrorsOnMainThread) { |
| std::unique_ptr<media::AudioBus> audio_bus = |
| media::AudioBus::Create(loopback_params_); |
| |
| std::unique_ptr<ReferenceSignalProvider> reference_signal_provider = |
| loopback_reference_manager_->GetReferenceSignalProvider(); |
| |
| // Setup the expectations for starting the loopback stream. |
| std::unique_ptr<StrictMock<MockAudioInputStream>> mock_input_stream = |
| ExpectCreateLoopbackStream(1000000); |
| |
| // Create a listener which checks that the methods are called on the right |
| // threads. |
| base::WaitableEvent on_playout_data_event; |
| StrictMock<ThreadedReferenceListener> threaded_listener( |
| main_task_runner_, audio_thread_task_runner_, &on_playout_data_event); |
| |
| // Add the first listener to the first provider. This should create the |
| // stream. |
| reference_signal_provider->StartListening(&threaded_listener, |
| output_device_id_); |
| CHECK(mock_input_stream->captured_callback_); |
| AudioInputCallback* audio_callback = *(mock_input_stream->captured_callback_); |
| |
| base::WaitableEvent fired_error_event; |
| audio_thread_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce( |
| [](base::WaitableEvent* fired_error_event, |
| AudioInputCallback* audio_callback) { |
| audio_callback->OnError(); |
| fired_error_event->Signal(); |
| }, |
| &fired_error_event, audio_callback)); |
| |
| // TimedWait returns true when the signal is called, and false on timeout. |
| EXPECT_TRUE(fired_error_event.TimedWait(base::Seconds(1))); |
| |
| // At this point OnError has been called on the audio thread, scheduling the |
| // error on the main thread. Fast-forwarding will run the deletion on the main |
| // thread, which should send the error to the listener. |
| EXPECT_CALL(threaded_listener, MockOnReferenceStreamError()); |
| task_environment_.FastForwardBy(base::TimeDelta()); |
| } |
| |
| } // namespace |
| } // namespace audio |