| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/mirroring/service/media_remoter.h" |
| |
| #include "base/run_loop.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/test/mock_callback.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/task_environment.h" |
| #include "base/time/default_tick_clock.h" |
| #include "components/mirroring/mojom/cast_message_channel.mojom.h" |
| #include "components/mirroring/mojom/session_parameters.mojom.h" |
| #include "components/mirroring/service/mirror_settings.h" |
| #include "components/mirroring/service/rpc_dispatcher.h" |
| #include "components/openscreen_platform/task_runner.h" |
| #include "media/base/audio_codecs.h" |
| #include "media/base/video_codecs.h" |
| #include "media/cast/test/openscreen_test_helpers.h" |
| #include "media/cast/test/test_with_cast_environment.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using media::mojom::RemotingSinkMetadata; |
| using media::mojom::RemotingStopReason; |
| using mirroring::mojom::SessionType; |
| using ::testing::_; |
| using ::testing::InvokeWithoutArgs; |
| using ::testing::Mock; |
| |
| namespace mirroring { |
| |
| namespace { |
| |
| // Mojo handles used for managing the remoting data streams. |
| struct DataStreamHandles { |
| mojo::ScopedDataPipeProducerHandle audio_producer_handle; |
| mojo::ScopedDataPipeProducerHandle video_producer_handle; |
| |
| mojo::PendingRemote<media::mojom::RemotingDataStreamSender> |
| audio_stream_sender; |
| mojo::PendingRemote<media::mojom::RemotingDataStreamSender> |
| video_stream_sender; |
| }; |
| |
| class MockRemotingSource : public media::mojom::RemotingSource { |
| public: |
| MockRemotingSource() = default; |
| ~MockRemotingSource() override = default; |
| |
| void Bind(mojo::PendingReceiver<media::mojom::RemotingSource> receiver) { |
| receiver_.Bind(std::move(receiver)); |
| } |
| |
| MOCK_METHOD0(OnSinkGone, void()); |
| MOCK_METHOD0(OnStarted, void()); |
| MOCK_METHOD1(OnStartFailed, void(media::mojom::RemotingStartFailReason)); |
| MOCK_METHOD1(OnMessageFromSink, void(const std::vector<uint8_t>&)); |
| MOCK_METHOD1(OnStopped, void(RemotingStopReason)); |
| MOCK_METHOD1(OnSinkAvailable, void(const RemotingSinkMetadata&)); |
| void OnSinkAvailable( |
| media::mojom::RemotingSinkMetadataPtr metadata) override { |
| OnSinkAvailable(*metadata); |
| } |
| |
| private: |
| mojo::Receiver<media::mojom::RemotingSource> receiver_{this}; |
| }; |
| |
| class MockRpcDispatcher : public RpcDispatcher { |
| public: |
| MockRpcDispatcher() = default; |
| ~MockRpcDispatcher() override = default; |
| |
| MOCK_METHOD1(Subscribe, void(ResponseCallback)); |
| MOCK_METHOD0(Unsubscribe, void()); |
| MOCK_METHOD1(SendOutboundMessage, bool(base::span<const uint8_t> message)); |
| }; |
| |
| RemotingSinkMetadata DefaultSinkMetadata() { |
| RemotingSinkMetadata metadata; |
| metadata.features.push_back(media::mojom::RemotingSinkFeature::RENDERING); |
| metadata.video_capabilities.push_back( |
| media::mojom::RemotingSinkVideoCapability::CODEC_VP8); |
| metadata.audio_capabilities.push_back( |
| media::mojom::RemotingSinkAudioCapability::CODEC_BASELINE_SET); |
| metadata.friendly_name = "Test"; |
| return metadata; |
| } |
| |
| } // namespace |
| |
| class MediaRemoterTest : public mojom::CastMessageChannel, |
| public MediaRemoter::Client, |
| public media::cast::TestWithCastEnvironment { |
| public: |
| MediaRemoterTest() : sink_metadata_(DefaultSinkMetadata()) {} |
| |
| MediaRemoterTest(MediaRemoterTest&&) = delete; |
| MediaRemoterTest(const MediaRemoterTest&) = delete; |
| MediaRemoterTest& operator=(const MediaRemoterTest&) = delete; |
| MediaRemoterTest& operator=(MediaRemoterTest&&) = delete; |
| ~MediaRemoterTest() override = default; |
| |
| protected: |
| // mojom::CastMessageChannel mock implementation (inbound messages). |
| MOCK_METHOD1(OnMessage, void(mojom::CastMessagePtr)); |
| MOCK_METHOD0(OnConnectToRemotingSource, void()); |
| MOCK_METHOD0(RequestRemotingStreaming, void()); |
| MOCK_METHOD0(RestartMirroringStreaming, void()); |
| |
| // MediaRemoter::Client implementation. |
| void ConnectToRemotingSource( |
| mojo::PendingRemote<media::mojom::Remoter> remoter, |
| mojo::PendingReceiver<media::mojom::RemotingSource> source_receiver) |
| override { |
| remoter_.Bind(std::move(remoter)); |
| remoting_source_.Bind(std::move(source_receiver)); |
| OnConnectToRemotingSource(); |
| } |
| |
| void CreateRemoter() { |
| EXPECT_FALSE(media_remoter_); |
| EXPECT_CALL(*this, OnConnectToRemotingSource()); |
| EXPECT_CALL(remoting_source_, OnSinkAvailable(_)); |
| media_remoter_ = |
| std::make_unique<MediaRemoter>(*this, sink_metadata_, rpc_dispatcher_); |
| RunUntilIdle(); |
| Mock::VerifyAndClear(this); |
| Mock::VerifyAndClear(&remoting_source_); |
| } |
| |
| // Requests to start a remoting session. |
| void StartRemoting() { |
| ASSERT_TRUE(remoter_); |
| EXPECT_CALL(*this, RequestRemotingStreaming()); |
| remoter_->Start(); |
| RunUntilIdle(); |
| Mock::VerifyAndClear(this); |
| } |
| |
| // Stops the current remoting session. |
| void StopRemoting() { |
| ASSERT_TRUE(remoter_); |
| EXPECT_CALL(remoting_source_, OnStopped(RemotingStopReason::USER_DISABLED)); |
| EXPECT_CALL(remoting_source_, OnSinkGone()); |
| EXPECT_CALL(rpc_dispatcher_, Unsubscribe()); |
| EXPECT_CALL(*this, RestartMirroringStreaming()); |
| remoter_->Stop(media::mojom::RemotingStopReason::USER_DISABLED); |
| RunUntilIdle(); |
| Mock::VerifyAndClear(this); |
| Mock::VerifyAndClear(&remoting_source_); |
| } |
| |
| // Signals that a remoting streaming session starts successfully. |
| void RemotingStreamingStarted() { |
| ASSERT_TRUE(media_remoter_); |
| |
| // Cannot have two instances of test senders at once. |
| openscreen_test_senders_.reset(); |
| openscreen_test_senders_ = |
| std::make_unique<media::cast::OpenscreenTestSenders>( |
| media::cast::OpenscreenTestSenders::Config( |
| base::SequencedTaskRunner::GetCurrentDefault(), |
| GetMockTickClock(), |
| openscreen::cast::RtpPayloadType::kAudioVarious, |
| openscreen::cast::RtpPayloadType::kVideoVarious)); |
| media_remoter_->StartRpcMessaging( |
| cast_environment(), std::move(openscreen_test_senders_->audio_sender), |
| std::move(openscreen_test_senders_->video_sender), |
| MirrorSettings::GetDefaultAudioConfig(media::AudioCodec::kUnknown), |
| MirrorSettings::GetDefaultVideoConfig(media::VideoCodec::kUnknown)); |
| RunUntilIdle(); |
| Mock::VerifyAndClear(&remoting_source_); |
| } |
| |
| // Signals that mirroring is resumed successfully. |
| void MirroringResumed(bool is_remoting_disabled) { |
| // When MediaRemoter is in the REMOTING_DISABLED state, it should not notify |
| // its remoting_source_ about available sinks. |
| EXPECT_CALL(remoting_source_, OnSinkAvailable(_)) |
| .Times(is_remoting_disabled ? 0 : 1); |
| media_remoter_->OnMirroringResumed(); |
| RunUntilIdle(); |
| Mock::VerifyAndClear(&remoting_source_); |
| } |
| |
| // Signals that remoting session failed to start. |
| void RemotingStartFailed() { |
| ASSERT_TRUE(media_remoter_); |
| EXPECT_CALL(remoting_source_, OnStartFailed(_)); |
| EXPECT_CALL(remoting_source_, OnSinkGone()); |
| EXPECT_CALL(*this, RestartMirroringStreaming()); |
| media_remoter_->OnRemotingFailed(); |
| RunUntilIdle(); |
| Mock::VerifyAndClear(this); |
| Mock::VerifyAndClear(&remoting_source_); |
| } |
| |
| void StartDataStreams(SessionType session_type) { |
| static constexpr int kDataPipeCapacity = 1000; |
| data_stream_handles_ = std::make_unique<DataStreamHandles>(); |
| mojo::ScopedDataPipeConsumerHandle audio_consumer_handle; |
| mojo::ScopedDataPipeConsumerHandle video_consumer_handle; |
| |
| if (session_type != SessionType::VIDEO_ONLY) { |
| EXPECT_EQ(MOJO_RESULT_OK, mojo::CreateDataPipe( |
| kDataPipeCapacity, |
| data_stream_handles_->audio_producer_handle, |
| audio_consumer_handle)); |
| } |
| |
| if (session_type != SessionType::AUDIO_ONLY) { |
| EXPECT_EQ(MOJO_RESULT_OK, mojo::CreateDataPipe( |
| kDataPipeCapacity, |
| data_stream_handles_->video_producer_handle, |
| video_consumer_handle)); |
| } |
| |
| remoter_->StartDataStreams(std::move(audio_consumer_handle), |
| std::move(video_consumer_handle), |
| (session_type != SessionType::VIDEO_ONLY) |
| ? data_stream_handles_->audio_stream_sender |
| .InitWithNewPipeAndPassReceiver() |
| : mojo::NullReceiver(), |
| (session_type != SessionType::AUDIO_ONLY) |
| ? data_stream_handles_->video_stream_sender |
| .InitWithNewPipeAndPassReceiver() |
| : mojo::NullReceiver()); |
| } |
| |
| testing::StrictMock<MockRemotingSource>& remoting_source() { |
| return remoting_source_; |
| } |
| |
| testing::StrictMock<MockRpcDispatcher>& rpc_dispatcher() { |
| return rpc_dispatcher_; |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| mojo::Receiver<mojom::CastMessageChannel> receiver_{this}; |
| mojo::Remote<mojom::CastMessageChannel> inbound_channel_; |
| testing::StrictMock<MockRpcDispatcher> rpc_dispatcher_; |
| const media::mojom::RemotingSinkMetadata sink_metadata_; |
| testing::StrictMock<MockRemotingSource> remoting_source_; |
| mojo::Remote<media::mojom::Remoter> remoter_; |
| |
| // Configured for use by the media remoter. |
| std::unique_ptr<media::cast::OpenscreenTestSenders> openscreen_test_senders_; |
| std::unique_ptr<DataStreamHandles> data_stream_handles_; |
| std::unique_ptr<MediaRemoter> media_remoter_; |
| }; |
| |
| TEST_F(MediaRemoterTest, StartAndStopRemoting) { |
| CreateRemoter(); |
| StartRemoting(); |
| EXPECT_CALL(remoting_source(), OnStarted()); |
| EXPECT_CALL(rpc_dispatcher(), Subscribe(_)); |
| RemotingStreamingStarted(); |
| StartDataStreams(SessionType::AUDIO_AND_VIDEO); |
| StopRemoting(); |
| } |
| |
| TEST_F(MediaRemoterTest, StartAndStopRemotingAudioOnly) { |
| CreateRemoter(); |
| StartRemoting(); |
| EXPECT_CALL(remoting_source(), OnStarted()); |
| EXPECT_CALL(rpc_dispatcher(), Subscribe(_)); |
| RemotingStreamingStarted(); |
| StartDataStreams(SessionType::AUDIO_ONLY); |
| StopRemoting(); |
| } |
| |
| TEST_F(MediaRemoterTest, StartAndStopRemotingVideoOnly) { |
| CreateRemoter(); |
| StartRemoting(); |
| EXPECT_CALL(remoting_source(), OnStarted()); |
| EXPECT_CALL(rpc_dispatcher(), Subscribe(_)); |
| RemotingStreamingStarted(); |
| StartDataStreams(SessionType::VIDEO_ONLY); |
| StopRemoting(); |
| } |
| |
| TEST_F(MediaRemoterTest, StartRemotingWithoutCallingStart) { |
| CreateRemoter(); |
| // Should fail since we didn't call `StartRemoting().` |
| EXPECT_CALL(remoting_source(), OnStarted()).Times(0); |
| RemotingStreamingStarted(); |
| } |
| |
| TEST_F(MediaRemoterTest, StopRemotingWhileStarting) { |
| CreateRemoter(); |
| // Starts a remoting session. |
| StartRemoting(); |
| // Immediately stops the remoting session while not started yet. |
| StopRemoting(); |
| |
| // Signals that successfully switch to mirroring. |
| MirroringResumed(/* is_remoting_disabled */ false); |
| // Now remoting can be started again. |
| StartRemoting(); |
| EXPECT_CALL(*this, RestartMirroringStreaming()); |
| |
| // Called when MediaRemoter is destroyed. |
| EXPECT_CALL(rpc_dispatcher(), Unsubscribe()); |
| } |
| |
| TEST_F(MediaRemoterTest, RemotingStartFailed) { |
| CreateRemoter(); |
| StartRemoting(); |
| RemotingStartFailed(); |
| StopRemoting(); |
| EXPECT_CALL(*this, RestartMirroringStreaming()); |
| MirroringResumed(/* is_remoting_disabled */ true); |
| |
| // Called when MediaRemoter is destroyed. |
| EXPECT_CALL(rpc_dispatcher(), Unsubscribe()); |
| } |
| |
| TEST_F(MediaRemoterTest, SwitchBetweenMultipleSessions) { |
| CreateRemoter(); |
| |
| // Start a remoting session. |
| StartRemoting(); |
| EXPECT_CALL(remoting_source(), OnStarted()); |
| EXPECT_CALL(rpc_dispatcher(), Subscribe(_)); |
| RemotingStreamingStarted(); |
| StartDataStreams(SessionType::AUDIO_AND_VIDEO); |
| |
| // Stop the remoting session and switch to mirroring. |
| StopRemoting(); |
| MirroringResumed(/* is_remoting_disabled */ false); |
| |
| // Switch to remoting again. |
| StartRemoting(); |
| EXPECT_CALL(remoting_source(), OnStarted()); |
| EXPECT_CALL(rpc_dispatcher(), Subscribe(_)); |
| RemotingStreamingStarted(); |
| StartDataStreams(SessionType::AUDIO_AND_VIDEO); |
| |
| // Switch to mirroring again. |
| StopRemoting(); |
| MirroringResumed(/* is_remoting_disabled */ false); |
| } |
| |
| } // namespace mirroring |