blob: a2876d37944ce97cf1258b76fb76d0ba29fe5f41 [file] [log] [blame]
// Copyright 2015 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 <stdint.h>
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/task/post_task.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "media/base/cdm_config.h"
#include "media/base/mock_filters.h"
#include "media/base/test_helpers.h"
#include "media/mojo/buildflags.h"
#include "media/mojo/clients/mojo_decryptor.h"
#include "media/mojo/clients/mojo_demuxer_stream_impl.h"
#include "media/mojo/common/media_type_converters.h"
#include "media/mojo/mojom/cdm_proxy.mojom.h"
#include "media/mojo/mojom/constants.mojom.h"
#include "media/mojo/mojom/content_decryption_module.mojom.h"
#include "media/mojo/mojom/decryptor.mojom.h"
#include "media/mojo/mojom/interface_factory.mojom.h"
#include "media/mojo/mojom/media_service.mojom.h"
#include "media/mojo/mojom/renderer.mojom.h"
#include "media/mojo/services/media_interface_provider.h"
#include "media/mojo/services/media_manifest.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/service_manager/public/cpp/manifest_builder.h"
#include "services/service_manager/public/cpp/test/test_service.h"
#include "services/service_manager/public/cpp/test/test_service_manager.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
#include "media/cdm/cdm_paths.h" // nogncheck
#include "media/mojo/mojom/cdm_proxy.mojom.h"
#endif
namespace media {
namespace {
using testing::_;
using testing::DoAll;
using testing::Invoke;
using testing::InvokeWithoutArgs;
using testing::NiceMock;
using testing::SaveArg;
using testing::StrictMock;
using testing::WithArg;
MATCHER_P(MatchesResult, success, "") {
return arg->success == success;
}
#if BUILDFLAG(ENABLE_MOJO_CDM) && !defined(OS_ANDROID)
const char kClearKeyKeySystem[] = "org.w3.clearkey";
const char kInvalidKeySystem[] = "invalid.key.system";
#endif
const char kSecurityOrigin[] = "https://foo.com";
// Returns a trivial encrypted DecoderBuffer.
scoped_refptr<DecoderBuffer> CreateEncryptedBuffer() {
scoped_refptr<DecoderBuffer> encrypted_buffer(new DecoderBuffer(100));
encrypted_buffer->set_decrypt_config(
DecryptConfig::CreateCencConfig("dummy_key_id", "0123456789ABCDEF", {}));
return encrypted_buffer;
}
class MockCdmProxyClient : public mojom::CdmProxyClient {
public:
MockCdmProxyClient() = default;
~MockCdmProxyClient() override = default;
// mojom::CdmProxyClient implementation.
MOCK_METHOD0(NotifyHardwareReset, void());
private:
DISALLOW_COPY_AND_ASSIGN(MockCdmProxyClient);
};
class MockRendererClient : public mojom::RendererClient {
public:
MockRendererClient() = default;
~MockRendererClient() override = default;
// mojom::RendererClient implementation.
MOCK_METHOD3(OnTimeUpdate,
void(base::TimeDelta time,
base::TimeDelta max_time,
base::TimeTicks capture_time));
MOCK_METHOD2(OnBufferingStateChange,
void(BufferingState state, BufferingStateChangeReason reason));
MOCK_METHOD0(OnEnded, void());
MOCK_METHOD0(OnError, void());
MOCK_METHOD1(OnVideoOpacityChange, void(bool opaque));
MOCK_METHOD1(OnAudioConfigChange, void(const AudioDecoderConfig&));
MOCK_METHOD1(OnVideoConfigChange, void(const VideoDecoderConfig&));
MOCK_METHOD1(OnVideoNaturalSizeChange, void(const gfx::Size& size));
MOCK_METHOD1(OnStatisticsUpdate,
void(const media::PipelineStatistics& stats));
MOCK_METHOD1(OnWaiting, void(WaitingReason));
MOCK_METHOD1(OnDurationChange, void(base::TimeDelta duration));
MOCK_METHOD1(OnRemotePlayStateChange, void(MediaStatus::State state));
private:
DISALLOW_COPY_AND_ASSIGN(MockRendererClient);
};
ACTION_P(QuitLoop, run_loop) {
base::PostTask(FROM_HERE, run_loop->QuitClosure());
}
service_manager::Manifest MakeMediaManifestForExecutable() {
service_manager::Manifest manifest = GetMediaManifest();
manifest.options.sandbox_type = "none";
manifest.options.execution_mode =
service_manager::Manifest::ExecutionMode::kStandaloneExecutable;
return manifest;
}
const char kTestServiceName[] = "media_service_unittests";
// Tests MediaService built into a standalone mojo service binary (see
// ServiceMain() in main.cc) where MediaService uses TestMojoMediaClient.
// TestMojoMediaClient supports CDM creation using DefaultCdmFactory (only
// supports Clear Key key system), and Renderer creation using
// DefaultRendererFactory that always create media::RendererImpl.
class MediaServiceTest : public testing::Test {
public:
MediaServiceTest()
: test_service_manager_(
{MakeMediaManifestForExecutable(),
service_manager::ManifestBuilder()
.WithServiceName(kTestServiceName)
.RequireCapability(mojom::kMediaServiceName, "media:media")
.Build()}),
test_service_(
test_service_manager_.RegisterTestInstance(kTestServiceName)),
cdm_proxy_client_receiver_(&cdm_proxy_client_),
renderer_client_receiver_(&renderer_client_),
video_stream_(DemuxerStream::VIDEO) {}
~MediaServiceTest() override = default;
service_manager::Connector* connector() { return test_service_.connector(); }
void SetUp() override {
mojo::PendingRemote<service_manager::mojom::InterfaceProvider>
host_interfaces;
auto provider = std::make_unique<MediaInterfaceProvider>(
host_interfaces.InitWithNewPipeAndPassReceiver());
connector()->Connect(mojom::kMediaServiceName,
media_service_.BindNewPipeAndPassReceiver());
media_service_.set_disconnect_handler(
base::BindRepeating(&MediaServiceTest::MediaServiceConnectionClosed,
base::Unretained(this)));
media_service_->CreateInterfaceFactory(
interface_factory_.BindNewPipeAndPassReceiver(),
std::move(host_interfaces));
}
MOCK_METHOD3(OnCdmInitialized,
void(mojom::CdmPromiseResultPtr result,
int cdm_id,
mojom::DecryptorPtr decryptor));
MOCK_METHOD0(OnCdmConnectionError, void());
// Returns the CDM ID associated with the CDM.
int InitializeCdm(const std::string& key_system, bool expected_result) {
base::RunLoop run_loop;
interface_factory_->CreateCdm(key_system,
cdm_.BindNewPipeAndPassReceiver());
cdm_.set_disconnect_handler(base::BindRepeating(
&MediaServiceTest::OnCdmConnectionError, base::Unretained(this)));
int cdm_id = CdmContext::kInvalidCdmId;
// The last parameter mojom::DecryptorPtr is move-only and not supported by
// DoAll. Hence use WithArg to only extract the "int cdm_id" out and then
// call DoAll.
EXPECT_CALL(*this, OnCdmInitialized(MatchesResult(expected_result), _, _))
.WillOnce(WithArg<1>(DoAll(SaveArg<0>(&cdm_id), QuitLoop(&run_loop))));
cdm_->Initialize(key_system, url::Origin::Create(GURL(kSecurityOrigin)),
CdmConfig(),
base::BindOnce(&MediaServiceTest::OnCdmInitialized,
base::Unretained(this)));
run_loop.Run();
return cdm_id;
}
MOCK_METHOD4(OnCdmProxyInitialized,
void(CdmProxy::Status status,
CdmProxy::Protocol protocol,
uint32_t crypto_session_id,
int cdm_id));
// Returns the CDM ID associated with the CdmProxy.
int InitializeCdmProxy(const base::Token& cdm_guid) {
base::RunLoop run_loop;
interface_factory_->CreateCdmProxy(cdm_guid,
cdm_proxy_.BindNewPipeAndPassReceiver());
mojo::PendingAssociatedRemote<mojom::CdmProxyClient> client_remote;
cdm_proxy_client_receiver_.Bind(
client_remote.InitWithNewEndpointAndPassReceiver());
int cdm_id = CdmContext::kInvalidCdmId;
EXPECT_CALL(*this, OnCdmProxyInitialized(CdmProxy::Status::kOk, _, _, _))
.WillOnce(DoAll(SaveArg<3>(&cdm_id), QuitLoop(&run_loop)));
cdm_proxy_->Initialize(
std::move(client_remote),
base::BindOnce(&MediaServiceTest::OnCdmProxyInitialized,
base::Unretained(this)));
run_loop.Run();
return cdm_id;
}
MOCK_METHOD2(OnDecrypted,
void(Decryptor::Status, scoped_refptr<DecoderBuffer>));
void CreateDecryptor(int cdm_id, bool expected_result) {
base::RunLoop run_loop;
mojo::PendingRemote<mojom::Decryptor> decryptor_remote;
interface_factory_->CreateDecryptor(
cdm_id, decryptor_remote.InitWithNewPipeAndPassReceiver());
MojoDecryptor mojo_decryptor(std::move(decryptor_remote));
// In the success case, there's no decryption key to decrypt the buffer so
// we would expect no-key.
auto expected_status =
expected_result ? Decryptor::kNoKey : Decryptor::kError;
EXPECT_CALL(*this, OnDecrypted(expected_status, _))
.WillOnce(QuitLoop(&run_loop));
mojo_decryptor.Decrypt(Decryptor::kVideo, CreateEncryptedBuffer(),
base::BindRepeating(&MediaServiceTest::OnDecrypted,
base::Unretained(this)));
run_loop.Run();
}
MOCK_METHOD1(OnRendererInitialized, void(bool));
void InitializeRenderer(const VideoDecoderConfig& video_config,
bool expected_result) {
base::RunLoop run_loop;
interface_factory_->CreateDefaultRenderer(
std::string(), renderer_.BindNewPipeAndPassReceiver());
video_stream_.set_video_decoder_config(video_config);
mojo::PendingRemote<mojom::DemuxerStream> video_stream_proxy;
mojo_video_stream_.reset(new MojoDemuxerStreamImpl(
&video_stream_, video_stream_proxy.InitWithNewPipeAndPassReceiver()));
mojo::PendingAssociatedRemote<mojom::RendererClient> client_remote;
renderer_client_receiver_.Bind(
client_remote.InitWithNewEndpointAndPassReceiver());
std::vector<mojo::PendingRemote<mojom::DemuxerStream>> streams;
streams.push_back(std::move(video_stream_proxy));
EXPECT_CALL(*this, OnRendererInitialized(expected_result))
.WillOnce(QuitLoop(&run_loop));
renderer_->Initialize(
std::move(client_remote), std::move(streams), nullptr,
base::BindOnce(&MediaServiceTest::OnRendererInitialized,
base::Unretained(this)));
run_loop.Run();
}
MOCK_METHOD0(MediaServiceConnectionClosed, void());
protected:
base::test::TaskEnvironment task_environment_;
service_manager::TestServiceManager test_service_manager_;
service_manager::TestService test_service_;
mojo::Remote<mojom::MediaService> media_service_;
mojo::Remote<mojom::InterfaceFactory> interface_factory_;
mojo::Remote<mojom::ContentDecryptionModule> cdm_;
mojo::Remote<mojom::CdmProxy> cdm_proxy_;
mojo::Remote<mojom::Renderer> renderer_;
NiceMock<MockCdmProxyClient> cdm_proxy_client_;
mojo::AssociatedReceiver<mojom::CdmProxyClient> cdm_proxy_client_receiver_;
NiceMock<MockRendererClient> renderer_client_;
mojo::AssociatedReceiver<mojom::RendererClient> renderer_client_receiver_;
StrictMock<MockDemuxerStream> video_stream_;
std::unique_ptr<MojoDemuxerStreamImpl> mojo_video_stream_;
private:
DISALLOW_COPY_AND_ASSIGN(MediaServiceTest);
};
} // namespace
// Note: base::RunLoop::RunUntilIdle() does not work well in these tests because
// even when the loop is idle, we may still have pending events in the pipe.
// - If you have an InterfacePtr hosted by the service in the service process,
// you can use InterfacePtr::FlushForTesting(). Note that this doesn't drain
// the task runner in the test process and doesn't cover all negative cases.
// - If you expect a callback on an InterfacePtr call or connection error, use
// base::RunLoop::Run() and QuitLoop().
// TODO(crbug.com/829233): Enable these tests on Android.
#if BUILDFLAG(ENABLE_MOJO_CDM) && !defined(OS_ANDROID)
TEST_F(MediaServiceTest, InitializeCdm_Success) {
InitializeCdm(kClearKeyKeySystem, true);
}
TEST_F(MediaServiceTest, InitializeCdm_InvalidKeySystem) {
InitializeCdm(kInvalidKeySystem, false);
}
TEST_F(MediaServiceTest, Decryptor_WithCdm) {
int cdm_id = InitializeCdm(kClearKeyKeySystem, true);
CreateDecryptor(cdm_id, true);
}
#endif // BUILDFLAG(ENABLE_MOJO_CDM) && !defined(OS_ANDROID)
#if BUILDFLAG(ENABLE_MOJO_RENDERER)
TEST_F(MediaServiceTest, InitializeRenderer) {
InitializeRenderer(TestVideoConfig::Normal(), true);
}
#endif // BUILDFLAG(ENABLE_MOJO_RENDERER)
#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
TEST_F(MediaServiceTest, CdmProxy) {
InitializeCdmProxy(kClearKeyCdmGuid);
}
TEST_F(MediaServiceTest, Decryptor_WithCdmProxy) {
int cdm_id = InitializeCdmProxy(kClearKeyCdmGuid);
CreateDecryptor(cdm_id, true);
}
TEST_F(MediaServiceTest, Decryptor_WrongCdmId) {
int cdm_id = InitializeCdmProxy(kClearKeyCdmGuid);
CreateDecryptor(cdm_id + 1, false);
}
TEST_F(MediaServiceTest, DeferredDestruction_CdmProxy) {
InitializeCdmProxy(kClearKeyCdmGuid);
// Disconnecting InterfaceFactory should not terminate the MediaService since
// there is still a CdmProxy hosted.
interface_factory_.reset();
cdm_proxy_.FlushForTesting();
// Disconnecting CdmProxy will now terminate the MediaService.
base::RunLoop run_loop;
EXPECT_CALL(*this, MediaServiceConnectionClosed())
.WillOnce(QuitLoop(&run_loop));
cdm_proxy_.reset();
run_loop.Run();
}
#endif // BUILDFLAG(ENABLE_LIBRARY_CDMS)
TEST_F(MediaServiceTest, Decryptor_WithoutCdmOrCdmProxy) {
// Creating decryptor without creating CDM or CdmProxy.
CreateDecryptor(1, false);
}
TEST_F(MediaServiceTest, Lifetime_DestroyMediaService) {
// Disconnecting |media_service_| doesn't terminate MediaService
// since |interface_factory_| is still alive. This is ensured here since
// MediaServiceConnectionClosed() is not called.
EXPECT_CALL(*this, MediaServiceConnectionClosed()).Times(0);
media_service_.reset();
interface_factory_.FlushForTesting();
}
TEST_F(MediaServiceTest, Lifetime_DestroyInterfaceFactory) {
// Disconnecting InterfaceFactory will now terminate the MediaService since
// there's no media components hosted.
base::RunLoop run_loop;
EXPECT_CALL(*this, MediaServiceConnectionClosed())
.WillOnce(QuitLoop(&run_loop));
interface_factory_.reset();
run_loop.Run();
}
#if (BUILDFLAG(ENABLE_MOJO_CDM) && !defined(OS_ANDROID)) || \
BUILDFLAG(ENABLE_MOJO_RENDERER)
// MediaService stays alive as long as there are InterfaceFactory impls, which
// are then deferred destroyed until no media components (e.g. CDM or Renderer)
// are hosted.
TEST_F(MediaServiceTest, Lifetime) {
#if BUILDFLAG(ENABLE_MOJO_CDM) && !defined(OS_ANDROID)
InitializeCdm(kClearKeyKeySystem, true);
#endif
#if BUILDFLAG(ENABLE_MOJO_RENDERER)
InitializeRenderer(TestVideoConfig::Normal(), true);
#endif
// Disconnecting CDM and Renderer services doesn't terminate MediaService
// since |interface_factory_| is still alive.
cdm_.reset();
renderer_.reset();
interface_factory_.FlushForTesting();
// Disconnecting InterfaceFactory will now terminate the MediaService.
base::RunLoop run_loop;
EXPECT_CALL(*this, MediaServiceConnectionClosed())
.WillOnce(QuitLoop(&run_loop));
interface_factory_.reset();
run_loop.Run();
}
TEST_F(MediaServiceTest, DeferredDestruction) {
#if BUILDFLAG(ENABLE_MOJO_CDM) && !defined(OS_ANDROID)
InitializeCdm(kClearKeyKeySystem, true);
#endif
#if BUILDFLAG(ENABLE_MOJO_RENDERER)
InitializeRenderer(TestVideoConfig::Normal(), true);
#endif
ASSERT_TRUE(cdm_ || renderer_);
// Disconnecting InterfaceFactory should not terminate the MediaService since
// there are still media components (CDM or Renderer) hosted.
interface_factory_.reset();
if (cdm_)
cdm_.FlushForTesting();
else if (renderer_)
renderer_.FlushForTesting();
else
NOTREACHED();
// Disconnecting CDM and Renderer will now terminate the MediaService.
base::RunLoop run_loop;
EXPECT_CALL(*this, MediaServiceConnectionClosed())
.WillOnce(QuitLoop(&run_loop));
cdm_.reset();
renderer_.reset();
run_loop.Run();
}
#endif // (BUILDFLAG(ENABLE_MOJO_CDM) && !defined(OS_ANDROID)) ||
// BUILDFLAG(ENABLE_MOJO_RENDERER)
} // namespace media