// Copyright 2016 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 #include #include "base/bind.h" #include "base/memory/ptr_util.h" #include "base/memory/raw_ptr.h" #include "base/run_loop.h" #include "base/test/gmock_callback_support.h" #include "base/test/test_message_loop.h" #include "base/threading/platform_thread.h" #include "base/threading/thread_task_runner_handle.h" #include "base/timer/elapsed_timer.h" #include "media/base/cdm_config.h" #include "media/base/cdm_context.h" #include "media/base/mock_filters.h" #include "media/base/test_helpers.h" #include "media/cdm/default_cdm_factory.h" #include "media/mojo/clients/mojo_renderer.h" #include "media/mojo/common/media_type_converters.h" #include "media/mojo/mojom/content_decryption_module.mojom.h" #include "media/mojo/mojom/renderer.mojom.h" #include "media/mojo/services/mojo_cdm_service.h" #include "media/mojo/services/mojo_cdm_service_context.h" #include "media/mojo/services/mojo_renderer_service.h" #include "media/renderers/video_overlay_factory.h" #include "mojo/public/cpp/bindings/pending_remote.h" #include "mojo/public/cpp/bindings/receiver.h" #include "mojo/public/cpp/bindings/remote.h" #include "mojo/public/cpp/bindings/self_owned_receiver.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" using ::base::test::RunCallback; using ::base::test::RunOnceCallback; using ::base::test::RunOnceClosure; using ::testing::_; using ::testing::DoAll; using ::testing::Return; using ::testing::SaveArg; using ::testing::StrictMock; namespace media { namespace { const int64_t kStartPlayingTimeInMs = 100; const char kClearKeyKeySystem[] = "org.w3.clearkey"; ACTION_P2(GetMediaTime, start_time, elapsed_timer) { return start_time + elapsed_timer->Elapsed(); } void WaitFor(base::TimeDelta duration) { base::RunLoop run_loop; base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, run_loop.QuitClosure(), duration); run_loop.Run(); } } // namespace class MojoRendererTest : public ::testing::Test { public: MojoRendererTest() { std::unique_ptr> mock_renderer( new StrictMock()); mock_renderer_ = mock_renderer.get(); mojo::PendingRemote remote_renderer_remote; renderer_receiver_ = MojoRendererService::Create( &mojo_cdm_service_context_, std::move(mock_renderer), remote_renderer_remote.InitWithNewPipeAndPassReceiver()); mojo_renderer_ = std::make_unique( message_loop_.task_runner(), std::unique_ptr(nullptr), nullptr, std::move(remote_renderer_remote)); // CreateAudioStream() and CreateVideoStream() overrides expectations for // expected non-NULL streams. EXPECT_CALL(demuxer_, GetAllStreams()).WillRepeatedly(Return(streams_)); EXPECT_CALL(*mock_renderer_, GetMediaTime()) .WillRepeatedly(Return(base::TimeDelta())); } MojoRendererTest(const MojoRendererTest&) = delete; MojoRendererTest& operator=(const MojoRendererTest&) = delete; ~MojoRendererTest() override = default; void Destroy() { mojo_renderer_.reset(); base::RunLoop().RunUntilIdle(); } // Completion callbacks. MOCK_METHOD1(OnInitialized, void(PipelineStatus)); MOCK_METHOD0(OnFlushed, void()); MOCK_METHOD1(OnCdmAttached, void(bool)); std::unique_ptr> CreateStream( DemuxerStream::Type type) { std::unique_ptr> stream( new StrictMock(type)); return stream; } void CreateAudioStream() { audio_stream_ = CreateStream(DemuxerStream::AUDIO); audio_stream_->set_audio_decoder_config(TestAudioConfig::Normal()); streams_.push_back(audio_stream_.get()); EXPECT_CALL(demuxer_, GetAllStreams()).WillRepeatedly(Return(streams_)); } void CreateVideoStream(bool is_encrypted = false) { video_stream_ = CreateStream(DemuxerStream::VIDEO); video_stream_->set_video_decoder_config( is_encrypted ? TestVideoConfig::NormalEncrypted() : TestVideoConfig::Normal()); std::vector streams; streams_.push_back(audio_stream_.get()); EXPECT_CALL(demuxer_, GetAllStreams()).WillRepeatedly(Return(streams_)); } void InitializeAndExpect(PipelineStatus status) { DVLOG(1) << __func__ << ": " << status; EXPECT_CALL(*this, OnInitialized(SameStatusCode(status))); mojo_renderer_->Initialize(&demuxer_, &renderer_client_, base::BindOnce(&MojoRendererTest::OnInitialized, base::Unretained(this))); base::RunLoop().RunUntilIdle(); } void Initialize() { CreateAudioStream(); EXPECT_CALL(*mock_renderer_, SetVolume(1)); EXPECT_CALL(*mock_renderer_, OnInitialize(_, _, _)) .WillOnce(DoAll(SaveArg<1>(&remote_renderer_client_), RunOnceCallback<2>(PIPELINE_OK))); InitializeAndExpect(PIPELINE_OK); } void Flush() { DVLOG(1) << __func__; // Flush callback should always be fired. EXPECT_CALL(*this, OnFlushed()); mojo_renderer_->Flush( base::BindOnce(&MojoRendererTest::OnFlushed, base::Unretained(this))); base::RunLoop().RunUntilIdle(); } void SetCdmAndExpect(bool success) { DVLOG(1) << __func__; // Set CDM callback should always be fired. EXPECT_CALL(*this, OnCdmAttached(success)); mojo_renderer_->SetCdm(&cdm_context_, base::BindOnce(&MojoRendererTest::OnCdmAttached, base::Unretained(this))); base::RunLoop().RunUntilIdle(); } // Simulates a connection error at the client side by killing the service. // Note that |mock_renderer_| will also be destroyed, do NOT expect anything // on it. Otherwise the test will crash. void ConnectionError() { DVLOG(1) << __func__; DCHECK(renderer_receiver_); renderer_receiver_->Close(); base::RunLoop().RunUntilIdle(); } void OnCdmServiceInitialized(mojom::CdmContextPtr cdm_context, const std::string& error_message) { cdm_context_.set_cdm_id(cdm_context->cdm_id); } void CreateCdm() { mojo_cdm_service_ = std::make_unique(&mojo_cdm_service_context_); mojo_cdm_service_->Initialize( &cdm_factory_, {kClearKeyKeySystem, false, false, false}, base::BindOnce(&MojoRendererTest::OnCdmServiceInitialized, base::Unretained(this))); base::RunLoop().RunUntilIdle(); } void StartPlayingFrom(base::TimeDelta start_time) { EXPECT_CALL(*mock_renderer_, StartPlayingFrom(start_time)); mojo_renderer_->StartPlayingFrom(start_time); EXPECT_EQ(start_time, mojo_renderer_->GetMediaTime()); base::RunLoop().RunUntilIdle(); } void Play() { StartPlayingFrom(base::Milliseconds(kStartPlayingTimeInMs)); } // Fixture members. base::TestMessageLoop message_loop_; // The MojoRenderer that we are testing. std::unique_ptr mojo_renderer_; // Client side mocks and helpers. StrictMock renderer_client_; StrictMock cdm_context_; mojo::Remote cdm_remote_; // Client side mock demuxer and demuxer streams. StrictMock demuxer_; std::unique_ptr> audio_stream_; std::unique_ptr> video_stream_; std::vector streams_; // Service side bindings (declaration order is critical). MojoCdmServiceContext mojo_cdm_service_context_; DefaultCdmFactory cdm_factory_; std::unique_ptr mojo_cdm_service_; // Service side mocks and helpers. raw_ptr> mock_renderer_; RendererClient* remote_renderer_client_; mojo::SelfOwnedReceiverRef renderer_receiver_; }; TEST_F(MojoRendererTest, Initialize_Success) { Initialize(); } TEST_F(MojoRendererTest, Initialize_Failure) { CreateAudioStream(); // Mojo Renderer only expects a boolean result, which will be translated // to PIPELINE_OK or PIPELINE_ERROR_INITIALIZATION_FAILED. EXPECT_CALL(*mock_renderer_, OnInitialize(_, _, _)) .WillOnce(RunOnceCallback<2>(PIPELINE_ERROR_ABORT)); InitializeAndExpect(PIPELINE_ERROR_INITIALIZATION_FAILED); } TEST_F(MojoRendererTest, Initialize_BeforeConnectionError) { CreateAudioStream(); EXPECT_CALL(*mock_renderer_, OnInitialize(_, _, _)) .WillOnce(InvokeWithoutArgs(this, &MojoRendererTest::ConnectionError)); InitializeAndExpect(PIPELINE_ERROR_INITIALIZATION_FAILED); } TEST_F(MojoRendererTest, Initialize_AfterConnectionError) { ConnectionError(); CreateAudioStream(); InitializeAndExpect(PIPELINE_ERROR_INITIALIZATION_FAILED); } TEST_F(MojoRendererTest, Flush_Success) { Initialize(); EXPECT_CALL(*mock_renderer_, OnFlush(_)).WillOnce(RunOnceClosure<0>()); Flush(); } TEST_F(MojoRendererTest, Flush_ConnectionError) { Initialize(); // Upon connection error, OnError() should be called once and only once. EXPECT_CALL(renderer_client_, OnError(HasStatusCode(PIPELINE_ERROR_DISCONNECTED))) .Times(1); EXPECT_CALL(*mock_renderer_, OnFlush(_)) .WillOnce(InvokeWithoutArgs(this, &MojoRendererTest::ConnectionError)); Flush(); } TEST_F(MojoRendererTest, Flush_AfterConnectionError) { Initialize(); // Upon connection error, OnError() should be called once and only once. EXPECT_CALL(renderer_client_, OnError(HasStatusCode(PIPELINE_ERROR_DISCONNECTED))) .Times(1); ConnectionError(); Flush(); } TEST_F(MojoRendererTest, SetCdm_Success) { Initialize(); CreateCdm(); EXPECT_CALL(*mock_renderer_, OnSetCdm(_, _)) .WillOnce(RunOnceCallback<1>(true)); SetCdmAndExpect(true); } TEST_F(MojoRendererTest, SetCdm_Failure) { Initialize(); CreateCdm(); EXPECT_CALL(*mock_renderer_, OnSetCdm(_, _)) .WillOnce(RunOnceCallback<1>(false)); SetCdmAndExpect(false); } TEST_F(MojoRendererTest, SetCdm_InvalidCdmId) { Initialize(); SetCdmAndExpect(false); } TEST_F(MojoRendererTest, SetCdm_NonExistCdmId) { Initialize(); cdm_context_.set_cdm_id(base::UnguessableToken::Create()); SetCdmAndExpect(false); } TEST_F(MojoRendererTest, SetCdm_ReleasedCdmId) { // The CdmContext set on |mock_renderer_|. CdmContext* mock_renderer_cdm_context = nullptr; Initialize(); CreateCdm(); EXPECT_CALL(*mock_renderer_, OnSetCdm(_, _)) .WillOnce(DoAll(SaveArg<0>(&mock_renderer_cdm_context), RunOnceCallback<1>(true))); SetCdmAndExpect(true); EXPECT_TRUE(mock_renderer_cdm_context); // Release the CDM. mojo_cdm_service_.reset(); base::RunLoop().RunUntilIdle(); // SetCdm() on |mock_renderer_| should not be called. SetCdmAndExpect(false); // The CDM should still be around since it's set on the |mock_renderer_|. It // should have a Decryptor since we use kClearKeyKeySystem. EXPECT_TRUE(mock_renderer_cdm_context->GetDecryptor()); } TEST_F(MojoRendererTest, SetCdm_BeforeInitialize) { CreateCdm(); EXPECT_CALL(*mock_renderer_, OnSetCdm(_, _)) .WillOnce(RunOnceCallback<1>(true)); SetCdmAndExpect(true); } TEST_F(MojoRendererTest, SetCdm_AfterInitializeAndConnectionError) { CreateCdm(); Initialize(); EXPECT_CALL(renderer_client_, OnError(HasStatusCode(PIPELINE_ERROR_DISCONNECTED))) .Times(1); ConnectionError(); SetCdmAndExpect(false); } TEST_F(MojoRendererTest, SetCdm_AfterConnectionErrorAndBeforeInitialize) { CreateCdm(); // Initialize() is not called so RendererClient::OnError() is not expected. ConnectionError(); SetCdmAndExpect(false); InitializeAndExpect(PIPELINE_ERROR_INITIALIZATION_FAILED); } TEST_F(MojoRendererTest, SetCdm_BeforeInitializeAndConnectionError) { CreateCdm(); EXPECT_CALL(*mock_renderer_, OnSetCdm(_, _)) .WillOnce(RunOnceCallback<1>(true)); SetCdmAndExpect(true); // Initialize() is not called so RendererClient::OnError() is not expected. ConnectionError(); CreateAudioStream(); InitializeAndExpect(PIPELINE_ERROR_INITIALIZATION_FAILED); } TEST_F(MojoRendererTest, StartPlayingFrom) { Initialize(); Play(); } TEST_F(MojoRendererTest, GetMediaTime) { Initialize(); EXPECT_EQ(base::TimeDelta(), mojo_renderer_->GetMediaTime()); const base::TimeDelta kSleepTime = base::Milliseconds(500); const base::TimeDelta kStartTime = base::Milliseconds(kStartPlayingTimeInMs); // Media time should not advance since playback rate is 0. EXPECT_CALL(*mock_renderer_, SetPlaybackRate(0)); EXPECT_CALL(*mock_renderer_, StartPlayingFrom(kStartTime)); EXPECT_CALL(*mock_renderer_, GetMediaTime()) .WillRepeatedly(Return(kStartTime)); mojo_renderer_->SetPlaybackRate(0); mojo_renderer_->StartPlayingFrom(kStartTime); WaitFor(kSleepTime); EXPECT_EQ(kStartTime, mojo_renderer_->GetMediaTime()); // Media time should now advance since playback rate is > 0. std::unique_ptr elapsed_timer(new base::ElapsedTimer); EXPECT_CALL(*mock_renderer_, SetPlaybackRate(1.0)); EXPECT_CALL(*mock_renderer_, GetMediaTime()) .WillRepeatedly(GetMediaTime(kStartTime, elapsed_timer.get())); mojo_renderer_->SetPlaybackRate(1.0); WaitFor(kSleepTime); EXPECT_GT(mojo_renderer_->GetMediaTime(), kStartTime); // Flushing should pause media-time updates. EXPECT_CALL(*mock_renderer_, OnFlush(_)).WillOnce(RunOnceClosure<0>()); Flush(); base::TimeDelta pause_time = mojo_renderer_->GetMediaTime(); EXPECT_GT(pause_time, kStartTime); WaitFor(kSleepTime); EXPECT_EQ(pause_time, mojo_renderer_->GetMediaTime()); Destroy(); } TEST_F(MojoRendererTest, OnBufferingStateChange) { Initialize(); Play(); EXPECT_CALL(renderer_client_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH, BUFFERING_CHANGE_REASON_UNKNOWN)) .Times(1); remote_renderer_client_->OnBufferingStateChange( BUFFERING_HAVE_ENOUGH, BUFFERING_CHANGE_REASON_UNKNOWN); EXPECT_CALL(renderer_client_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING, DECODER_UNDERFLOW)) .Times(1); remote_renderer_client_->OnBufferingStateChange(BUFFERING_HAVE_NOTHING, DECODER_UNDERFLOW); base::RunLoop().RunUntilIdle(); } TEST_F(MojoRendererTest, OnEnded) { Initialize(); Play(); EXPECT_CALL(renderer_client_, OnEnded()).Times(1); remote_renderer_client_->OnEnded(); base::RunLoop().RunUntilIdle(); } // TODO(xhwang): Add tests for all RendererClient methods. TEST_F(MojoRendererTest, Destroy_PendingInitialize) { CreateAudioStream(); EXPECT_CALL(*mock_renderer_, OnInitialize(_, _, _)) .WillRepeatedly(RunOnceCallback<2>(PIPELINE_ERROR_ABORT)); EXPECT_CALL(*this, OnInitialized( HasStatusCode(PIPELINE_ERROR_INITIALIZATION_FAILED))); mojo_renderer_->Initialize( &demuxer_, &renderer_client_, base::BindOnce(&MojoRendererTest::OnInitialized, base::Unretained(this))); Destroy(); } TEST_F(MojoRendererTest, Destroy_PendingFlush) { EXPECT_CALL(*mock_renderer_, OnSetCdm(_, _)) .WillRepeatedly(RunOnceCallback<1>(true)); EXPECT_CALL(*this, OnCdmAttached(false)); mojo_renderer_->SetCdm( &cdm_context_, base::BindOnce(&MojoRendererTest::OnCdmAttached, base::Unretained(this))); Destroy(); } TEST_F(MojoRendererTest, Destroy_PendingSetCdm) { Initialize(); EXPECT_CALL(*mock_renderer_, OnFlush(_)).WillRepeatedly(RunOnceClosure<0>()); EXPECT_CALL(*this, OnFlushed()); mojo_renderer_->Flush( base::BindOnce(&MojoRendererTest::OnFlushed, base::Unretained(this))); Destroy(); } // TODO(xhwang): Add more tests on OnError. For example, ErrorDuringFlush, // ErrorAfterFlush etc. TEST_F(MojoRendererTest, ErrorDuringPlayback) { Initialize(); EXPECT_CALL(renderer_client_, OnError(HasStatusCode(PIPELINE_ERROR_DECODE))) .Times(1); Play(); remote_renderer_client_->OnError(PIPELINE_ERROR_DECODE); base::RunLoop().RunUntilIdle(); EXPECT_CALL(*mock_renderer_, SetPlaybackRate(0.0)).Times(1); mojo_renderer_->SetPlaybackRate(0.0); Flush(); } } // namespace media