Commit 204e8b07 authored by liberato@chromium.org's avatar liberato@chromium.org Committed by Commit Bot

Enable Cdm use in MediaCodecVideoDecoder.

This CL causes MCVD to use the Cdm that's provided for secure
content.  It requests a secure codec when needed, and a secure
output surface.

Bug: 760821
Cq-Include-Trybots: master.tryserver.chromium.android:android_optional_gpu_tests_rel;master.tryserver.chromium.linux:linux_optional_gpu_tests_rel;master.tryserver.chromium.mac:mac_optional_gpu_tests_rel;master.tryserver.chromium.win:win_optional_gpu_tests_rel
Change-Id: I11ac076d729913b0c8b76d9f5133af8ea3f11c09
Reviewed-on: https://chromium-review.googlesource.com/779620
Commit-Queue: Frank Liberato <liberato@chromium.org>
Reviewed-by: default avatarChris Watkins <watk@chromium.org>
Cr-Commit-Position: refs/heads/master@{#518416}
parent 30635ab3
...@@ -35,8 +35,9 @@ if (is_android) { ...@@ -35,8 +35,9 @@ if (is_android) {
"media_codec_util.h", "media_codec_util.h",
"media_drm_bridge.cc", "media_drm_bridge.cc",
"media_drm_bridge.h", "media_drm_bridge.h",
"media_drm_bridge_cdm_context.cc",
"media_drm_bridge_cdm_context.h", "media_drm_bridge_cdm_context.h",
"media_drm_bridge_cdm_context_impl.cc",
"media_drm_bridge_cdm_context_impl.h",
"media_drm_bridge_client.cc", "media_drm_bridge_client.cc",
"media_drm_bridge_client.h", "media_drm_bridge_client.h",
"media_drm_bridge_delegate.cc", "media_drm_bridge_delegate.cc",
...@@ -112,6 +113,8 @@ if (is_android) { ...@@ -112,6 +113,8 @@ if (is_android) {
"mock_android_overlay.h", "mock_android_overlay.h",
"mock_media_codec_bridge.cc", "mock_media_codec_bridge.cc",
"mock_media_codec_bridge.h", "mock_media_codec_bridge.h",
"mock_media_drm_bridge_cdm_context.cc",
"mock_media_drm_bridge_cdm_context.h",
"test_destruction_observable.cc", "test_destruction_observable.cc",
"test_destruction_observable.h", "test_destruction_observable.h",
] ]
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#include "base/sequenced_task_runner_helpers.h" #include "base/sequenced_task_runner_helpers.h"
#include "media/base/android/android_util.h" #include "media/base/android/android_util.h"
#include "media/base/android/media_drm_bridge_cdm_context.h" #include "media/base/android/media_drm_bridge_cdm_context.h"
#include "media/base/android/media_drm_bridge_cdm_context_impl.h"
#include "media/base/android/media_drm_storage.h" #include "media/base/android/media_drm_storage.h"
#include "media/base/android/media_drm_storage_bridge.h" #include "media/base/android/media_drm_storage_bridge.h"
#include "media/base/cdm_promise.h" #include "media/base/cdm_promise.h"
...@@ -334,7 +335,7 @@ class MEDIA_EXPORT MediaDrmBridge : public ContentDecryptionModule, ...@@ -334,7 +335,7 @@ class MEDIA_EXPORT MediaDrmBridge : public ContentDecryptionModule,
// Default task runner. // Default task runner.
scoped_refptr<base::SingleThreadTaskRunner> task_runner_; scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
MediaDrmBridgeCdmContext media_drm_bridge_cdm_context_; MediaDrmBridgeCdmContextImpl media_drm_bridge_cdm_context_;
// NOTE: Weak pointers must be invalidated before all other member variables. // NOTE: Weak pointers must be invalidated before all other member variables.
base::WeakPtrFactory<MediaDrmBridge> weak_factory_; base::WeakPtrFactory<MediaDrmBridge> weak_factory_;
......
...@@ -5,11 +5,8 @@ ...@@ -5,11 +5,8 @@
#ifndef MEDIA_BASE_ANDROID_MEDIA_DRM_BRIDGE_CDM_CONTEXT_H_ #ifndef MEDIA_BASE_ANDROID_MEDIA_DRM_BRIDGE_CDM_CONTEXT_H_
#define MEDIA_BASE_ANDROID_MEDIA_DRM_BRIDGE_CDM_CONTEXT_H_ #define MEDIA_BASE_ANDROID_MEDIA_DRM_BRIDGE_CDM_CONTEXT_H_
#include <jni.h>
#include <memory> #include <memory>
#include "base/android/scoped_java_ref.h"
#include "base/callback.h" #include "base/callback.h"
#include "base/macros.h" #include "base/macros.h"
#include "media/base/android/android_util.h" #include "media/base/android/android_util.h"
...@@ -19,16 +16,14 @@ ...@@ -19,16 +16,14 @@
namespace media { namespace media {
class MediaDrmBridge;
// The CdmContext implementation for MediaDrmBridge. MediaDrmBridge supports // The CdmContext implementation for MediaDrmBridge. MediaDrmBridge supports
// neither Decryptor nor CDM ID, but uses MediaCrypto to connect to MediaCodec. // neither Decryptor nor CDM ID, but uses MediaCrypto to connect to MediaCodec.
// MediaCodec-based decoders should cast the given CdmContext to this class to // MediaCodec-based decoders should cast the given CdmContext to this class to
// access APIs defined in this class. // access APIs defined in this class.
// //
// Methods can be called on any thread. The registered callbacks will be fired // Methods can be called on any thread. The registered callbacks will be fired
// on the thread |media_drm_bridge_| is running on. The caller should make sure // on any thread. The caller should make sure that the callbacks are posted to
// that the callbacks are posted to the correct thread. // the correct thread.
// //
// TODO(xhwang): Remove PlayerTracker interface. // TODO(xhwang): Remove PlayerTracker interface.
class MEDIA_EXPORT MediaDrmBridgeCdmContext : public CdmContext, class MEDIA_EXPORT MediaDrmBridgeCdmContext : public CdmContext,
...@@ -46,30 +41,11 @@ class MEDIA_EXPORT MediaDrmBridgeCdmContext : public CdmContext, ...@@ -46,30 +41,11 @@ class MEDIA_EXPORT MediaDrmBridgeCdmContext : public CdmContext,
base::Callback<void(JavaObjectPtr media_crypto, base::Callback<void(JavaObjectPtr media_crypto,
bool requires_secure_video_codec)>; bool requires_secure_video_codec)>;
// The |media_drm_bridge| owns |this| and is guaranteed to outlive |this|. MediaDrmBridgeCdmContext() {}
explicit MediaDrmBridgeCdmContext(MediaDrmBridge* media_drm_bridge); ~MediaDrmBridgeCdmContext() override {}
~MediaDrmBridgeCdmContext() final;
// CdmContext implementation.
Decryptor* GetDecryptor() final;
int GetCdmId() const final;
// PlayerTracker implementation.
// Methods can be called on any thread. The registered callbacks will be fired
// on |task_runner_|. The caller should make sure that the callbacks are
// posted to the correct thread.
//
// Note: RegisterPlayer() must be called before SetMediaCryptoReadyCB() to
// avoid missing any new key notifications.
int RegisterPlayer(const base::Closure& new_key_cb,
const base::Closure& cdm_unset_cb) final;
void UnregisterPlayer(int registration_id) final;
void SetMediaCryptoReadyCB(const MediaCryptoReadyCB& media_crypto_ready_cb);
private: virtual void SetMediaCryptoReadyCB(
MediaDrmBridge* const media_drm_bridge_; const MediaCryptoReadyCB& media_crypto_ready_cb) = 0;
DISALLOW_COPY_AND_ASSIGN(MediaDrmBridgeCdmContext); DISALLOW_COPY_AND_ASSIGN(MediaDrmBridgeCdmContext);
}; };
......
...@@ -2,39 +2,39 @@ ...@@ -2,39 +2,39 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include "media/base/android/media_drm_bridge_cdm_context.h" #include "media/base/android/media_drm_bridge_cdm_context_impl.h"
#include "media/base/android/media_drm_bridge.h" #include "media/base/android/media_drm_bridge.h"
namespace media { namespace media {
MediaDrmBridgeCdmContext::MediaDrmBridgeCdmContext( MediaDrmBridgeCdmContextImpl::MediaDrmBridgeCdmContextImpl(
MediaDrmBridge* media_drm_bridge) MediaDrmBridge* media_drm_bridge)
: media_drm_bridge_(media_drm_bridge) { : media_drm_bridge_(media_drm_bridge) {
DCHECK(media_drm_bridge_); DCHECK(media_drm_bridge_);
} }
MediaDrmBridgeCdmContext::~MediaDrmBridgeCdmContext() {} MediaDrmBridgeCdmContextImpl::~MediaDrmBridgeCdmContextImpl() {}
Decryptor* MediaDrmBridgeCdmContext::GetDecryptor() { Decryptor* MediaDrmBridgeCdmContextImpl::GetDecryptor() {
return nullptr; return nullptr;
} }
int MediaDrmBridgeCdmContext::GetCdmId() const { int MediaDrmBridgeCdmContextImpl::GetCdmId() const {
return kInvalidCdmId; return kInvalidCdmId;
} }
int MediaDrmBridgeCdmContext::RegisterPlayer( int MediaDrmBridgeCdmContextImpl::RegisterPlayer(
const base::Closure& new_key_cb, const base::Closure& new_key_cb,
const base::Closure& cdm_unset_cb) { const base::Closure& cdm_unset_cb) {
return media_drm_bridge_->RegisterPlayer(new_key_cb, cdm_unset_cb); return media_drm_bridge_->RegisterPlayer(new_key_cb, cdm_unset_cb);
} }
void MediaDrmBridgeCdmContext::UnregisterPlayer(int registration_id) { void MediaDrmBridgeCdmContextImpl::UnregisterPlayer(int registration_id) {
media_drm_bridge_->UnregisterPlayer(registration_id); media_drm_bridge_->UnregisterPlayer(registration_id);
} }
void MediaDrmBridgeCdmContext::SetMediaCryptoReadyCB( void MediaDrmBridgeCdmContextImpl::SetMediaCryptoReadyCB(
const MediaCryptoReadyCB& media_crypto_ready_cb) { const MediaCryptoReadyCB& media_crypto_ready_cb) {
media_drm_bridge_->SetMediaCryptoReadyCB(media_crypto_ready_cb); media_drm_bridge_->SetMediaCryptoReadyCB(media_crypto_ready_cb);
} }
......
// 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.
#ifndef MEDIA_BASE_ANDROID_MEDIA_DRM_BRIDGE_CDM_CONTEXT_IMPL_H_
#define MEDIA_BASE_ANDROID_MEDIA_DRM_BRIDGE_CDM_CONTEXT_IMPL_H_
#include <jni.h>
#include <memory>
#include "base/android/scoped_java_ref.h"
#include "base/macros.h"
#include "media/base/android/media_drm_bridge_cdm_context.h"
#include "media/base/media_export.h"
namespace media {
class MediaDrmBridge;
// Implementation of MediaDrmBridgeCdmContext.
//
// The registered callbacks will be fired on the thread |media_drm_bridge_| is
// running on.
class MEDIA_EXPORT MediaDrmBridgeCdmContextImpl
: public MediaDrmBridgeCdmContext {
public:
// The |media_drm_bridge| owns |this| and is guaranteed to outlive |this|.
explicit MediaDrmBridgeCdmContextImpl(MediaDrmBridge* media_drm_bridge);
~MediaDrmBridgeCdmContextImpl() final;
// CdmContext implementation.
Decryptor* GetDecryptor() final;
int GetCdmId() const final;
// PlayerTracker implementation.
// Methods can be called on any thread. The registered callbacks will be fired
// on |task_runner_|. The caller should make sure that the callbacks are
// posted to the correct thread.
//
// Note: RegisterPlayer() must be called before SetMediaCryptoReadyCB() to
// avoid missing any new key notifications.
int RegisterPlayer(const base::Closure& new_key_cb,
const base::Closure& cdm_unset_cb) final;
void UnregisterPlayer(int registration_id) final;
// MediaDrmBridgeCdmContext implementation.
void SetMediaCryptoReadyCB(
const MediaCryptoReadyCB& media_crypto_ready_cb) final;
private:
MediaDrmBridge* const media_drm_bridge_;
DISALLOW_COPY_AND_ASSIGN(MediaDrmBridgeCdmContextImpl);
};
} // namespace media
#endif // MEDIA_BASE_ANDROID_MEDIA_DRM_BRIDGE_CDM_CONTEXT_IMPL_H_
// Copyright 2017 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 "media/base/android/mock_media_drm_bridge_cdm_context.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/threading/thread_task_runner_handle.h"
using ::testing::DoAll;
using ::testing::Invoke;
using ::testing::Not;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::_;
namespace media {
MockMediaDrmBridgeCdmContext::MockMediaDrmBridgeCdmContext(int cdm_id) {
// Provide some sane defaults.
ON_CALL(*this, GetCdmId()).WillByDefault(Return(cdm_id));
ON_CALL(*this, RegisterPlayer(_, _))
.WillByDefault(DoAll(SaveArg<0>(&new_key_cb), SaveArg<1>(&cdm_unset_cb),
Return(kRegistrationId)));
ON_CALL(*this, SetMediaCryptoReadyCB(_))
.WillByDefault(SaveArg<0>(&media_crypto_ready_cb));
// Don't set any expectation on the number of correct calls to
// UnregisterPlayer, but expect no calls with the wrong registration id.
EXPECT_CALL(*this, UnregisterPlayer(Not(kRegistrationId))).Times(0);
}
MockMediaDrmBridgeCdmContext::~MockMediaDrmBridgeCdmContext() {}
} // namespace media
// Copyright 2017 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.
#ifndef MEDIA_BASE_ANDROID_MOCK_MEDIA_DRM_BRIDGE_CDM_CONTEXT_H_
#define MEDIA_BASE_ANDROID_MOCK_MEDIA_DRM_BRIDGE_CDM_CONTEXT_H_
#include <memory>
#include "base/macros.h"
#include "media/base/android/media_drm_bridge_cdm_context.h"
#include "media/base/media_export.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace media {
class MEDIA_EXPORT MockMediaDrmBridgeCdmContext
: public testing::NiceMock<MediaDrmBridgeCdmContext> {
public:
explicit MockMediaDrmBridgeCdmContext(int cdm_id);
~MockMediaDrmBridgeCdmContext() override;
MOCK_METHOD0(GetDecryptor, Decryptor*());
MOCK_CONST_METHOD0(GetCdmId, int());
MOCK_METHOD2(RegisterPlayer,
int(const base::Closure& new_key_cb,
const base::Closure& cdm_unset_cb));
MOCK_METHOD1(UnregisterPlayer, void(int registration_id));
// MediaDrmBridgeCdmContext implementation.
MOCK_METHOD1(SetMediaCryptoReadyCB,
void(const MediaCryptoReadyCB& media_crypto_ready_cb));
static constexpr int kRegistrationId = 1000;
base::Closure new_key_cb;
base::Closure cdm_unset_cb;
MediaCryptoReadyCB media_crypto_ready_cb;
private:
DISALLOW_COPY_AND_ASSIGN(MockMediaDrmBridgeCdmContext);
};
} // namespace media
#endif // MEDIA_BASE_ANDROID_MOCK_MEDIA_DRM_BRIDGE_CDM_CONTEXT_H_
...@@ -19,7 +19,8 @@ FakeCodecAllocator::FakeCodecAllocator( ...@@ -19,7 +19,8 @@ FakeCodecAllocator::FakeCodecAllocator(
scoped_refptr<base::SequencedTaskRunner> task_runner) scoped_refptr<base::SequencedTaskRunner> task_runner)
: testing::NiceMock<AVDACodecAllocator>( : testing::NiceMock<AVDACodecAllocator>(
base::BindRepeating(&MockMediaCodecBridge::CreateVideoDecoder), base::BindRepeating(&MockMediaCodecBridge::CreateVideoDecoder),
task_runner) {} task_runner),
most_recent_config(new CodecConfig()) {}
FakeCodecAllocator::~FakeCodecAllocator() = default; FakeCodecAllocator::~FakeCodecAllocator() = default;
...@@ -29,8 +30,7 @@ void FakeCodecAllocator::StopThread(AVDACodecAllocatorClient* client) {} ...@@ -29,8 +30,7 @@ void FakeCodecAllocator::StopThread(AVDACodecAllocatorClient* client) {}
std::unique_ptr<MediaCodecBridge> FakeCodecAllocator::CreateMediaCodecSync( std::unique_ptr<MediaCodecBridge> FakeCodecAllocator::CreateMediaCodecSync(
scoped_refptr<CodecConfig> config) { scoped_refptr<CodecConfig> config) {
most_recent_overlay = config->surface_bundle->overlay.get(); CopyCodecConfig(config);
most_recent_surface_texture = config->surface_bundle->surface_texture.get();
MockCreateMediaCodecSync(most_recent_overlay, most_recent_surface_texture); MockCreateMediaCodecSync(most_recent_overlay, most_recent_surface_texture);
std::unique_ptr<MockMediaCodecBridge> codec; std::unique_ptr<MockMediaCodecBridge> codec;
...@@ -53,8 +53,7 @@ void FakeCodecAllocator::CreateMediaCodecAsync( ...@@ -53,8 +53,7 @@ void FakeCodecAllocator::CreateMediaCodecAsync(
// Clear |most_recent_codec| until somebody calls Provide*CodecAsync(). // Clear |most_recent_codec| until somebody calls Provide*CodecAsync().
most_recent_codec = nullptr; most_recent_codec = nullptr;
most_recent_codec_destruction_observer = nullptr; most_recent_codec_destruction_observer = nullptr;
most_recent_overlay = config->surface_bundle->overlay.get(); CopyCodecConfig(config);
most_recent_surface_texture = config->surface_bundle->surface_texture.get();
pending_surface_bundle_ = config->surface_bundle; pending_surface_bundle_ = config->surface_bundle;
client_ = client; client_ = client;
codec_creation_pending_ = true; codec_creation_pending_ = true;
...@@ -96,4 +95,22 @@ void FakeCodecAllocator::ProvideNullCodecAsync() { ...@@ -96,4 +95,22 @@ void FakeCodecAllocator::ProvideNullCodecAsync() {
client_->OnCodecConfigured(nullptr, std::move(pending_surface_bundle_)); client_->OnCodecConfigured(nullptr, std::move(pending_surface_bundle_));
} }
void FakeCodecAllocator::CopyCodecConfig(scoped_refptr<CodecConfig> config) {
// CodecConfig isn't copyable, since it has unique_ptrs and such.
most_recent_overlay = config->surface_bundle->overlay.get();
most_recent_surface_texture = config->surface_bundle->surface_texture.get();
most_recent_config->media_crypto =
config->media_crypto
? base::MakeUnique<base::android::ScopedJavaGlobalRef<jobject>>(
*config->media_crypto)
: nullptr;
most_recent_config->requires_secure_codec = config->requires_secure_codec;
most_recent_config->initial_expected_coded_size =
config->initial_expected_coded_size;
most_recent_config->software_codec_forbidden =
config->software_codec_forbidden;
most_recent_config->csd0 = config->csd0;
most_recent_config->csd1 = config->csd1;
}
} // namespace media } // namespace media
...@@ -72,7 +72,13 @@ class FakeCodecAllocator : public testing::NiceMock<AVDACodecAllocator> { ...@@ -72,7 +72,13 @@ class FakeCodecAllocator : public testing::NiceMock<AVDACodecAllocator> {
// Whether CreateMediaCodecSync() is allowed to succeed. // Whether CreateMediaCodecSync() is allowed to succeed.
bool allow_sync_creation = true; bool allow_sync_creation = true;
// Copy of most of the fields in the most recent config, except for the ptrs.
scoped_refptr<CodecConfig> most_recent_config;
private: private:
// Copy |config| to |most_recent_config| etc.
void CopyCodecConfig(scoped_refptr<CodecConfig> config);
// Whether CreateMediaCodecAsync() has been called but a codec hasn't been // Whether CreateMediaCodecAsync() has been called but a codec hasn't been
// provided yet. // provided yet.
bool codec_creation_pending_ = false; bool codec_creation_pending_ = false;
......
...@@ -134,6 +134,17 @@ MediaCodecVideoDecoder::~MediaCodecVideoDecoder() { ...@@ -134,6 +134,17 @@ MediaCodecVideoDecoder::~MediaCodecVideoDecoder() {
DVLOG(2) << __func__; DVLOG(2) << __func__;
ReleaseCodec(); ReleaseCodec();
codec_allocator_->StopThread(this); codec_allocator_->StopThread(this);
if (!media_drm_bridge_cdm_context_)
return;
DCHECK(cdm_registration_id_);
// Cancel previously registered callback (if any).
media_drm_bridge_cdm_context_->SetMediaCryptoReadyCB(
MediaDrmBridgeCdmContext::MediaCryptoReadyCB());
media_drm_bridge_cdm_context_->UnregisterPlayer(cdm_registration_id_);
} }
void MediaCodecVideoDecoder::Destroy() { void MediaCodecVideoDecoder::Destroy() {
...@@ -175,9 +186,73 @@ void MediaCodecVideoDecoder::Initialize(const VideoDecoderConfig& config, ...@@ -175,9 +186,73 @@ void MediaCodecVideoDecoder::Initialize(const VideoDecoderConfig& config,
ExtractSpsAndPps(config.extra_data(), &csd0_, &csd1_); ExtractSpsAndPps(config.extra_data(), &csd0_, &csd1_);
#endif #endif
// For encrypted content, defer signalling success until the Cdm is ready.
if (config.is_encrypted()) {
SetCdm(cdm_context, init_cb);
return;
}
// Do the rest of the initialization lazily on the first decode. // Do the rest of the initialization lazily on the first decode.
// TODO(watk): Add CDM Support. init_cb.Run(true);
DCHECK(!cdm_context); }
void MediaCodecVideoDecoder::SetCdm(CdmContext* cdm_context,
const InitCB& init_cb) {
if (!cdm_context) {
LOG(ERROR) << "No CDM provided";
EnterTerminalState(State::kError);
init_cb.Run(false);
return;
}
// On Android platform the CdmContext must be a MediaDrmBridgeCdmContext.
media_drm_bridge_cdm_context_ =
static_cast<media::MediaDrmBridgeCdmContext*>(cdm_context);
// Register CDM callbacks. The callbacks registered will be posted back to
// this thread via BindToCurrentLoop.
// Since |this| holds a reference to the |cdm_|, by the time the CDM is
// destructed, UnregisterPlayer() must have been called and |this| has been
// destructed as well. So the |cdm_unset_cb| will never have a chance to be
// called.
// TODO(xhwang): Remove |cdm_unset_cb| after it's not used on all platforms.
cdm_registration_id_ = media_drm_bridge_cdm_context_->RegisterPlayer(
media::BindToCurrentLoop(base::Bind(&MediaCodecVideoDecoder::OnKeyAdded,
weak_factory_.GetWeakPtr())),
base::Bind(&base::DoNothing));
media_drm_bridge_cdm_context_->SetMediaCryptoReadyCB(media::BindToCurrentLoop(
base::Bind(&MediaCodecVideoDecoder::OnMediaCryptoReady,
weak_factory_.GetWeakPtr(), init_cb)));
}
void MediaCodecVideoDecoder::OnMediaCryptoReady(
const InitCB& init_cb,
JavaObjectPtr media_crypto,
bool requires_secure_video_codec) {
DVLOG(1) << __func__;
DCHECK(state_ == State::kInitializing);
if (!media_crypto || media_crypto->is_null()) {
LOG(ERROR) << "MediaCrypto is not available";
EnterTerminalState(State::kError);
init_cb.Run(false);
return;
}
media_crypto_ = *media_crypto;
requires_secure_codec_ = requires_secure_video_codec;
// Request a secure surface in all cases. For L3, it's okay if we fall back
// to SurfaceTexture rather than fail composition. For L1, it's required.
surface_chooser_helper_.SetSecureSurfaceMode(
requires_secure_video_codec
? SurfaceChooserHelper::SecureSurfaceMode::kRequired
: SurfaceChooserHelper::SecureSurfaceMode::kRequested);
// Signal success, and create the codec lazily on the first decode.
init_cb.Run(true); init_cb.Run(true);
} }
...@@ -317,9 +392,11 @@ void MediaCodecVideoDecoder::CreateCodec() { ...@@ -317,9 +392,11 @@ void MediaCodecVideoDecoder::CreateCodec() {
scoped_refptr<CodecConfig> config = new CodecConfig(); scoped_refptr<CodecConfig> config = new CodecConfig();
config->codec = decoder_config_.codec(); config->codec = decoder_config_.codec();
// TODO(watk): Set |requires_secure_codec| correctly using config->requires_secure_codec = requires_secure_codec_;
// MediaDrmBridgeCdmContext::MediaCryptoReadyCB. // TODO(liberato): per android_util.h, remove JavaObjectPtr.
config->requires_secure_codec = decoder_config_.is_encrypted(); config->media_crypto =
base::MakeUnique<base::android::ScopedJavaGlobalRef<jobject>>(
media_crypto_);
config->initial_expected_coded_size = decoder_config_.coded_size(); config->initial_expected_coded_size = decoder_config_.coded_size();
config->surface_bundle = target_surface_bundle_; config->surface_bundle = target_surface_bundle_;
// Note that this might be the same surface bundle that we've been using, if // Note that this might be the same surface bundle that we've been using, if
......
...@@ -83,6 +83,17 @@ class MEDIA_GPU_EXPORT MediaCodecVideoDecoder ...@@ -83,6 +83,17 @@ class MEDIA_GPU_EXPORT MediaCodecVideoDecoder
// Protected for testing. // Protected for testing.
~MediaCodecVideoDecoder() override; ~MediaCodecVideoDecoder() override;
// Set up |cdm_context| as part of initialization. Guarantees that |init_cb|
// will be called depending on the outcome, though not necessarily before this
// function returns.
void SetCdm(CdmContext* cdm_context, const InitCB& init_cb);
// Called when the Cdm provides |media_crypto|. Will signal |init_cb| based
// on the result, and set the codec config properly.
void OnMediaCryptoReady(const InitCB& init_cb,
JavaObjectPtr media_crypto,
bool requires_secure_video_codec);
private: private:
// The test has access for PumpCodec(). // The test has access for PumpCodec().
friend class MediaCodecVideoDecoderTest; friend class MediaCodecVideoDecoderTest;
...@@ -261,6 +272,22 @@ class MEDIA_GPU_EXPORT MediaCodecVideoDecoder ...@@ -261,6 +272,22 @@ class MEDIA_GPU_EXPORT MediaCodecVideoDecoder
SurfaceChooserHelper::FrameInformation cached_frame_information_ = SurfaceChooserHelper::FrameInformation cached_frame_information_ =
SurfaceChooserHelper::FrameInformation::SURFACETEXTURE_INSECURE; SurfaceChooserHelper::FrameInformation::SURFACETEXTURE_INSECURE;
// CDM related stuff.
// CDM context that knowns about MediaCrypto. Owned by CDM which is external
// to this decoder.
MediaDrmBridgeCdmContext* media_drm_bridge_cdm_context_ = nullptr;
// MediaDrmBridge requires registration/unregistration of the player, this
// registration id is used for this.
int cdm_registration_id_ = 0;
// Do we need a hw-secure codec?
bool requires_secure_codec_ = false;
// Optional crypto object from the Cdm.
base::android::ScopedJavaGlobalRef<jobject> media_crypto_;
// If we're running in a service context this ref lets us keep the service // If we're running in a service context this ref lets us keep the service
// thread alive until destruction. // thread alive until destruction.
std::unique_ptr<service_manager::ServiceContextRef> context_ref_; std::unique_ptr<service_manager::ServiceContextRef> context_ref_;
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
#include "media/gpu/android/media_codec_video_decoder.h" #include "media/gpu/android/media_codec_video_decoder.h"
#include "base/android/jni_android.h"
#include "base/bind.h" #include "base/bind.h"
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/test/mock_callback.h" #include "base/test/mock_callback.h"
...@@ -11,6 +12,7 @@ ...@@ -11,6 +12,7 @@
#include "gpu/command_buffer/service/gpu_preferences.h" #include "gpu/command_buffer/service/gpu_preferences.h"
#include "media/base/android/media_codec_util.h" #include "media/base/android/media_codec_util.h"
#include "media/base/android/mock_android_overlay.h" #include "media/base/android/mock_android_overlay.h"
#include "media/base/android/mock_media_drm_bridge_cdm_context.h"
#include "media/base/decoder_buffer.h" #include "media/base/decoder_buffer.h"
#include "media/base/gmock_callback_support.h" #include "media/base/gmock_callback_support.h"
#include "media/base/test_helpers.h" #include "media/base/test_helpers.h"
...@@ -159,6 +161,17 @@ class MediaCodecVideoDecoderTest : public testing::Test { ...@@ -159,6 +161,17 @@ class MediaCodecVideoDecoderTest : public testing::Test {
destruction_observer_->ExpectDestruction(); destruction_observer_->ExpectDestruction();
} }
void CreateCdm(bool require_secure_video_decoder) {
cdm_ = base::MakeUnique<MockMediaDrmBridgeCdmContext>(cdm_id_);
require_secure_video_decoder_ = require_secure_video_decoder;
// We need to send an object as the media crypto, but MCVD shouldn't
// use it for anything. Just send in some random java object, so that
// it's not null.
media_crypto_ = base::android::ScopedJavaGlobalRef<jobject>(
gl::SurfaceTexture::Create(0)->j_surface_texture());
}
// Just call Initialize(). MCVD will be waiting for a call to Decode() before // Just call Initialize(). MCVD will be waiting for a call to Decode() before
// continuining initialization. // continuining initialization.
bool Initialize( bool Initialize(
...@@ -167,9 +180,21 @@ class MediaCodecVideoDecoderTest : public testing::Test { ...@@ -167,9 +180,21 @@ class MediaCodecVideoDecoderTest : public testing::Test {
CreateMcvd(); CreateMcvd();
bool result = false; bool result = false;
auto init_cb = [](bool* result_out, bool result) { *result_out = result; }; auto init_cb = [](bool* result_out, bool result) { *result_out = result; };
mcvd_->Initialize(config, false, nullptr, base::Bind(init_cb, &result), mcvd_->Initialize(config, false, cdm_.get(), base::Bind(init_cb, &result),
base::Bind(&OutputCb)); base::Bind(&OutputCb));
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
if (config.is_encrypted() && cdm_) {
// If the output is encrypted, then we expect that MCVD will be waiting
// for the media crypto object.
// TODO(liberato): why does CreateJavaObjectPtr() not link?
cdm_->media_crypto_ready_cb.Run(
base::MakeUnique<base::android::ScopedJavaGlobalRef<jobject>>(
media_crypto_),
require_secure_video_decoder_);
base::RunLoop().RunUntilIdle();
}
return result; return result;
} }
...@@ -232,10 +257,17 @@ class MediaCodecVideoDecoderTest : public testing::Test { ...@@ -232,10 +257,17 @@ class MediaCodecVideoDecoderTest : public testing::Test {
bool restart_for_transitions_; bool restart_for_transitions_;
gpu::GpuPreferences gpu_preferences_; gpu::GpuPreferences gpu_preferences_;
const int cdm_id_ = 123;
// This is not an actual media crypto object.
base::android::ScopedJavaGlobalRef<jobject> media_crypto_;
bool require_secure_video_decoder_ = false;
// |mcvd_raw_| lets us call PumpCodec() even after |mcvd_| is dropped, for // |mcvd_raw_| lets us call PumpCodec() even after |mcvd_| is dropped, for
// testing the teardown path. // testing the teardown path.
MediaCodecVideoDecoder* mcvd_raw_; MediaCodecVideoDecoder* mcvd_raw_;
std::unique_ptr<MediaCodecVideoDecoder> mcvd_; std::unique_ptr<MediaCodecVideoDecoder> mcvd_;
// This must outlive |mcvd_| .
std::unique_ptr<MockMediaDrmBridgeCdmContext> cdm_;
}; };
TEST_F(MediaCodecVideoDecoderTest, UnknownCodecIsRejected) { TEST_F(MediaCodecVideoDecoderTest, UnknownCodecIsRejected) {
...@@ -699,4 +731,73 @@ TEST_F(MediaCodecVideoDecoderTest, TeardownDrainsVp8CodecsBeforeDestruction) { ...@@ -699,4 +731,73 @@ TEST_F(MediaCodecVideoDecoderTest, TeardownDrainsVp8CodecsBeforeDestruction) {
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
} }
TEST_F(MediaCodecVideoDecoderTest, CdmInitializationWorksForL3) {
// Make sure that MCVD uses the cdm, and sends it along to the codec.
CreateCdm(false);
EXPECT_CALL(*cdm_, RegisterPlayer(_, _));
InitializeWithOverlay_OneDecodePending(
TestVideoConfig::NormalEncrypted(kCodecH264));
ASSERT_TRUE(!!cdm_->new_key_cb);
ASSERT_TRUE(!!cdm_->cdm_unset_cb);
ASSERT_TRUE(!!cdm_->media_crypto_ready_cb);
ASSERT_EQ(surface_chooser_->current_state_.is_secure, true);
ASSERT_EQ(surface_chooser_->current_state_.is_required, false);
ASSERT_FALSE(codec_allocator_->most_recent_config->requires_secure_codec);
// We can't check for equality safely, but verify that something was provided.
ASSERT_TRUE(codec_allocator_->most_recent_config->media_crypto->obj());
// When |mcvd_| is destroyed, expect that it will unregister itself.
EXPECT_CALL(*cdm_,
UnregisterPlayer(MockMediaDrmBridgeCdmContext::kRegistrationId));
}
TEST_F(MediaCodecVideoDecoderTest, CdmInitializationWorksForL1) {
// Make sure that MCVD uses the cdm, and sends it along to the codec.
CreateCdm(true);
EXPECT_CALL(*cdm_, RegisterPlayer(_, _));
InitializeWithOverlay_OneDecodePending(
TestVideoConfig::NormalEncrypted(kCodecH264));
ASSERT_TRUE(!!cdm_->new_key_cb);
ASSERT_TRUE(!!cdm_->cdm_unset_cb);
ASSERT_TRUE(!!cdm_->media_crypto_ready_cb);
ASSERT_EQ(surface_chooser_->current_state_.is_secure, true);
ASSERT_EQ(surface_chooser_->current_state_.is_required, true);
ASSERT_TRUE(codec_allocator_->most_recent_config->requires_secure_codec);
ASSERT_TRUE(codec_allocator_->most_recent_config->media_crypto->obj());
// When |mcvd_| is destroyed, expect that it will unregister itself.
EXPECT_CALL(*cdm_,
UnregisterPlayer(MockMediaDrmBridgeCdmContext::kRegistrationId));
}
TEST_F(MediaCodecVideoDecoderTest, CdmIsIgnoredIfNotEncrypted) {
CreateCdm(true);
// It should not register or unregister.
EXPECT_CALL(*cdm_, RegisterPlayer(_, _)).Times(0);
EXPECT_CALL(*cdm_,
UnregisterPlayer(MockMediaDrmBridgeCdmContext::kRegistrationId))
.Times(0);
ASSERT_TRUE(Initialize(TestVideoConfig::NormalH264()));
ASSERT_TRUE(!cdm_->new_key_cb);
ASSERT_TRUE(!cdm_->cdm_unset_cb);
ASSERT_TRUE(!cdm_->media_crypto_ready_cb);
ASSERT_EQ(surface_chooser_->current_state_.is_secure, false);
ASSERT_EQ(surface_chooser_->current_state_.is_required, false);
}
TEST_F(MediaCodecVideoDecoderTest, MissingMediaCryptoFailsInit) {
// Encrypted media that doesn't get a mediacrypto should fail to init.
CreateCdm(true);
media_crypto_ = nullptr;
EXPECT_CALL(*cdm_, RegisterPlayer(_, _));
ASSERT_FALSE(Initialize(TestVideoConfig::NormalEncrypted(kCodecH264)));
EXPECT_CALL(*cdm_,
UnregisterPlayer(MockMediaDrmBridgeCdmContext::kRegistrationId));
}
TEST_F(MediaCodecVideoDecoderTest, MissingCdmFailsInit) {
// MCVD should fail init if we don't provide a cdm with an encrypted config.
ASSERT_FALSE(Initialize(TestVideoConfig::NormalEncrypted(kCodecH264)));
}
} // namespace media } // namespace media
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment