| // Copyright 2021 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/output_device_mixer_impl.h" |
| |
| #include <array> |
| |
| #include "base/logging.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/test/task_environment.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using testing::_; |
| using testing::InSequence; |
| using testing::Mock; |
| using testing::Return; |
| using testing::StrictMock; |
| |
| namespace audio { |
| namespace { |
| |
| using media::AudioOutputStream; |
| |
| const std::string kDeviceId = "device-id"; |
| |
| MATCHER_P(AudioParamsEq, other, "AudioParameters matcher") { |
| return arg.Equals(other); |
| } |
| |
| // Mock listener. |
| class MockListener : public ReferenceOutput::Listener { |
| public: |
| MOCK_METHOD3(OnPlayoutData, |
| void(const media::AudioBus& audio_bus, |
| int sample_rate, |
| base::TimeDelta delay)); |
| }; |
| |
| // Mock of a physical output stream_under_test. |
| class MockAudioOutputStream : public AudioOutputStream { |
| public: |
| MockAudioOutputStream() = default; |
| ~MockAudioOutputStream() override = default; |
| MockAudioOutputStream(MockAudioOutputStream&& other) { |
| provided_callback_ = other.provided_callback_; |
| } |
| |
| void Start(AudioSourceCallback* callback) override { |
| provided_callback_ = callback; |
| StartCalled(); |
| } |
| |
| MOCK_METHOD0(StartCalled, void()); |
| MOCK_METHOD0(Stop, void()); |
| MOCK_METHOD0(Open, bool()); |
| void SetVolume(double volume) final { volume_ = volume; } |
| double GetVolume() { return volume_; } |
| MOCK_METHOD1(GetVolume, void(double*)); |
| MOCK_METHOD0(Close, void()); |
| MOCK_METHOD0(Flush, void()); |
| |
| void SimulateOnMoreData() { |
| DCHECK(provided_callback_); |
| provided_callback_->OnMoreData(base::TimeDelta(), base::TimeTicks(), {}, |
| nullptr); |
| } |
| |
| void SimulateError() { |
| DCHECK(provided_callback_); |
| provided_callback_->OnError(AudioSourceCallback::ErrorType::kUnknown); |
| } |
| |
| private: |
| raw_ptr<AudioOutputStream::AudioSourceCallback, DanglingUntriaged> |
| provided_callback_ = nullptr; |
| double volume_ = 0; |
| }; |
| |
| class MockAudioSourceCallback : public AudioOutputStream::AudioSourceCallback { |
| public: |
| MockAudioSourceCallback() = default; |
| MockAudioSourceCallback(MockAudioSourceCallback&& other) {} |
| |
| MOCK_METHOD4(OnMoreData, |
| int(base::TimeDelta delay, |
| base::TimeTicks delay_timestamp, |
| const media::AudioGlitchInfo& glitch_info, |
| media::AudioBus* dest)); |
| |
| MOCK_METHOD1(OnError, |
| void(AudioOutputStream::AudioSourceCallback::ErrorType type)); |
| }; |
| |
| // Mocks interesting calls of MixingGraph::Input. |
| class MockMixingGraphInput : public MixingGraph::Input { |
| public: |
| MockMixingGraphInput() = default; |
| MockMixingGraphInput(MockMixingGraphInput&& other) {} |
| MOCK_CONST_METHOD0(GetParams, const media::AudioParameters&()); |
| void SetVolume(double volume) final { volume_ = volume; } |
| double GetVolume() { return volume_; } |
| MOCK_METHOD1(Start, |
| void(AudioOutputStream::AudioSourceCallback* source_callback)); |
| MOCK_METHOD0(Stop, void()); |
| MOCK_METHOD3(ProvideInput, |
| double(media::AudioBus* audio_bus, |
| uint32_t frames_delayed, |
| const media::AudioGlitchInfo& glitch_info)); |
| |
| private: |
| double volume_ = 0; |
| }; |
| |
| // MixingGraph::Input eventually produced by MockMixingGraph::CreateInput(). |
| // Delegates all the interesting calls to MockMixingGraphInput. |
| class FakeMixingGraphInput : public MixingGraph::Input { |
| public: |
| FakeMixingGraphInput(const media::AudioParameters& params, |
| MockMixingGraphInput* mock_input) |
| : params_(params), mock_input_(mock_input) {} |
| const media::AudioParameters& GetParams() const final { return params_; } |
| void SetVolume(double volume) final { mock_input_->SetVolume(volume); } |
| void Start(AudioOutputStream::AudioSourceCallback* source_callback) final { |
| mock_input_->Start(source_callback); |
| } |
| void Stop() final { mock_input_->Stop(); } |
| |
| MOCK_METHOD3(ProvideInput, |
| double(media::AudioBus* audio_bus, |
| uint32_t frames_delayed, |
| const media::AudioGlitchInfo& glitch_info)); |
| |
| private: |
| const media::AudioParameters params_; |
| const raw_ptr<MockMixingGraphInput> mock_input_; |
| }; |
| |
| // Created and owned by OutputMixerManagerImpl; it's essentially a factory |
| // producing FGraphakeMixerInput instances. |
| class MockMixingGraph : public MixingGraph { |
| public: |
| using CreateGraphInputCallback = |
| base::RepeatingCallback<std::unique_ptr<MixingGraph::Input>( |
| const media::AudioParameters& params)>; |
| |
| MockMixingGraph(CreateGraphInputCallback create_graph_input_cb, |
| MixingGraph::OnMoreDataCallback on_more_data_cb, |
| MixingGraph::OnErrorCallback on_error_cb) |
| : create_graph_input_cb_(std::move(create_graph_input_cb)), |
| on_more_data_cb_(std::move(on_more_data_cb)), |
| on_error_cb_(std::move(on_error_cb)) {} |
| |
| std::unique_ptr<Input> CreateInput( |
| const media::AudioParameters& params) final { |
| return create_graph_input_cb_.Run(params); |
| } |
| |
| MOCK_METHOD4(OnMoreData, |
| int(base::TimeDelta delay, |
| base::TimeTicks delay_timestamp, |
| const media::AudioGlitchInfo& glitch_info, |
| media::AudioBus* dest)); |
| |
| void OnError(AudioOutputStream::AudioSourceCallback::ErrorType type) final { |
| on_error_cb_.Run(type); |
| } |
| |
| void SimulateOnMoreData() { |
| auto audio_bus = media::AudioBus::Create(2, 480); |
| on_more_data_cb_.Run(*audio_bus.get(), base::TimeDelta()); |
| } |
| |
| private: |
| MOCK_METHOD1(AddInput, void(MixingGraph::Input* input)); |
| MOCK_METHOD1(RemoveInput, void(MixingGraph::Input* input)); |
| CreateGraphInputCallback create_graph_input_cb_; |
| MixingGraph::OnMoreDataCallback on_more_data_cb_; |
| MixingGraph::OnErrorCallback on_error_cb_; |
| }; |
| |
| class OutputDeviceMixerImplTestBase { |
| protected: |
| enum class PlaybackMode { kMixing, kIndependent }; |
| |
| // Holds all the mocks associated with a given audio output. |
| struct MixTrackMock { |
| explicit MixTrackMock(int frames_per_buffer) |
| : params(media::AudioParameters::AUDIO_PCM_LOW_LATENCY, |
| media::ChannelLayoutConfig::Stereo(), |
| 48000, |
| frames_per_buffer) {} |
| StrictMock<MockMixingGraphInput> graph_input; |
| StrictMock<MockAudioOutputStream> rendering_stream; |
| const media::AudioParameters params; |
| StrictMock<MockAudioSourceCallback> source_callback; |
| // Set to true when independent rendering started successfully for the first |
| // time. |
| bool independent_rendering_stream_was_open = false; |
| }; |
| |
| // Helper. |
| struct StreamUnderTest { |
| // MixableOutputStream produced by OutputMixerImpl. |
| raw_ptr<AudioOutputStream, DanglingUntriaged> mixable_stream; |
| // All the mocks associated with it. |
| raw_ptr<MixTrackMock> mix_track_mock; |
| }; |
| |
| OutputDeviceMixerImplTestBase() { |
| ON_CALL(*this, MockCreateOutputStream).WillByDefault(Return(true)); |
| } |
| // Registers physical output stream creation. |
| MOCK_METHOD1(MockCreateOutputStream, |
| bool(const media::AudioParameters& params)); |
| |
| // Creates the mixer for testing, passing callbacks to produce mock |
| // MixingGraph and mock physical output stream_under_test. |
| std::unique_ptr<OutputDeviceMixer> CreateMixerUnderTest( |
| const std::string& device_id = kDeviceId) { |
| auto mixer = std::make_unique<OutputDeviceMixerImpl>( |
| device_id, mixer_output_params_, |
| base::BindOnce(&OutputDeviceMixerImplTestBase::CreateMockMixingGraph, |
| base::Unretained(this)), |
| base::BindRepeating(&OutputDeviceMixerImplTestBase::CreateOutputStream, |
| base::Unretained(this), device_id)); |
| return mixer; |
| } |
| |
| // Helper which calls the mixer to create an output stream |
| // (MixabeOutputStream) and associates the result output stream with its |
| // corresponding set of mocks. |
| StreamUnderTest CreateNextStreamUnderTest(OutputDeviceMixer* mixer) { |
| EXPECT_LE(mix_track_mocks_in_use_count_, mix_track_mocks_.size() - 1); |
| const media::AudioParameters params = |
| mix_track_mocks_[mix_track_mocks_in_use_count_].params; |
| return {mixer->MakeMixableStream( |
| params, |
| base::BindOnce( |
| &OutputDeviceMixerImplTestBase::OnDeviceChangeForMixMember, |
| base::Unretained(this), params)), |
| &mix_track_mocks_[mix_track_mocks_in_use_count_++]}; |
| } |
| |
| void ExpectIndependentRenderingStreamStreamClosedIfItWasOpen( |
| StreamUnderTest& stream_under_test) { |
| if (stream_under_test.mix_track_mock |
| ->independent_rendering_stream_was_open) { |
| EXPECT_CALL(stream_under_test.mix_track_mock->rendering_stream, Close()); |
| } |
| } |
| |
| // Opens a MixabeOutputStream created by the mixer under test and sets |
| // expectations on associated mocks. |
| void OpenAndVerifyStreamUnderTest(StreamUnderTest& stream_under_test, |
| PlaybackMode playback_mode) { |
| if (playback_mode == PlaybackMode::kIndependent) { |
| SetIndependentRenderingStreamOpenExpectations( |
| stream_under_test.mix_track_mock, /*open_success=*/true); |
| } |
| EXPECT_TRUE(stream_under_test.mixable_stream->Open()); |
| VerifyAndClearAllExpectations(); |
| } |
| |
| // Closes a MixabeOutputStream created by the mixer under test and sets |
| // expectations on associated mocks. |
| void CloseAndVerifyStreamUnderTest(StreamUnderTest& stream_under_test) { |
| ExpectIndependentRenderingStreamStreamClosedIfItWasOpen(stream_under_test); |
| stream_under_test.mixable_stream->Close(); |
| VerifyAndClearAllExpectations(); |
| } |
| |
| // Starts a MixabeOutputStream created by the mixer under test and sets |
| // expectations on associated mocks. |playback_mode| is the mode |
| // OutputMixerImpl is expected to be in at the moment. |
| void StartAndVerifyStreamUnderTest(StreamUnderTest& stream_under_test, |
| PlaybackMode playback_mode) { |
| ExpectPlaybackStarted(stream_under_test.mix_track_mock, playback_mode); |
| stream_under_test.mixable_stream->Start( |
| &stream_under_test.mix_track_mock->source_callback); |
| VerifyAndClearAllExpectations(); |
| } |
| |
| // Stops a MixabeOutputStream created by the mixer under test and sets |
| // expectations on associated mocks. |playback_mode| is the mode |
| // OutputMixerImpl is expected to be in at the moment. |
| void StopAndVerifyStreamUnderTest(StreamUnderTest& stream_under_test, |
| PlaybackMode playback_mode) { |
| ExpectPlaybackStopped(stream_under_test.mix_track_mock, playback_mode); |
| stream_under_test.mixable_stream->Stop(); |
| VerifyAndClearAllExpectations(); |
| } |
| |
| // Sets expectations for the mixer to open the mixing stream. |
| void ExpectMixingGraphOutputStreamOpen() { |
| EXPECT_CALL(*this, |
| MockCreateOutputStream(AudioParamsEq(mixer_output_params_))); |
| EXPECT_CALL(mock_mixing_graph_output_stream_, Open()) |
| .WillOnce(Return(true)); |
| } |
| |
| void ExpectMixingGraphOutputStreamClosed() { |
| EXPECT_CALL(mock_mixing_graph_output_stream_, Close()); |
| } |
| |
| // Sets expectations for the mixer to start rendering mixed audio. |
| void ExpectMixingGraphOutputStreamStarted() { |
| EXPECT_CALL(mock_mixing_graph_output_stream_, StartCalled()); |
| mixing_graph_output_stream_not_running_ = false; |
| } |
| |
| // Sets expectations for the mixer to stop rendering mixed audio. |
| void ExpectMixingGraphOutputStreamStopped() { |
| EXPECT_CALL(mock_mixing_graph_output_stream_, Stop); |
| mixing_graph_output_stream_not_running_ = true; |
| } |
| |
| void SetIndependentRenderingStreamOpenExpectations( |
| MixTrackMock* mix_track_mock, |
| bool open_success) { |
| ASSERT_FALSE(mix_track_mock->independent_rendering_stream_was_open); |
| EXPECT_CALL(*this, |
| MockCreateOutputStream(AudioParamsEq(mix_track_mock->params))); |
| EXPECT_CALL(mix_track_mock->rendering_stream, Open()) |
| .WillOnce(Return(open_success)); |
| if (!open_success) { |
| EXPECT_CALL(mix_track_mock->rendering_stream, Close()); |
| } |
| mix_track_mock->independent_rendering_stream_was_open = open_success; |
| } |
| |
| void ExpectPlaybackStarted(MixTrackMock* mix_track_mock, |
| PlaybackMode playback_mode) { |
| if (playback_mode == PlaybackMode::kIndependent) { |
| if (!mix_track_mock->independent_rendering_stream_was_open) { |
| // If opening rendering stream during creation failed, it must be open |
| // on first start. |
| SetIndependentRenderingStreamOpenExpectations(mix_track_mock, |
| /*open_success=*/true); |
| } |
| EXPECT_CALL(mix_track_mock->rendering_stream, StartCalled()); |
| } else { |
| EXPECT_CALL(mix_track_mock->graph_input, |
| Start(&mix_track_mock->source_callback)); |
| if (mixing_graph_output_stream_not_running_) { |
| ExpectMixingGraphOutputStreamStarted(); |
| } |
| } |
| } |
| |
| void ExpectPlaybackStopped(MixTrackMock* mix_track_mock, |
| PlaybackMode playback_mode) { |
| if (playback_mode == PlaybackMode::kIndependent) { |
| if (mix_track_mock->independent_rendering_stream_was_open) { |
| EXPECT_CALL(mix_track_mock->rendering_stream, Stop); |
| } |
| } else { |
| EXPECT_CALL(mix_track_mock->graph_input, Stop); |
| } |
| } |
| |
| // Sets "start mixing" expectations for a given set of mocks. |
| void ExpectMixingStarted(const std::set<MixTrackMock*>& mocks) { |
| for (MixTrackMock* mock : mocks) |
| EXPECT_CALL(mock->graph_input, Start(&mock->source_callback)); |
| if (mocks.size() && mixing_graph_output_stream_not_running_) |
| ExpectMixingGraphOutputStreamStarted(); |
| } |
| |
| // Sets "stop mixing" expectations for a given set of mocks. |
| void ExpectMixingStopped(const std::set<MixTrackMock*>& mocks) { |
| for (MixTrackMock* mock : mocks) |
| EXPECT_CALL(mock->graph_input, Stop); |
| if (mocks.size() && !mixing_graph_output_stream_not_running_) |
| ExpectMixingGraphOutputStreamStopped(); |
| } |
| |
| // Sets "start playing independently" expectations for a given set of mocks. |
| void ExpectIndependentPlaybackStarted(const std::set<MixTrackMock*>& mocks) { |
| for (MixTrackMock* mock : mocks) |
| ExpectPlaybackStarted(mock, PlaybackMode::kIndependent); |
| } |
| |
| // Sets "stop playing independently" expectations for a given set of mocks. |
| void ExpectIndependentPlaybackStopped(const std::set<MixTrackMock*>& mocks) { |
| for (MixTrackMock* mock : mocks) |
| ExpectPlaybackStopped(mock, PlaybackMode::kIndependent); |
| } |
| |
| // Sets "no playback change" expectations for a given set of mocks. |
| void ExpectNoPlaybackModeChange(const std::set<MixTrackMock*>& mocks) { |
| EXPECT_CALL(mock_mixing_graph_output_stream_, Stop).Times(0); |
| EXPECT_CALL(mock_mixing_graph_output_stream_, StartCalled()).Times(0); |
| |
| for (MixTrackMock* mock : mocks) { |
| EXPECT_CALL(mock->graph_input, Stop).Times(0); |
| EXPECT_CALL(mock->rendering_stream, StartCalled()).Times(0); |
| EXPECT_CALL(mock->graph_input, Start(_)).Times(0); |
| EXPECT_CALL(mock->rendering_stream, Stop).Times(0); |
| } |
| } |
| |
| // Verifies and clears expectations on all mocks. |
| void VerifyAndClearAllExpectations() { |
| for (auto& mix_member_mock : mix_track_mocks_) { |
| Mock::VerifyAndClearExpectations(&mix_member_mock.graph_input); |
| Mock::VerifyAndClearExpectations(&mix_member_mock.rendering_stream); |
| Mock::VerifyAndClearExpectations(&mix_member_mock.source_callback); |
| } |
| Mock::VerifyAndClearExpectations(&mock_mixing_graph_output_stream_); |
| Mock::VerifyAndClearExpectations(this); |
| } |
| |
| // Called when device change callback of a MixTrack is called. |
| MOCK_METHOD1(OnDeviceChangeForMixMember, |
| void(const media::AudioParameters& params)); |
| |
| // Fast-forward to the point when delayed switch to unmixed playback must have |
| // happened (if it was scheduled). |
| void FastForwardToUnmixedPlayback() { |
| task_environment_.FastForwardBy( |
| OutputDeviceMixerImpl::kSwitchToUnmixedPlaybackDelay * 2); |
| } |
| |
| raw_ptr<MockMixingGraph, DanglingUntriaged> mock_mixing_graph_ = nullptr; |
| StrictMock<MockAudioOutputStream> mock_mixing_graph_output_stream_; |
| |
| const media::AudioParameters mixer_output_params_{ |
| media::AudioParameters::AUDIO_PCM_LOW_LATENCY, |
| media::ChannelLayoutConfig::Stereo(), 48000, 5}; |
| |
| private: |
| AudioOutputStream* CreateOutputStream(const std::string& expected_device_id, |
| const std::string& device_id, |
| const media::AudioParameters& params) { |
| EXPECT_EQ(device_id, expected_device_id); |
| if (!MockCreateOutputStream(params)) |
| return nullptr; // Fail stream creation. |
| if (params.Equals(mixer_output_params_)) |
| return &mock_mixing_graph_output_stream_; |
| return &(FindMixTrackMock(params)->rendering_stream); |
| } |
| |
| // Passed into OutputMixerImpl as a callback producing MixingGraph. |
| std::unique_ptr<MixingGraph> CreateMockMixingGraph( |
| const media::AudioParameters& output_params, |
| MixingGraph::OnMoreDataCallback on_more_data_cb, |
| MixingGraph::OnErrorCallback on_error_cb) { |
| auto mock_mixing_graph = std::make_unique<StrictMock<MockMixingGraph>>( |
| base::BindRepeating( |
| &OutputDeviceMixerImplTestBase::CreateMockGraphInput, |
| base::Unretained(this)), |
| std::move(on_more_data_cb), std::move(on_error_cb)); |
| mock_mixing_graph_ = mock_mixing_graph.get(); |
| return mock_mixing_graph; |
| } |
| |
| // Passed into MockMixingGraph as a callback producing MixingGraph::Input |
| // instances. |
| std::unique_ptr<MixingGraph::Input> CreateMockGraphInput( |
| const media::AudioParameters& params) { |
| return std::make_unique<FakeMixingGraphInput>( |
| params, &(FindMixTrackMock(params)->graph_input)); |
| } |
| |
| // Finds the mocks for MixableOutputStream created with given |params|. |
| MixTrackMock* FindMixTrackMock(const media::AudioParameters& params) { |
| for (auto& track_mock : mix_track_mocks_) { |
| if (track_mock.params.Equals(params)) |
| return &track_mock; |
| } |
| DCHECK(false); |
| return nullptr; |
| } |
| |
| base::test::SingleThreadTaskEnvironment task_environment_{ |
| base::test::SingleThreadTaskEnvironment::TimeSource::MOCK_TIME}; |
| |
| // A set of mocks for the data associated with MixableOutputStream; each mock |
| // is identfied by its MixTrackMock::|params|.frames_per_buffer(). |
| // To create mixable_stream output streams_under_test the tests call |
| // OutputDeviceMixerImpl::MakeMixableStream with MixTrackMock::|params| |
| // parameters, thus building an association between a MixableOutputStream and |
| // MixTrackMock. |
| std::array<MixTrackMock, 3> mix_track_mocks_{ |
| MixTrackMock(10), MixTrackMock(20), MixTrackMock(30)}; |
| |
| // How many elements of |mix_track_mocks_| are already in use. |
| size_t mix_track_mocks_in_use_count_ = 0; |
| |
| // Helps to correctly set expectations for |mock_mixing_graph_output_stream_|. |
| bool mixing_graph_output_stream_not_running_ = true; |
| }; |
| |
| class OutputDeviceMixerImplTest : public OutputDeviceMixerImplTestBase, |
| public testing::TestWithParam<int> {}; |
| |
| class OutputDeviceMixerImplTestWithDefault |
| : public OutputDeviceMixerImplTestBase, |
| public testing::TestWithParam<std::string> {}; |
| |
| TEST_P(OutputDeviceMixerImplTestWithDefault, OneUmixedStream_CreateClose) { |
| std::unique_ptr<OutputDeviceMixer> mixer = CreateMixerUnderTest(GetParam()); |
| StreamUnderTest stream_under_test = CreateNextStreamUnderTest(mixer.get()); |
| |
| // Physical output streams are created only on Open(). |
| EXPECT_CALL(*this, MockCreateOutputStream(_)).Times(0); |
| |
| EXPECT_CALL(stream_under_test.mix_track_mock->rendering_stream, Open()) |
| .Times(0); |
| EXPECT_CALL(mock_mixing_graph_output_stream_, Open()).Times(0); |
| EXPECT_CALL(stream_under_test.mix_track_mock->rendering_stream, Close()) |
| .Times(0); |
| EXPECT_CALL(mock_mixing_graph_output_stream_, Close()).Times(0); |
| |
| CloseAndVerifyStreamUnderTest(stream_under_test); |
| } |
| |
| TEST_F(OutputDeviceMixerImplTest, OneUmixedStream_PhysicalStreamCreateFailed) { |
| std::unique_ptr<OutputDeviceMixer> mixer = CreateMixerUnderTest(); |
| StreamUnderTest stream_under_test = CreateNextStreamUnderTest(mixer.get()); |
| |
| // Fail physical stream creation for the mix member. Open() fails, physical |
| // stream for independent rendering is not created. |
| EXPECT_CALL(*this, MockCreateOutputStream(AudioParamsEq( |
| stream_under_test.mix_track_mock->params))) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(stream_under_test.mix_track_mock->rendering_stream, Open()) |
| .Times(0); |
| EXPECT_CALL(stream_under_test.mix_track_mock->rendering_stream, Close()) |
| .Times(0); |
| |
| EXPECT_CALL(*this, |
| MockCreateOutputStream(AudioParamsEq(mixer_output_params_))) |
| .Times(0); |
| EXPECT_CALL(mock_mixing_graph_output_stream_, Open()).Times(0); |
| EXPECT_CALL(mock_mixing_graph_output_stream_, Close()).Times(0); |
| |
| EXPECT_FALSE(stream_under_test.mixable_stream->Open()); |
| CloseAndVerifyStreamUnderTest(stream_under_test); |
| } |
| |
| TEST_F(OutputDeviceMixerImplTest, OneUmixedStream_PhysicalStreamOpenFailed) { |
| std::unique_ptr<OutputDeviceMixer> mixer = CreateMixerUnderTest(); |
| StreamUnderTest stream_under_test = CreateNextStreamUnderTest(mixer.get()); |
| |
| // Fail opening physical stream for the mix track. Open() fails, physical |
| // stream for independent rendering is not created. |
| SetIndependentRenderingStreamOpenExpectations( |
| stream_under_test.mix_track_mock, |
| /*open_success=*/false); |
| |
| EXPECT_CALL(mock_mixing_graph_output_stream_, Open()).Times(0); |
| EXPECT_CALL(mock_mixing_graph_output_stream_, Close()).Times(0); |
| |
| EXPECT_FALSE(stream_under_test.mixable_stream->Open()); |
| CloseAndVerifyStreamUnderTest(stream_under_test); |
| } |
| |
| TEST_P(OutputDeviceMixerImplTestWithDefault, OneUmixedStream_CreateOpenClose) { |
| std::unique_ptr<OutputDeviceMixer> mixer = CreateMixerUnderTest(GetParam()); |
| StreamUnderTest stream_under_test = CreateNextStreamUnderTest(mixer.get()); |
| OpenAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kIndependent); |
| CloseAndVerifyStreamUnderTest(stream_under_test); |
| } |
| |
| TEST_F(OutputDeviceMixerImplTest, TwoUmixedStream_CreateOpenClose) { |
| std::unique_ptr<OutputDeviceMixer> mixer = CreateMixerUnderTest(); |
| StreamUnderTest stream_under_test1 = CreateNextStreamUnderTest(mixer.get()); |
| StreamUnderTest stream_under_test2 = CreateNextStreamUnderTest(mixer.get()); |
| OpenAndVerifyStreamUnderTest(stream_under_test1, PlaybackMode::kIndependent); |
| OpenAndVerifyStreamUnderTest(stream_under_test2, PlaybackMode::kIndependent); |
| CloseAndVerifyStreamUnderTest(stream_under_test2); |
| CloseAndVerifyStreamUnderTest(stream_under_test1); |
| } |
| |
| TEST_F(OutputDeviceMixerImplTest, |
| TwoUmixedStream_SecondOpenFailureDoesNotAffectMixerStream) { |
| std::unique_ptr<OutputDeviceMixer> mixer = CreateMixerUnderTest(); |
| StreamUnderTest stream_under_test1 = CreateNextStreamUnderTest(mixer.get()); |
| StreamUnderTest stream_under_test2 = CreateNextStreamUnderTest(mixer.get()); |
| |
| OpenAndVerifyStreamUnderTest(stream_under_test1, PlaybackMode::kIndependent); |
| StartAndVerifyStreamUnderTest(stream_under_test1, PlaybackMode::kIndependent); |
| |
| // Failing to open stream_under_test2 does not affect stream_under_test2. |
| SetIndependentRenderingStreamOpenExpectations( |
| stream_under_test2.mix_track_mock, /*open_success=*/false); |
| EXPECT_FALSE(stream_under_test2.mixable_stream->Open()); |
| VerifyAndClearAllExpectations(); |
| CloseAndVerifyStreamUnderTest(stream_under_test2); |
| |
| StopAndVerifyStreamUnderTest(stream_under_test1, PlaybackMode::kIndependent); |
| CloseAndVerifyStreamUnderTest(stream_under_test1); |
| } |
| |
| TEST_F(OutputDeviceMixerImplTest, OneUmixedStream_SetVolumeIsPropagated) { |
| std::unique_ptr<OutputDeviceMixer> mixer = CreateMixerUnderTest(); |
| StreamUnderTest stream_under_test = CreateNextStreamUnderTest(mixer.get()); |
| |
| double volume1 = 0.1; |
| double volume2 = 0.2; |
| double volume3 = 0.3; |
| double volume4 = 0.3; |
| double volume_result = 0; |
| |
| // Volume is propagated before MixableOutputStream::Open(). |
| stream_under_test.mixable_stream->SetVolume(volume1); |
| stream_under_test.mixable_stream->GetVolume(&volume_result); |
| EXPECT_EQ(volume_result, volume1); |
| EXPECT_EQ(stream_under_test.mix_track_mock->graph_input.GetVolume(), volume1); |
| |
| stream_under_test.mixable_stream->SetVolume(volume2); |
| stream_under_test.mixable_stream->GetVolume(&volume_result); |
| EXPECT_EQ(volume_result, volume2); |
| EXPECT_EQ(stream_under_test.mix_track_mock->graph_input.GetVolume(), volume2); |
| |
| OpenAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kIndependent); |
| |
| // Volume is propagated after MixableOutputStream::Open(). |
| stream_under_test.mixable_stream->GetVolume(&volume_result); |
| EXPECT_EQ(volume_result, volume2); |
| EXPECT_EQ(stream_under_test.mix_track_mock->graph_input.GetVolume(), volume2); |
| |
| StartAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kIndependent); |
| |
| // Volume is applied to the independent rendering stream of the MixTrack. |
| EXPECT_EQ(stream_under_test.mix_track_mock->rendering_stream.GetVolume(), |
| volume2); |
| |
| // Now when the stream is open, the volume is propagated to both the mixer |
| // input and the independent rendering tream of the MixTrack. |
| stream_under_test.mixable_stream->SetVolume(volume3); |
| stream_under_test.mixable_stream->GetVolume(&volume_result); |
| EXPECT_EQ(volume_result, volume3); |
| EXPECT_EQ(stream_under_test.mix_track_mock->graph_input.GetVolume(), volume3); |
| EXPECT_EQ(stream_under_test.mix_track_mock->rendering_stream.GetVolume(), |
| volume3); |
| |
| StopAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kIndependent); |
| |
| stream_under_test.mixable_stream->SetVolume(volume4); |
| stream_under_test.mixable_stream->GetVolume(&volume_result); |
| EXPECT_EQ(volume_result, volume4); |
| EXPECT_EQ(stream_under_test.mix_track_mock->graph_input.GetVolume(), volume4); |
| EXPECT_EQ(stream_under_test.mix_track_mock->rendering_stream.GetVolume(), |
| volume4); |
| |
| CloseAndVerifyStreamUnderTest(stream_under_test); |
| } |
| |
| TEST_F(OutputDeviceMixerImplTest, |
| OneUmixedStream_CreateOpenPlayOnErrorStopClose) { |
| std::unique_ptr<OutputDeviceMixer> mixer = CreateMixerUnderTest(); |
| StreamUnderTest stream_under_test = CreateNextStreamUnderTest(mixer.get()); |
| |
| OpenAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kIndependent); |
| |
| { |
| InSequence s; |
| |
| StartAndVerifyStreamUnderTest(stream_under_test, |
| PlaybackMode::kIndependent); |
| |
| EXPECT_CALL(stream_under_test.mix_track_mock->source_callback, |
| OnMoreData(_, _, _, _)) |
| .Times(1); |
| EXPECT_CALL(stream_under_test.mix_track_mock->source_callback, OnError(_)) |
| .Times(1); |
| |
| stream_under_test.mix_track_mock->rendering_stream.SimulateOnMoreData(); |
| stream_under_test.mix_track_mock->rendering_stream.SimulateError(); |
| |
| VerifyAndClearAllExpectations(); |
| |
| StopAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kIndependent); |
| } |
| |
| // Make sure it works after restart as well. |
| { |
| InSequence s; |
| |
| StartAndVerifyStreamUnderTest(stream_under_test, |
| PlaybackMode::kIndependent); |
| |
| EXPECT_CALL(stream_under_test.mix_track_mock->source_callback, |
| OnMoreData(_, _, _, _)) |
| .Times(1); |
| EXPECT_CALL(stream_under_test.mix_track_mock->source_callback, OnError(_)) |
| .Times(1); |
| |
| stream_under_test.mix_track_mock->rendering_stream.SimulateOnMoreData(); |
| stream_under_test.mix_track_mock->rendering_stream.SimulateError(); |
| |
| VerifyAndClearAllExpectations(); |
| |
| StopAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kIndependent); |
| } |
| |
| CloseAndVerifyStreamUnderTest(stream_under_test); |
| } |
| |
| TEST_P(OutputDeviceMixerImplTest, |
| NStreamsOpen_StartStopListeningDoesNotStartMixing) { |
| std::unique_ptr<OutputDeviceMixer> mixer = CreateMixerUnderTest(); |
| |
| int stream_count = GetParam(); |
| std::vector<StreamUnderTest> streams_under_test; |
| std::set<MixTrackMock*> playing_stream_mocks; |
| for (int i = 0; i < stream_count; ++i) { |
| streams_under_test.push_back(CreateNextStreamUnderTest(mixer.get())); |
| OpenAndVerifyStreamUnderTest(streams_under_test.back(), |
| PlaybackMode::kIndependent); |
| } |
| |
| // The mixer may have streams open, but they are not playing, so |
| // Start/StopListening has no effect. |
| MockListener listener; |
| ExpectMixingGraphOutputStreamOpen(); |
| mixer->StartListening(&listener); |
| VerifyAndClearAllExpectations(); |
| |
| ExpectMixingGraphOutputStreamClosed(); |
| mixer->StopListening(&listener); |
| VerifyAndClearAllExpectations(); |
| |
| FastForwardToUnmixedPlayback(); |
| for (auto& stream_under_test : streams_under_test) { |
| CloseAndVerifyStreamUnderTest(stream_under_test); |
| } |
| } |
| |
| TEST_P(OutputDeviceMixerImplTest, NStreamsPlaying_StartStopTwoListeners) { |
| std::unique_ptr<OutputDeviceMixer> mixer = CreateMixerUnderTest(); |
| |
| int stream_count = GetParam(); |
| if (!stream_count) |
| return; // Not interesting. |
| std::vector<StreamUnderTest> streams_under_test; |
| std::set<MixTrackMock*> playing_stream_mocks; |
| for (int i = 0; i < stream_count; ++i) { |
| streams_under_test.push_back(CreateNextStreamUnderTest(mixer.get())); |
| OpenAndVerifyStreamUnderTest(streams_under_test.back(), |
| PlaybackMode::kIndependent); |
| StartAndVerifyStreamUnderTest(streams_under_test.back(), |
| PlaybackMode::kIndependent); |
| playing_stream_mocks.insert((&streams_under_test.back())->mix_track_mock); |
| } |
| |
| MockListener listener1; |
| MockListener listener2; |
| |
| // We are playing at least one stream. |
| // Expect switch to mixing when the first listener comes. |
| ExpectIndependentPlaybackStopped(playing_stream_mocks); |
| ExpectMixingGraphOutputStreamOpen(); |
| ExpectMixingStarted(playing_stream_mocks); |
| mixer->StartListening(&listener1); |
| FastForwardToUnmixedPlayback(); |
| VerifyAndClearAllExpectations(); |
| |
| // Nothing should change when the second listener comes. |
| ExpectNoPlaybackModeChange(playing_stream_mocks); |
| mixer->StartListening(&listener2); |
| FastForwardToUnmixedPlayback(); |
| VerifyAndClearAllExpectations(); |
| |
| // First listener leaves: we still should be mixing. |
| ExpectNoPlaybackModeChange(playing_stream_mocks); |
| mixer->StopListening(&listener1); |
| FastForwardToUnmixedPlayback(); |
| VerifyAndClearAllExpectations(); |
| |
| // Second listener leaves: switching to unmixed playback is delayed, so we do |
| // not expect it to happen immediately. |
| ExpectNoPlaybackModeChange(playing_stream_mocks); |
| mixer->StopListening(&listener2); |
| VerifyAndClearAllExpectations(); |
| |
| // Expect switching to unmixed playback after we fast-forward to the future. |
| ExpectMixingGraphOutputStreamClosed(); |
| ExpectMixingStopped(playing_stream_mocks); |
| ExpectIndependentPlaybackStarted(playing_stream_mocks); |
| FastForwardToUnmixedPlayback(); |
| VerifyAndClearAllExpectations(); |
| |
| for (auto& stream_under_test : streams_under_test) { |
| StopAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kIndependent); |
| CloseAndVerifyStreamUnderTest(stream_under_test); |
| } |
| } |
| |
| TEST_P(OutputDeviceMixerImplTest, |
| NStreamsPlaying_StopStartListening_DoesNotStopMixing) { |
| std::unique_ptr<OutputDeviceMixer> mixer = CreateMixerUnderTest(); |
| |
| int stream_count = GetParam(); |
| if (!stream_count) |
| return; // Not interesting. |
| std::vector<StreamUnderTest> streams_under_test; |
| std::set<MixTrackMock*> playing_stream_mocks; |
| for (int i = 0; i < stream_count; ++i) { |
| streams_under_test.push_back(CreateNextStreamUnderTest(mixer.get())); |
| OpenAndVerifyStreamUnderTest(streams_under_test.back(), |
| PlaybackMode::kIndependent); |
| StartAndVerifyStreamUnderTest(streams_under_test.back(), |
| PlaybackMode::kIndependent); |
| playing_stream_mocks.insert((&streams_under_test.back())->mix_track_mock); |
| } |
| |
| MockListener listener; |
| |
| // We are playing at least one stream. |
| // Expect switch to mixing when the listener comes. |
| ExpectIndependentPlaybackStopped(playing_stream_mocks); |
| ExpectMixingGraphOutputStreamOpen(); |
| ExpectMixingStarted(playing_stream_mocks); |
| mixer->StartListening(&listener); |
| FastForwardToUnmixedPlayback(); |
| VerifyAndClearAllExpectations(); |
| |
| // If the listener stops listening and then starts again immediately, we do |
| // not switch to unmixed playback even after the timeout. |
| ExpectNoPlaybackModeChange(playing_stream_mocks); |
| mixer->StopListening(&listener); |
| mixer->StartListening(&listener); |
| FastForwardToUnmixedPlayback(); |
| VerifyAndClearAllExpectations(); |
| |
| // Stop listening and expect the switch to unmixed playback upon the timeout. |
| ExpectMixingStopped(playing_stream_mocks); |
| ExpectMixingGraphOutputStreamClosed(); |
| ExpectIndependentPlaybackStarted(playing_stream_mocks); |
| mixer->StopListening(&listener); |
| FastForwardToUnmixedPlayback(); |
| VerifyAndClearAllExpectations(); |
| |
| for (auto& stream_under_test : streams_under_test) { |
| StopAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kIndependent); |
| CloseAndVerifyStreamUnderTest(stream_under_test); |
| } |
| } |
| |
| TEST_P(OutputDeviceMixerImplTest, |
| StartStopNStreamsWhileListening_MixedPlaybackUntilListenerGone) { |
| std::unique_ptr<OutputDeviceMixer> mixer = CreateMixerUnderTest(); |
| |
| int stream_count = GetParam(); |
| if (!stream_count) |
| return; // Not interesting. |
| |
| MockListener listener; |
| ExpectMixingGraphOutputStreamOpen(); |
| mixer->StartListening(&listener); |
| VerifyAndClearAllExpectations(); |
| |
| std::vector<StreamUnderTest> streams_under_test; |
| for (int i = 0; i < stream_count; ++i) { |
| streams_under_test.push_back(CreateNextStreamUnderTest(mixer.get())); |
| OpenAndVerifyStreamUnderTest(streams_under_test.back(), |
| PlaybackMode::kMixing); |
| StartAndVerifyStreamUnderTest(streams_under_test.back(), |
| PlaybackMode::kMixing); |
| } |
| |
| // Stopping all playback does not stop rendering of the mixing graph. |
| for (auto& stream_under_test : streams_under_test) { |
| StopAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kMixing); |
| FastForwardToUnmixedPlayback(); |
| VerifyAndClearAllExpectations(); |
| } |
| |
| // Restarting playback. |
| for (auto& stream_under_test : streams_under_test) { |
| StartAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kMixing); |
| FastForwardToUnmixedPlayback(); |
| VerifyAndClearAllExpectations(); |
| } |
| |
| // Stopping and closing all the streams does not stop rendering of the mixing |
| // graph. |
| for (auto& stream_under_test : streams_under_test) { |
| StopAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kMixing); |
| CloseAndVerifyStreamUnderTest(stream_under_test); |
| } |
| |
| FastForwardToUnmixedPlayback(); |
| VerifyAndClearAllExpectations(); |
| |
| // Now when we stop the listener, rendering of the mixing graph should |
| // stop immediately. Mixing graph output stream will be closed. |
| ExpectMixingGraphOutputStreamStopped(); |
| ExpectMixingGraphOutputStreamClosed(); |
| mixer->StopListening(&listener); |
| VerifyAndClearAllExpectations(); |
| } |
| |
| TEST_P(OutputDeviceMixerImplTest, StartStopNStreamsWhileListening_DeleteMixer) { |
| std::unique_ptr<OutputDeviceMixer> mixer = CreateMixerUnderTest(); |
| |
| MockListener listener; |
| ExpectMixingGraphOutputStreamOpen(); |
| mixer->StartListening(&listener); |
| VerifyAndClearAllExpectations(); |
| |
| int stream_count = GetParam(); |
| std::vector<StreamUnderTest> streams_under_test; |
| for (int i = 0; i < stream_count; ++i) { |
| streams_under_test.push_back(CreateNextStreamUnderTest(mixer.get())); |
| OpenAndVerifyStreamUnderTest(streams_under_test.back(), |
| PlaybackMode::kMixing); |
| StartAndVerifyStreamUnderTest(streams_under_test.back(), |
| PlaybackMode::kMixing); |
| } |
| |
| for (auto& stream_under_test : streams_under_test) { |
| StopAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kMixing); |
| CloseAndVerifyStreamUnderTest(stream_under_test); |
| } |
| |
| if (stream_count) |
| ExpectMixingGraphOutputStreamStopped(); |
| ExpectMixingGraphOutputStreamClosed(); |
| mixer->StopListening(&listener); |
| mixer = nullptr; |
| // Mixer output stream must be closed immediately. |
| VerifyAndClearAllExpectations(); |
| } |
| |
| TEST_F(OutputDeviceMixerImplTest, |
| DeleteMixer_WhileGraphOutputStreamStopIsDelayed) { |
| std::unique_ptr<OutputDeviceMixer> mixer = CreateMixerUnderTest(); |
| |
| MockListener listener; |
| ExpectMixingGraphOutputStreamOpen(); |
| mixer->StartListening(&listener); |
| VerifyAndClearAllExpectations(); |
| |
| auto stream_under_test = CreateNextStreamUnderTest(mixer.get()); |
| OpenAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kMixing); |
| StartAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kMixing); |
| mixer->StopListening(&listener); |
| |
| // Since there are no listeners left, mixing playback must be stopped as soon |
| // as the stream is gone. |
| ExpectMixingGraphOutputStreamStopped(); |
| ExpectMixingGraphOutputStreamClosed(); |
| StopAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kMixing); |
| |
| CloseAndVerifyStreamUnderTest(stream_under_test); |
| |
| mixer = nullptr; |
| VerifyAndClearAllExpectations(); |
| } |
| |
| TEST_P(OutputDeviceMixerImplTest, NStreamsMixing_OnMixingStreamError) { |
| std::unique_ptr<OutputDeviceMixer> mixer = CreateMixerUnderTest(); |
| |
| int stream_count = GetParam(); |
| if (!stream_count) |
| return; // Not interesting. |
| |
| MockListener listener; |
| ExpectMixingGraphOutputStreamOpen(); |
| mixer->StartListening(&listener); |
| VerifyAndClearAllExpectations(); |
| |
| std::vector<StreamUnderTest> streams_under_test; |
| std::set<MixTrackMock*> playing_stream_mocks; |
| for (int i = 0; i < stream_count; ++i) { |
| streams_under_test.push_back(CreateNextStreamUnderTest(mixer.get())); |
| OpenAndVerifyStreamUnderTest(streams_under_test.back(), |
| PlaybackMode::kMixing); |
| StartAndVerifyStreamUnderTest(streams_under_test.back(), |
| PlaybackMode::kMixing); |
| playing_stream_mocks.insert((&streams_under_test.back())->mix_track_mock); |
| } |
| |
| for (auto& stream_under_test : streams_under_test) |
| EXPECT_CALL(stream_under_test.mix_track_mock->source_callback, OnError(_)); |
| |
| ExpectMixingStopped(playing_stream_mocks); |
| ExpectMixingGraphOutputStreamClosed(); // To be able to recover in the |
| // future. |
| mock_mixing_graph_output_stream_.SimulateError(); |
| VerifyAndClearAllExpectations(); |
| |
| for (auto& stream_under_test : streams_under_test) { |
| StopAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kIndependent); |
| } |
| |
| mixer->StopListening(&listener); |
| for (auto& stream_under_test : streams_under_test) { |
| CloseAndVerifyStreamUnderTest(stream_under_test); |
| } |
| mixer.reset(); |
| VerifyAndClearAllExpectations(); |
| } |
| |
| TEST_F(OutputDeviceMixerImplTest, OnMixingStreamError_Recovers) { |
| std::unique_ptr<OutputDeviceMixer> mixer = CreateMixerUnderTest(); |
| |
| MockListener listener; |
| ExpectMixingGraphOutputStreamOpen(); |
| mixer->StartListening(&listener); |
| VerifyAndClearAllExpectations(); |
| |
| std::vector<StreamUnderTest> streams_under_test; |
| std::set<MixTrackMock*> playing_stream_mocks; |
| for (int i = 0; i < 2; ++i) { |
| streams_under_test.push_back(CreateNextStreamUnderTest(mixer.get())); |
| OpenAndVerifyStreamUnderTest(streams_under_test.back(), |
| PlaybackMode::kMixing); |
| StartAndVerifyStreamUnderTest(streams_under_test.back(), |
| PlaybackMode::kMixing); |
| playing_stream_mocks.insert((&streams_under_test.back())->mix_track_mock); |
| } |
| |
| // Simulate the mixing stream error. |
| for (auto& stream_under_test : streams_under_test) |
| EXPECT_CALL(stream_under_test.mix_track_mock->source_callback, OnError(_)); |
| ExpectMixingStopped(playing_stream_mocks); |
| ExpectMixingGraphOutputStreamClosed(); |
| mock_mixing_graph_output_stream_.SimulateError(); |
| VerifyAndClearAllExpectations(); |
| |
| // Opening and starting a new stream: mixing should retry and start |
| // successfully. |
| streams_under_test.push_back(CreateNextStreamUnderTest(mixer.get())); |
| OpenAndVerifyStreamUnderTest(streams_under_test.back(), |
| PlaybackMode::kMixing); |
| |
| // Mixing should restart now. |
| ExpectMixingGraphOutputStreamOpen(); |
| ExpectMixingStarted(playing_stream_mocks); |
| StartAndVerifyStreamUnderTest(streams_under_test.back(), |
| PlaybackMode::kMixing); |
| VerifyAndClearAllExpectations(); |
| |
| for (auto& stream_under_test : streams_under_test) { |
| StopAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kMixing); |
| } |
| |
| // No playback, the listener is gone - we should stop mixing. |
| ExpectMixingGraphOutputStreamStopped(); |
| ExpectMixingGraphOutputStreamClosed(); |
| mixer->StopListening(&listener); |
| VerifyAndClearAllExpectations(); |
| |
| for (auto& stream_under_test : streams_under_test) { |
| CloseAndVerifyStreamUnderTest(stream_under_test); |
| } |
| } |
| |
| TEST_P(OutputDeviceMixerImplTest, NStreamsPlayingUmixed_DeviceChange) { |
| std::unique_ptr<OutputDeviceMixer> mixer = CreateMixerUnderTest(); |
| |
| int stream_count = GetParam(); |
| |
| std::vector<StreamUnderTest> streams_under_test; |
| |
| for (int i = 0; i < stream_count; ++i) { |
| streams_under_test.push_back(CreateNextStreamUnderTest(mixer.get())); |
| OpenAndVerifyStreamUnderTest(streams_under_test.back(), |
| PlaybackMode::kIndependent); |
| if (i) { // Leave one stream just open. |
| StartAndVerifyStreamUnderTest(streams_under_test.back(), |
| PlaybackMode::kIndependent); |
| } |
| } |
| |
| for (int i = 0; i < stream_count; ++i) { |
| if (i) { |
| EXPECT_CALL(streams_under_test[i].mix_track_mock->rendering_stream, Stop); |
| } |
| ExpectIndependentRenderingStreamStreamClosedIfItWasOpen( |
| streams_under_test[i]); |
| EXPECT_CALL(*this, OnDeviceChangeForMixMember(AudioParamsEq( |
| streams_under_test[i].mix_track_mock->params))); |
| } |
| |
| mixer->ProcessDeviceChange(); |
| VerifyAndClearAllExpectations(); |
| |
| // Stop() and Close() operations on streams are no-op now. |
| for (int i = 0; i < stream_count; ++i) { |
| if (i) { |
| streams_under_test[i].mixable_stream->Stop(); |
| } |
| streams_under_test[i].mixable_stream->Close(); |
| } |
| } |
| |
| TEST_P(OutputDeviceMixerImplTest, NStreamsPlayingMixed_DeviceChange) { |
| std::unique_ptr<OutputDeviceMixer> mixer = CreateMixerUnderTest(); |
| |
| int stream_count = GetParam(); |
| |
| MockListener listener; |
| ExpectMixingGraphOutputStreamOpen(); |
| mixer->StartListening(&listener); |
| VerifyAndClearAllExpectations(); |
| |
| std::vector<StreamUnderTest> streams_under_test; |
| for (int i = 0; i < stream_count; ++i) { |
| streams_under_test.push_back(CreateNextStreamUnderTest(mixer.get())); |
| OpenAndVerifyStreamUnderTest(streams_under_test.back(), |
| PlaybackMode::kMixing); |
| if (i) { // Leave one stream just open. |
| StartAndVerifyStreamUnderTest(streams_under_test.back(), |
| PlaybackMode::kMixing); |
| } |
| } |
| |
| for (int i = 0; i < stream_count; ++i) { |
| if (i) { |
| EXPECT_CALL(streams_under_test[i].mix_track_mock->graph_input, Stop); |
| } |
| ExpectIndependentRenderingStreamStreamClosedIfItWasOpen( |
| streams_under_test[i]); |
| EXPECT_CALL(*this, OnDeviceChangeForMixMember(AudioParamsEq( |
| streams_under_test[i].mix_track_mock->params))); |
| } |
| |
| if (stream_count > 1) // Since we do not start the first stream. |
| ExpectMixingGraphOutputStreamStopped(); |
| ExpectMixingGraphOutputStreamClosed(); |
| |
| mixer->ProcessDeviceChange(); |
| VerifyAndClearAllExpectations(); |
| |
| // Stop() and Close() operations on streams are no-op now. |
| for (int i = 0; i < stream_count; ++i) { |
| if (i) { |
| streams_under_test[i].mixable_stream->Stop(); |
| } |
| streams_under_test[i].mixable_stream->Close(); |
| } |
| } |
| |
| TEST_F(OutputDeviceMixerImplTest, OnMoreDataDeliversCallbacks) { |
| std::unique_ptr<OutputDeviceMixer> mixer = CreateMixerUnderTest(); |
| |
| MockListener listener1; |
| MockListener listener2; |
| |
| ExpectMixingGraphOutputStreamOpen(); |
| mixer->StartListening(&listener1); |
| VerifyAndClearAllExpectations(); |
| |
| StreamUnderTest stream_under_test = CreateNextStreamUnderTest(mixer.get()); |
| OpenAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kMixing); |
| StartAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kMixing); |
| |
| EXPECT_CALL(listener1, OnPlayoutData(_, _, _)).Times(1); |
| mock_mixing_graph_->SimulateOnMoreData(); |
| VerifyAndClearAllExpectations(); |
| |
| mixer->StartListening(&listener2); |
| |
| EXPECT_CALL(listener1, OnPlayoutData(_, _, _)).Times(1); |
| EXPECT_CALL(listener2, OnPlayoutData(_, _, _)).Times(1); |
| mock_mixing_graph_->SimulateOnMoreData(); |
| VerifyAndClearAllExpectations(); |
| |
| mixer->StopListening(&listener1); |
| |
| EXPECT_CALL(listener2, OnPlayoutData(_, _, _)).Times(1); |
| mock_mixing_graph_->SimulateOnMoreData(); |
| VerifyAndClearAllExpectations(); |
| |
| StopAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kMixing); |
| CloseAndVerifyStreamUnderTest(stream_under_test); |
| |
| ExpectMixingGraphOutputStreamStopped(); |
| ExpectMixingGraphOutputStreamClosed(); |
| mixer->StopListening(&listener2); |
| } |
| |
| TEST_F(OutputDeviceMixerImplTest, |
| MixingStreamCreationFailureHandledOnMixingPlaybackStart) { |
| std::unique_ptr<OutputDeviceMixer> mixer = CreateMixerUnderTest(); |
| MockListener listener; |
| // Fail creating the mixing stream. |
| EXPECT_CALL(*this, |
| MockCreateOutputStream(AudioParamsEq(mixer_output_params_))) |
| .WillOnce(Return(false)); |
| mixer->StartListening(&listener); |
| VerifyAndClearAllExpectations(); |
| |
| StreamUnderTest stream_under_test = CreateNextStreamUnderTest(mixer.get()); |
| OpenAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kMixing); |
| |
| // Since the previous attempt to create the mixing stream failed, it will |
| // retry now when starting mixing playback. Fail it again. |
| EXPECT_CALL(*this, |
| MockCreateOutputStream(AudioParamsEq(mixer_output_params_))) |
| .WillOnce(Return(false)); |
| |
| EXPECT_CALL(stream_under_test.mix_track_mock->source_callback, OnError(_)); |
| |
| stream_under_test.mixable_stream->Start( |
| &stream_under_test.mix_track_mock->source_callback); |
| |
| VerifyAndClearAllExpectations(); |
| |
| // Since mixing has not started, the mixer considers it as independent |
| // playback and will stop the independent rendering stream. It's ok: |
| // media::AudioOutputStream can be closed multiple times. |
| StopAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kIndependent); |
| CloseAndVerifyStreamUnderTest(stream_under_test); |
| |
| mixer->StopListening(&listener); |
| } |
| |
| TEST_F(OutputDeviceMixerImplTest, |
| MixingStreamOpenFailureHandled_ListenerPresent) { |
| std::unique_ptr<OutputDeviceMixer> mixer = CreateMixerUnderTest(); |
| |
| MockListener listener; |
| // Fail opening the mixing stream. |
| EXPECT_CALL(*this, |
| MockCreateOutputStream(AudioParamsEq(mixer_output_params_))); |
| EXPECT_CALL(mock_mixing_graph_output_stream_, Open()).WillOnce(Return(false)); |
| EXPECT_CALL(mock_mixing_graph_output_stream_, Close()); |
| mixer->StartListening(&listener); |
| VerifyAndClearAllExpectations(); |
| |
| StreamUnderTest stream_under_test = CreateNextStreamUnderTest(mixer.get()); |
| OpenAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kMixing); |
| |
| // Since the previous attempt to create the mixing stream failed, it will |
| // retry now when starting mixing playback. Fail it again. |
| EXPECT_CALL(*this, |
| MockCreateOutputStream(AudioParamsEq(mixer_output_params_))); |
| EXPECT_CALL(mock_mixing_graph_output_stream_, Open()).WillOnce(Return(false)); |
| EXPECT_CALL(mock_mixing_graph_output_stream_, Close()); |
| |
| EXPECT_CALL(stream_under_test.mix_track_mock->source_callback, OnError(_)); |
| |
| stream_under_test.mixable_stream->Start( |
| &stream_under_test.mix_track_mock->source_callback); |
| |
| VerifyAndClearAllExpectations(); |
| |
| // Since mixing has not started, the mixer considers it as independent |
| // playback and will stop the independent rendering stream. It's ok: |
| // media::AudioOutputStream can be closed multiple times. |
| StopAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kIndependent); |
| CloseAndVerifyStreamUnderTest(stream_under_test); |
| |
| mixer->StopListening(&listener); |
| } |
| |
| TEST_F(OutputDeviceMixerImplTest, |
| MixingStreamOpenFailureHandled_WhenStartListening) { |
| std::unique_ptr<OutputDeviceMixer> mixer = CreateMixerUnderTest(); |
| |
| MockListener listener; |
| |
| StreamUnderTest stream_under_test = CreateNextStreamUnderTest(mixer.get()); |
| OpenAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kIndependent); |
| StartAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kIndependent); |
| |
| EXPECT_CALL(*this, |
| MockCreateOutputStream(AudioParamsEq(mixer_output_params_))); |
| EXPECT_CALL(mock_mixing_graph_output_stream_, Open()).WillOnce(Return(false)); |
| EXPECT_CALL(mock_mixing_graph_output_stream_, Close()); |
| EXPECT_CALL(stream_under_test.mix_track_mock->rendering_stream, Stop); |
| EXPECT_CALL(stream_under_test.mix_track_mock->source_callback, OnError(_)); |
| |
| mixer->StartListening(&listener); |
| VerifyAndClearAllExpectations(); |
| |
| // Since mixing has not started, the mixer considers it as independent |
| // playback and will stop the independent rendering stream. It's ok: |
| // media::AudioOutputStream can be closed multiple times. |
| StopAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kIndependent); |
| CloseAndVerifyStreamUnderTest(stream_under_test); |
| |
| mixer->StopListening(&listener); |
| } |
| |
| TEST_F(OutputDeviceMixerImplTest, |
| MixingStreamCreationFailureHandled_NextMixingSuccessful) { |
| std::unique_ptr<OutputDeviceMixer> mixer = CreateMixerUnderTest(); |
| |
| MockListener listener; |
| // Fail creating the mixing stream. |
| EXPECT_CALL(*this, |
| MockCreateOutputStream(AudioParamsEq(mixer_output_params_))) |
| .WillOnce(Return(false)); |
| mixer->StartListening(&listener); |
| VerifyAndClearAllExpectations(); |
| |
| StreamUnderTest stream_under_test1 = CreateNextStreamUnderTest(mixer.get()); |
| OpenAndVerifyStreamUnderTest(stream_under_test1, PlaybackMode::kMixing); |
| |
| // Fail creating the mixing stream again. |
| EXPECT_CALL(*this, |
| MockCreateOutputStream(AudioParamsEq(mixer_output_params_))) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(stream_under_test1.mix_track_mock->source_callback, OnError(_)); |
| |
| stream_under_test1.mixable_stream->Start( |
| &stream_under_test1.mix_track_mock->source_callback); |
| VerifyAndClearAllExpectations(); |
| |
| // Since mixing has not started, the mixer considers it as independent |
| // playback and will stop the independent rendering stream. It's ok: |
| // media::AudioOutputStream can be closed multiple times. |
| StopAndVerifyStreamUnderTest(stream_under_test1, PlaybackMode::kIndependent); |
| CloseAndVerifyStreamUnderTest(stream_under_test1); |
| |
| // This time mixing should be successful. |
| StreamUnderTest stream_under_test2 = CreateNextStreamUnderTest(mixer.get()); |
| OpenAndVerifyStreamUnderTest(stream_under_test2, PlaybackMode::kMixing); |
| |
| ExpectMixingGraphOutputStreamOpen(); |
| StartAndVerifyStreamUnderTest(stream_under_test2, PlaybackMode::kMixing); |
| StopAndVerifyStreamUnderTest(stream_under_test2, PlaybackMode::kMixing); |
| CloseAndVerifyStreamUnderTest(stream_under_test2); |
| |
| ExpectMixingGraphOutputStreamStopped(); |
| ExpectMixingGraphOutputStreamClosed(); |
| mixer->StopListening(&listener); |
| } |
| |
| TEST_F(OutputDeviceMixerImplTest, |
| StartListening_OpenStream_StopListening_StartIndependentPlayback) { |
| std::unique_ptr<OutputDeviceMixer> mixer = CreateMixerUnderTest(); |
| |
| MockListener listener; |
| ExpectMixingGraphOutputStreamOpen(); |
| mixer->StartListening(&listener); |
| VerifyAndClearAllExpectations(); |
| |
| StreamUnderTest stream_under_test = CreateNextStreamUnderTest(mixer.get()); |
| OpenAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kMixing); |
| |
| ExpectMixingGraphOutputStreamClosed(); |
| mixer->StopListening(&listener); |
| VerifyAndClearAllExpectations(); |
| |
| // Since there were listeners attached when |stream_under_test| was open, its |
| // physical rendering stream is not open yet. |
| SetIndependentRenderingStreamOpenExpectations( |
| stream_under_test.mix_track_mock, |
| /*open_success=*/true); |
| StartAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kIndependent); |
| StopAndVerifyStreamUnderTest(stream_under_test, PlaybackMode::kIndependent); |
| CloseAndVerifyStreamUnderTest(stream_under_test); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(, |
| OutputDeviceMixerImplTest, |
| testing::Values(0, 1, 2, 3)); |
| |
| INSTANTIATE_TEST_SUITE_P(, |
| OutputDeviceMixerImplTestWithDefault, |
| testing::Values(kDeviceId, "")); |
| |
| } // namespace |
| |
| } // namespace audio |