| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "services/audio/output_controller.h" |
| |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <vector> |
| |
| #include "base/barrier_closure.h" |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/callback_helpers.h" |
| #include "base/environment.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/optional.h" |
| #include "base/run_loop.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/strings/string_piece.h" |
| #include "base/test/test_message_loop.h" |
| #include "base/threading/thread.h" |
| #include "base/time/time.h" |
| #include "base/unguessable_token.h" |
| #include "media/audio/audio_device_description.h" |
| #include "media/audio/fake_audio_log_factory.h" |
| #include "media/audio/fake_audio_manager.h" |
| #include "media/audio/test_audio_thread.h" |
| #include "media/base/audio_bus.h" |
| #include "media/base/audio_parameters.h" |
| #include "media/base/gmock_callback_support.h" |
| #include "services/audio/loopback_group_member.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::testing::_; |
| using ::testing::AtLeast; |
| using ::testing::Invoke; |
| using ::testing::Mock; |
| using ::testing::NiceMock; |
| using ::testing::Return; |
| using ::testing::StrictMock; |
| |
| using media::AudioBus; |
| using media::AudioManager; |
| using media::AudioOutputStream; |
| using media::AudioParameters; |
| using media::RunClosure; |
| using media::RunOnceClosure; |
| |
| namespace audio { |
| namespace { |
| |
| constexpr int kSampleRate = AudioParameters::kAudioCDSampleRate; |
| constexpr media::ChannelLayout kChannelLayout = media::CHANNEL_LAYOUT_STEREO; |
| constexpr int kSamplesPerPacket = kSampleRate / 1000; |
| constexpr double kTestVolume = 0.25; |
| constexpr float kBufferNonZeroData = 1.0f; |
| |
| AudioParameters GetTestParams() { |
| // AudioManagerForControllerTest only creates FakeAudioOutputStreams |
| // behind-the-scenes. So, the use of PCM_LOW_LATENCY won't actually result in |
| // any real system audio output during these tests. |
| return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, kChannelLayout, |
| kSampleRate, kSamplesPerPacket); |
| } |
| |
| class MockOutputControllerEventHandler : public OutputController::EventHandler { |
| public: |
| MockOutputControllerEventHandler() = default; |
| |
| MOCK_METHOD0(OnControllerPlaying, void()); |
| MOCK_METHOD0(OnControllerPaused, void()); |
| MOCK_METHOD0(OnControllerError, void()); |
| void OnLog(base::StringPiece) override {} |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MockOutputControllerEventHandler); |
| }; |
| |
| class MockOutputControllerSyncReader : public OutputController::SyncReader { |
| public: |
| MockOutputControllerSyncReader() = default; |
| |
| MOCK_METHOD3(RequestMoreData, |
| void(base::TimeDelta delay, |
| base::TimeTicks delay_timestamp, |
| int prior_frames_skipped)); |
| MOCK_METHOD1(Read, void(AudioBus* dest)); |
| MOCK_METHOD0(Close, void()); |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MockOutputControllerSyncReader); |
| }; |
| |
| class MockStreamMonitor : public StreamMonitor { |
| public: |
| MockStreamMonitor() = default; |
| |
| MOCK_METHOD1(OnStreamActive, void(Snoopable* snoopable)); |
| MOCK_METHOD1(OnStreamInactive, void(Snoopable* snoopable)); |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MockStreamMonitor); |
| }; |
| |
| // Wraps an AudioOutputStream instance, calling DidXYZ() mock methods for test |
| // verification of controller behavior. If a null AudioOutputStream pointer is |
| // provided to the constructor, a "data pump" thread will be run between the |
| // Start() and Stop() calls to simulate an AudioOutputStream not owned by the |
| // AudioManager. |
| class MockAudioOutputStream : public AudioOutputStream, |
| public AudioOutputStream::AudioSourceCallback { |
| public: |
| MockAudioOutputStream(AudioOutputStream* impl, AudioParameters::Format format) |
| : impl_(impl), format_(format) {} |
| |
| AudioParameters::Format format() const { return format_; } |
| |
| void set_close_callback(base::OnceClosure callback) { |
| close_callback_ = std::move(callback); |
| } |
| |
| // We forward to a fake stream to get automatic OnMoreData callbacks, |
| // required by some tests. |
| MOCK_METHOD0(DidOpen, void()); |
| MOCK_METHOD0(DidStart, void()); |
| MOCK_METHOD0(DidStop, void()); |
| MOCK_METHOD0(DidClose, void()); |
| MOCK_METHOD1(DidSetVolume, void(double)); |
| |
| bool Open() override { |
| if (impl_) |
| impl_->Open(); |
| DidOpen(); |
| return true; |
| } |
| |
| void Start(AudioOutputStream::AudioSourceCallback* cb) override { |
| EXPECT_EQ(nullptr, callback_); |
| callback_ = cb; |
| if (impl_) { |
| impl_->Start(this); |
| } else { |
| data_thread_ = std::make_unique<base::Thread>("AudioDataThread"); |
| CHECK(data_thread_->StartAndWaitForTesting()); |
| data_thread_->task_runner()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&MockAudioOutputStream::RunDataLoop, |
| base::Unretained(this), data_thread_->task_runner()), |
| GetTestParams().GetBufferDuration()); |
| } |
| DidStart(); |
| } |
| |
| void Stop() override { |
| if (impl_) { |
| impl_->Stop(); |
| } else { |
| data_thread_ = nullptr; // Joins/Stops the thread cleanly. |
| } |
| callback_ = nullptr; |
| DidStop(); |
| } |
| |
| void Close() override { |
| if (impl_) { |
| impl_->Close(); |
| impl_ = nullptr; |
| } |
| DidClose(); |
| if (close_callback_) |
| std::move(close_callback_).Run(); |
| delete this; |
| } |
| |
| void SetVolume(double volume) override { |
| volume_ = volume; |
| if (impl_) |
| impl_->SetVolume(volume); |
| DidSetVolume(volume); |
| } |
| |
| void GetVolume(double* volume) override { *volume = volume_; } |
| |
| protected: |
| ~MockAudioOutputStream() override = default; |
| |
| private: |
| // Calls OnMoreData() and then posts a delayed task to call itself again soon. |
| void RunDataLoop(scoped_refptr<base::SingleThreadTaskRunner> task_runner) { |
| auto bus = AudioBus::Create(GetTestParams()); |
| OnMoreData(base::TimeDelta(), base::TimeTicks::Now(), 0, bus.get()); |
| task_runner->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&MockAudioOutputStream::RunDataLoop, |
| base::Unretained(this), task_runner), |
| GetTestParams().GetBufferDuration()); |
| } |
| |
| int OnMoreData(base::TimeDelta delay, |
| base::TimeTicks delay_timestamp, |
| int prior_frames_skipped, |
| AudioBus* dest) override { |
| int res = callback_->OnMoreData(delay, delay_timestamp, |
| prior_frames_skipped, dest); |
| EXPECT_EQ(dest->channel(0)[0], kBufferNonZeroData); |
| return res; |
| } |
| |
| void OnError() override { |
| // Fake stream doesn't send errors. |
| NOTREACHED(); |
| } |
| |
| AudioOutputStream* impl_; |
| const AudioParameters::Format format_; |
| base::OnceClosure close_callback_; |
| AudioOutputStream::AudioSourceCallback* callback_ = nullptr; |
| double volume_ = 1.0; |
| std::unique_ptr<base::Thread> data_thread_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MockAudioOutputStream); |
| }; |
| |
| class MockSnooper : public Snoopable::Snooper { |
| public: |
| MockSnooper() = default; |
| ~MockSnooper() override = default; |
| |
| MOCK_METHOD0(DidProvideData, void()); |
| |
| void OnData(const media::AudioBus& audio_bus, |
| base::TimeTicks reference_time, |
| double volume) final { |
| // Is the AudioBus populated? |
| EXPECT_EQ(kBufferNonZeroData, audio_bus.channel(0)[0]); |
| |
| // Are reference timestamps monotonically increasing? |
| if (!last_reference_time_.is_null()) { |
| EXPECT_LT(last_reference_time_, reference_time); |
| } |
| last_reference_time_ = reference_time; |
| |
| // Is the correct volume being provided? |
| EXPECT_EQ(kTestVolume, volume); |
| |
| DidProvideData(); |
| } |
| |
| private: |
| base::TimeTicks last_reference_time_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MockSnooper); |
| }; |
| |
| // A FakeAudioManager that produces MockAudioOutputStreams, and tracks the last |
| // stream that was created and the last stream that was closed. |
| class AudioManagerForControllerTest : public media::FakeAudioManager { |
| public: |
| AudioManagerForControllerTest() |
| : media::FakeAudioManager(std::make_unique<media::TestAudioThread>(false), |
| &fake_audio_log_factory_) {} |
| |
| ~AudioManagerForControllerTest() final = default; |
| |
| MockAudioOutputStream* last_created_stream() const { |
| return last_created_stream_; |
| } |
| MockAudioOutputStream* last_closed_stream() const { |
| return last_closed_stream_; |
| } |
| |
| AudioOutputStream* MakeAudioOutputStream(const AudioParameters& params, |
| const std::string& device_id, |
| const LogCallback& cb) final { |
| last_created_stream_ = new NiceMock<MockAudioOutputStream>( |
| media::FakeAudioManager::MakeAudioOutputStream(params, device_id, cb), |
| params.format()); |
| last_created_stream_->set_close_callback( |
| base::BindOnce(&AudioManagerForControllerTest::SetLastClosedStream, |
| base::Unretained(this), last_created_stream_)); |
| return last_created_stream_; |
| } |
| |
| AudioOutputStream* MakeAudioOutputStreamProxy( |
| const AudioParameters& params, |
| const std::string& device_id) final { |
| last_created_stream_ = new NiceMock<MockAudioOutputStream>( |
| media::FakeAudioManager::MakeAudioOutputStream(params, device_id, |
| base::DoNothing()), |
| params.format()); |
| last_created_stream_->set_close_callback( |
| base::BindOnce(&AudioManagerForControllerTest::SetLastClosedStream, |
| base::Unretained(this), last_created_stream_)); |
| return last_created_stream_; |
| } |
| |
| private: |
| void SetLastClosedStream(MockAudioOutputStream* stream) { |
| last_closed_stream_ = stream; |
| } |
| |
| media::FakeAudioLogFactory fake_audio_log_factory_; |
| MockAudioOutputStream* last_created_stream_ = nullptr; |
| MockAudioOutputStream* last_closed_stream_ = nullptr; |
| }; |
| |
| ACTION(PopulateBuffer) { |
| arg0->Zero(); |
| // Note: To confirm the buffer will be populated in these tests, it's |
| // sufficient that only the first float in channel 0 is set to the value. |
| arg0->channel(0)[0] = kBufferNonZeroData; |
| } |
| |
| class OutputControllerTest : public ::testing::Test { |
| public: |
| OutputControllerTest() |
| : group_id_(base::UnguessableToken::Create()), |
| processing_id_(base::UnguessableToken::Create()) {} |
| |
| ~OutputControllerTest() override { audio_manager_.Shutdown(); } |
| |
| void SetUp() override { |
| controller_.emplace(&audio_manager_, &mock_event_handler_, GetTestParams(), |
| std::string(), &mock_sync_reader_, |
| &stream_monitor_coordinator_, processing_id_); |
| controller_->SetVolume(kTestVolume); |
| } |
| |
| void TearDown() override { controller_ = base::nullopt; } |
| |
| protected: |
| // Returns the last-created or last-closed AudioOuptutStream. |
| MockAudioOutputStream* last_created_stream() const { |
| return audio_manager_.last_created_stream(); |
| } |
| MockAudioOutputStream* last_closed_stream() const { |
| return audio_manager_.last_closed_stream(); |
| } |
| |
| void Create() { |
| controller_->Create(false); |
| controller_->SetVolume(kTestVolume); |
| } |
| |
| void Play() { |
| base::RunLoop loop; |
| // The barrier is used to wait for all of the expectations to be fulfilled. |
| base::RepeatingClosure barrier = |
| base::BarrierClosure(3, loop.QuitClosure()); |
| EXPECT_CALL(mock_event_handler_, OnControllerPlaying()) |
| .WillOnce(RunClosure(barrier)); |
| EXPECT_CALL(mock_sync_reader_, RequestMoreData(_, _, _)) |
| .WillOnce(RunClosure(barrier)) |
| .WillRepeatedly(Return()); |
| EXPECT_CALL(mock_sync_reader_, Read(_)) |
| .WillOnce(Invoke([barrier](AudioBus* data) { |
| data->Zero(); |
| data->channel(0)[0] = kBufferNonZeroData; |
| barrier.Run(); |
| })) |
| .WillRepeatedly(PopulateBuffer()); |
| |
| controller_->Play(); |
| Mock::VerifyAndClearExpectations(&mock_event_handler_); |
| |
| // Waits for all gmock expectations to be satisfied. |
| loop.Run(); |
| } |
| |
| void Pause() { |
| base::RunLoop loop; |
| EXPECT_CALL(mock_event_handler_, OnControllerPaused()) |
| .WillOnce(RunOnceClosure(loop.QuitClosure())); |
| controller_->Pause(); |
| loop.Run(); |
| Mock::VerifyAndClearExpectations(&mock_event_handler_); |
| } |
| |
| void ChangeDevice() { |
| // Expect the event handler to receive one OnControllerPaying() call and no |
| // OnControllerPaused() call. |
| EXPECT_CALL(mock_event_handler_, OnControllerPlaying()); |
| EXPECT_CALL(mock_event_handler_, OnControllerPaused()).Times(0); |
| |
| // Simulate a device change event to OutputController from the AudioManager. |
| audio_manager_.GetTaskRunner()->PostTask( |
| FROM_HERE, base::BindOnce(&OutputController::OnDeviceChange, |
| base::Unretained(&(*controller_)))); |
| |
| // Wait for device change to take effect. |
| base::RunLoop loop; |
| audio_manager_.GetTaskRunner()->PostTask(FROM_HERE, loop.QuitClosure()); |
| loop.Run(); |
| |
| Mock::VerifyAndClearExpectations(&mock_event_handler_); |
| } |
| |
| void StartMutingBeforePlaying() { controller_->StartMuting(); } |
| |
| void StartMutingWhilePlaying() { |
| EXPECT_CALL(mock_event_handler_, OnControllerPlaying()); |
| controller_->StartMuting(); |
| Mock::VerifyAndClearExpectations(&mock_event_handler_); |
| } |
| |
| void StopMuting() { |
| EXPECT_CALL(mock_event_handler_, OnControllerPlaying()); |
| controller_->StopMuting(); |
| Mock::VerifyAndClearExpectations(&mock_event_handler_); |
| } |
| |
| void StartSnooping(MockSnooper* snooper, Snoopable::SnoopingMode mode) { |
| controller_->StartSnooping(snooper, mode); |
| } |
| |
| void WaitForSnoopedData(MockSnooper* snooper) { |
| base::RunLoop loop; |
| EXPECT_CALL(*snooper, DidProvideData()) |
| .WillOnce(RunOnceClosure(loop.QuitClosure())) |
| .WillRepeatedly(Return()); |
| loop.Run(); |
| Mock::VerifyAndClearExpectations(snooper); |
| } |
| |
| void StopSnooping(MockSnooper* snooper, Snoopable::SnoopingMode mode) { |
| controller_->StopSnooping(snooper, mode); |
| } |
| |
| Snoopable* GetSnoopable() { return &(*controller_); } |
| |
| void JoinProcessingGroup(StreamMonitor* monitor) { |
| stream_monitor_coordinator_.RegisterMember(processing_id_, monitor); |
| } |
| |
| void LeaveProcessingGroup(StreamMonitor* monitor) { |
| stream_monitor_coordinator_.UnregisterMember(processing_id_, monitor); |
| } |
| |
| void Close() { |
| EXPECT_CALL(mock_sync_reader_, Close()); |
| controller_->Close(); |
| |
| // Flush any pending tasks (that should have been canceled!). |
| base::RunLoop loop; |
| audio_manager_.GetTaskRunner()->PostTask(FROM_HERE, loop.QuitClosure()); |
| loop.Run(); |
| } |
| |
| void SimulateErrorThenDeviceChange() { |
| audio_manager_.GetTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&OutputControllerTest::TriggerErrorThenDeviceChange, |
| base::Unretained(this))); |
| |
| base::RunLoop loop; |
| audio_manager_.GetTaskRunner()->PostTask(FROM_HERE, loop.QuitClosure()); |
| loop.Run(); |
| } |
| |
| void TriggerErrorThenDeviceChange() { |
| DCHECK(audio_manager_.GetTaskRunner()->BelongsToCurrentThread()); |
| |
| // Errors should be deferred; the device change should ensure it's dropped. |
| EXPECT_CALL(mock_event_handler_, OnControllerError()).Times(0); |
| controller_->OnError(); |
| |
| EXPECT_CALL(mock_event_handler_, OnControllerPlaying()); |
| EXPECT_CALL(mock_event_handler_, OnControllerPaused()).Times(0); |
| controller_->OnDeviceChange(); |
| |
| Mock::VerifyAndClearExpectations(&mock_event_handler_); |
| } |
| |
| private: |
| base::TestMessageLoop message_loop_; |
| AudioManagerForControllerTest audio_manager_; |
| base::UnguessableToken group_id_; |
| base::UnguessableToken processing_id_; |
| StrictMock<MockOutputControllerEventHandler> mock_event_handler_; |
| StrictMock<MockOutputControllerSyncReader> mock_sync_reader_; |
| base::Optional<OutputController> controller_; |
| StreamMonitorCoordinator stream_monitor_coordinator_; |
| |
| DISALLOW_COPY_AND_ASSIGN(OutputControllerTest); |
| }; |
| |
| TEST_F(OutputControllerTest, CreateAndClose) { |
| Create(); |
| Close(); |
| } |
| |
| TEST_F(OutputControllerTest, PlayAndClose) { |
| Create(); |
| Play(); |
| Close(); |
| } |
| |
| TEST_F(OutputControllerTest, PlayPauseClose) { |
| Create(); |
| Play(); |
| Pause(); |
| Close(); |
| } |
| |
| TEST_F(OutputControllerTest, PlayPausePlayClose) { |
| Create(); |
| Play(); |
| Pause(); |
| Play(); |
| Close(); |
| } |
| |
| TEST_F(OutputControllerTest, PlayDeviceChangeClose) { |
| Create(); |
| Play(); |
| ChangeDevice(); |
| Close(); |
| } |
| |
| TEST_F(OutputControllerTest, PlayDeviceChangeError) { |
| Create(); |
| Play(); |
| SimulateErrorThenDeviceChange(); |
| Close(); |
| } |
| |
| // Syntactic convenience. |
| double GetStreamVolume(AudioOutputStream* stream) { |
| double result = NAN; |
| stream->GetVolume(&result); |
| return result; |
| } |
| |
| // Tests that muting before the stream is created will result in only the |
| // "muting stream" being created, and not any local playout streams (that might |
| // possibly cause an audible blip). |
| TEST_F(OutputControllerTest, MuteCreatePlayClose) { |
| StartMutingBeforePlaying(); |
| EXPECT_EQ(nullptr, last_created_stream()); // No stream yet. |
| EXPECT_EQ(nullptr, last_closed_stream()); // No stream yet. |
| |
| Create(); |
| MockAudioOutputStream* const mute_stream = last_created_stream(); |
| ASSERT_TRUE(mute_stream); |
| EXPECT_EQ(nullptr, last_closed_stream()); |
| EXPECT_EQ(AudioParameters::AUDIO_FAKE, mute_stream->format()); |
| |
| Play(); |
| ASSERT_EQ(mute_stream, last_created_stream()); |
| EXPECT_EQ(nullptr, last_closed_stream()); |
| EXPECT_EQ(AudioParameters::AUDIO_FAKE, mute_stream->format()); |
| |
| Close(); |
| EXPECT_EQ(mute_stream, last_created_stream()); |
| EXPECT_EQ(mute_stream, last_closed_stream()); |
| } |
| |
| // Tests that a local playout stream is shut-down and replaced with a "muting |
| // stream" if StartMuting() is called after playback begins. |
| TEST_F(OutputControllerTest, CreatePlayMuteClose) { |
| Create(); |
| MockAudioOutputStream* const playout_stream = last_created_stream(); |
| ASSERT_TRUE(playout_stream); |
| EXPECT_EQ(nullptr, last_closed_stream()); |
| |
| Play(); |
| ASSERT_EQ(playout_stream, last_created_stream()); |
| EXPECT_EQ(nullptr, last_closed_stream()); |
| EXPECT_EQ(GetTestParams().format(), playout_stream->format()); |
| EXPECT_EQ(kTestVolume, GetStreamVolume(playout_stream)); |
| |
| StartMutingWhilePlaying(); |
| MockAudioOutputStream* const mute_stream = last_created_stream(); |
| ASSERT_TRUE(mute_stream); |
| EXPECT_EQ(playout_stream, last_closed_stream()); |
| EXPECT_EQ(AudioParameters::AUDIO_FAKE, mute_stream->format()); |
| |
| Close(); |
| EXPECT_EQ(mute_stream, last_created_stream()); |
| EXPECT_EQ(mute_stream, last_closed_stream()); |
| } |
| |
| // Tests that the "muting stream" is shut down and replaced with the normal |
| // playout stream after StopMuting() is called. |
| TEST_F(OutputControllerTest, PlayMuteUnmuteClose) { |
| StartMutingBeforePlaying(); |
| Create(); |
| Play(); |
| MockAudioOutputStream* const mute_stream = last_created_stream(); |
| ASSERT_TRUE(mute_stream); |
| EXPECT_EQ(nullptr, last_closed_stream()); |
| EXPECT_EQ(AudioParameters::AUDIO_FAKE, mute_stream->format()); |
| |
| StopMuting(); |
| MockAudioOutputStream* const playout_stream = last_created_stream(); |
| ASSERT_TRUE(playout_stream); |
| EXPECT_EQ(mute_stream, last_closed_stream()); |
| EXPECT_EQ(GetTestParams().format(), playout_stream->format()); |
| EXPECT_EQ(kTestVolume, GetStreamVolume(playout_stream)); |
| |
| Close(); |
| EXPECT_EQ(playout_stream, last_created_stream()); |
| EXPECT_EQ(playout_stream, last_closed_stream()); |
| } |
| |
| class WithSnoopingMode |
| : public OutputControllerTest, |
| public ::testing::WithParamInterface<Snoopable::SnoopingMode> {}; |
| |
| TEST_P(WithSnoopingMode, SnoopCreatePlayStopClose) { |
| NiceMock<MockSnooper> snooper; |
| StartSnooping(&snooper, GetParam()); |
| Create(); |
| Play(); |
| WaitForSnoopedData(&snooper); |
| StopSnooping(&snooper, GetParam()); |
| Close(); |
| } |
| |
| TEST_P(WithSnoopingMode, CreatePlaySnoopStopClose) { |
| NiceMock<MockSnooper> snooper; |
| Create(); |
| Play(); |
| StartSnooping(&snooper, GetParam()); |
| WaitForSnoopedData(&snooper); |
| StopSnooping(&snooper, GetParam()); |
| Close(); |
| } |
| |
| TEST_P(WithSnoopingMode, CreatePlaySnoopCloseStop) { |
| NiceMock<MockSnooper> snooper; |
| Create(); |
| Play(); |
| StartSnooping(&snooper, GetParam()); |
| WaitForSnoopedData(&snooper); |
| Close(); |
| StopSnooping(&snooper, GetParam()); |
| } |
| |
| TEST_P(WithSnoopingMode, TwoSnoopers_StartAtDifferentTimes) { |
| NiceMock<MockSnooper> snooper1; |
| NiceMock<MockSnooper> snooper2; |
| StartSnooping(&snooper1, GetParam()); |
| Create(); |
| Play(); |
| WaitForSnoopedData(&snooper1); |
| StartSnooping(&snooper2, GetParam()); |
| WaitForSnoopedData(&snooper2); |
| WaitForSnoopedData(&snooper1); |
| WaitForSnoopedData(&snooper2); |
| Close(); |
| StopSnooping(&snooper1, GetParam()); |
| StopSnooping(&snooper2, GetParam()); |
| } |
| |
| TEST_P(WithSnoopingMode, TwoSnoopers_StopAtDifferentTimes) { |
| NiceMock<MockSnooper> snooper1; |
| NiceMock<MockSnooper> snooper2; |
| Create(); |
| Play(); |
| StartSnooping(&snooper1, GetParam()); |
| WaitForSnoopedData(&snooper1); |
| StartSnooping(&snooper2, GetParam()); |
| WaitForSnoopedData(&snooper2); |
| StopSnooping(&snooper1, GetParam()); |
| WaitForSnoopedData(&snooper2); |
| Close(); |
| StopSnooping(&snooper2, GetParam()); |
| } |
| |
| TEST_P(WithSnoopingMode, SnoopWhileMuting) { |
| NiceMock<MockSnooper> snooper; |
| |
| StartMutingBeforePlaying(); |
| EXPECT_EQ(nullptr, last_created_stream()); // No stream yet. |
| EXPECT_EQ(nullptr, last_closed_stream()); // No stream yet. |
| |
| Create(); |
| MockAudioOutputStream* const mute_stream = last_created_stream(); |
| ASSERT_TRUE(mute_stream); |
| EXPECT_EQ(nullptr, last_closed_stream()); |
| |
| Play(); |
| ASSERT_EQ(mute_stream, last_created_stream()); |
| EXPECT_EQ(nullptr, last_closed_stream()); |
| EXPECT_EQ(AudioParameters::AUDIO_FAKE, mute_stream->format()); |
| |
| StartSnooping(&snooper, GetParam()); |
| ASSERT_EQ(mute_stream, last_created_stream()); |
| EXPECT_EQ(nullptr, last_closed_stream()); |
| EXPECT_EQ(AudioParameters::AUDIO_FAKE, mute_stream->format()); |
| WaitForSnoopedData(&snooper); |
| |
| StopSnooping(&snooper, GetParam()); |
| ASSERT_EQ(mute_stream, last_created_stream()); |
| EXPECT_EQ(nullptr, last_closed_stream()); |
| EXPECT_EQ(AudioParameters::AUDIO_FAKE, mute_stream->format()); |
| |
| Close(); |
| EXPECT_EQ(mute_stream, last_created_stream()); |
| EXPECT_EQ(mute_stream, last_closed_stream()); |
| } |
| |
| INSTANTIATE_TEST_CASE_P(OutputControllerSnoopingTest, |
| WithSnoopingMode, |
| ::testing::Values(Snoopable::SnoopingMode::kDeferred, |
| Snoopable::SnoopingMode::kRealtime)); |
| |
| TEST_F(OutputControllerTest, InformsStreamMonitorsAlreadyInGroup) { |
| MockStreamMonitor monitor; |
| EXPECT_CALL(monitor, OnStreamActive(GetSnoopable())); |
| EXPECT_CALL(monitor, OnStreamInactive(GetSnoopable())); |
| JoinProcessingGroup(&monitor); |
| Create(); |
| Play(); |
| Close(); |
| LeaveProcessingGroup(&monitor); |
| } |
| |
| TEST_F(OutputControllerTest, InformsStreamMonitorsJoiningInGroup) { |
| MockStreamMonitor monitor; |
| EXPECT_CALL(monitor, OnStreamActive(GetSnoopable())); |
| EXPECT_CALL(monitor, OnStreamInactive(GetSnoopable())); |
| Create(); |
| Play(); |
| JoinProcessingGroup(&monitor); |
| Close(); |
| LeaveProcessingGroup(&monitor); |
| } |
| |
| TEST_F(OutputControllerTest, |
| DoesNotInformStreamMonitorsJoiningInGroupAfterClose) { |
| MockStreamMonitor monitor; |
| EXPECT_CALL(monitor, OnStreamActive(GetSnoopable())).Times(0); |
| EXPECT_CALL(monitor, OnStreamInactive(GetSnoopable())).Times(0); |
| Create(); |
| Play(); |
| Close(); |
| JoinProcessingGroup(&monitor); |
| LeaveProcessingGroup(&monitor); |
| } |
| |
| } // namespace |
| } // namespace audio |