gecko-dev/dom/media/gmp/ChromiumCDMChild.cpp

994 строки
32 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ChromiumCDMChild.h"
#include "GMPContentChild.h"
#include "WidevineUtils.h"
#include "WidevineFileIO.h"
#include "WidevineVideoFrame.h"
#include "GMPLog.h"
#include "GMPPlatform.h"
#include "mozilla/Unused.h"
#include "nsPrintfCString.h"
#include "base/time.h"
#include "GMPUtils.h"
#include "mozilla/ScopeExit.h"
#include "CDMStorageIdProvider.h"
namespace mozilla {
namespace gmp {
ChromiumCDMChild::ChromiumCDMChild(GMPContentChild* aPlugin)
: mPlugin(aPlugin)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild:: ctor this=%p", this);
}
void
ChromiumCDMChild::Init(cdm::ContentDecryptionModule_10* aCDM,
const nsCString& aStorageId)
{
MOZ_ASSERT(IsOnMessageLoopThread());
mCDM = aCDM;
MOZ_ASSERT(mCDM);
mStorageId = aStorageId;
}
void
ChromiumCDMChild::TimerExpired(void* aContext)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::TimerExpired(context=0x%p)", aContext);
if (mCDM) {
mCDM->TimerExpired(aContext);
}
}
class CDMShmemBuffer : public CDMBuffer
{
public:
CDMShmemBuffer(ChromiumCDMChild* aProtocol, ipc::Shmem aShmem)
: mProtocol(aProtocol)
, mSize(aShmem.Size<uint8_t>())
, mShmem(aShmem)
{
GMP_LOG("CDMShmemBuffer(size=%" PRIu32 ") created", Size());
// Note: Chrome initializes the size of a buffer to it capacity. We do the same.
}
CDMShmemBuffer(ChromiumCDMChild* aProtocol,
ipc::Shmem aShmem,
WidevineBuffer* aLocalBuffer)
: CDMShmemBuffer(aProtocol, aShmem)
{
MOZ_ASSERT(aLocalBuffer->Size() == Size());
memcpy(Data(),
aLocalBuffer->Data(),
std::min<uint32_t>(aLocalBuffer->Size(), Size()));
}
~CDMShmemBuffer() override
{
GMP_LOG("CDMShmemBuffer(size=%" PRIu32 ") destructed writable=%d",
Size(),
mShmem.IsWritable());
if (mShmem.IsWritable()) {
// The shmem wasn't extracted to send its data back up to the parent process,
// so we can reuse the shmem.
mProtocol->GiveBuffer(std::move(mShmem));
}
}
void Destroy() override
{
GMP_LOG("CDMShmemBuffer::Destroy(size=%" PRIu32 ")", Size());
delete this;
}
uint32_t Capacity() const override { return mShmem.Size<uint8_t>(); }
uint8_t* Data() override { return mShmem.get<uint8_t>(); }
void SetSize(uint32_t aSize) override
{
MOZ_ASSERT(aSize <= Capacity());
// Note: We can't use the shmem's size member after ExtractShmem(),
// has been called, so we track the size exlicitly so that we can use
// Size() in logging after we've called ExtractShmem().
GMP_LOG("CDMShmemBuffer::SetSize(size=%" PRIu32 ")", Size());
mSize = aSize;
}
uint32_t Size() const override { return mSize; }
ipc::Shmem ExtractShmem()
{
ipc::Shmem shmem = mShmem;
mShmem = ipc::Shmem();
return shmem;
}
CDMShmemBuffer* AsShmemBuffer() override { return this; }
private:
RefPtr<ChromiumCDMChild> mProtocol;
uint32_t mSize;
mozilla::ipc::Shmem mShmem;
CDMShmemBuffer(const CDMShmemBuffer&);
void operator=(const CDMShmemBuffer&);
};
static nsCString
ToString(const nsTArray<ipc::Shmem>& aBuffers)
{
nsCString s;
for (const ipc::Shmem& shmem : aBuffers) {
if (!s.IsEmpty()) {
s.AppendLiteral(",");
}
s.AppendInt(static_cast<uint32_t>(shmem.Size<uint8_t>()));
}
return s;
}
cdm::Buffer*
ChromiumCDMChild::Allocate(uint32_t aCapacity)
{
GMP_LOG("ChromiumCDMChild::Allocate(capacity=%" PRIu32 ") bufferSizes={%s}",
aCapacity,
ToString(mBuffers).get());
MOZ_ASSERT(IsOnMessageLoopThread());
if (mBuffers.IsEmpty()) {
Unused << SendIncreaseShmemPoolSize();
}
// Find the shmem with the least amount of wasted space if we were to
// select it for this sized allocation. We need to do this because shmems
// for decrypted audio as well as video frames are both stored in this
// list, and we don't want to use the video frame shmems for audio samples.
const size_t invalid = std::numeric_limits<size_t>::max();
size_t best = invalid;
auto wastedSpace = [this, aCapacity](size_t index) {
return mBuffers[index].Size<uint8_t>() - aCapacity;
};
for (size_t i = 0; i < mBuffers.Length(); i++) {
if (mBuffers[i].Size<uint8_t>() >= aCapacity &&
(best == invalid || wastedSpace(i) < wastedSpace(best))) {
best = i;
}
}
if (best == invalid) {
// The parent process should have bestowed upon us a shmem of appropriate
// size, but did not! Do a "dive and catch", and create an non-shared
// memory buffer. The parent will detect this and send us an extra shmem
// so future frames can be in shmems, i.e. returned on the fast path.
return new WidevineBuffer(aCapacity);
}
ipc::Shmem shmem = mBuffers[best];
mBuffers.RemoveElementAt(best);
return new CDMShmemBuffer(this, shmem);
}
void
ChromiumCDMChild::SetTimer(int64_t aDelayMs, void* aContext)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::SetTimer(delay=%" PRId64 ", context=0x%p)",
aDelayMs,
aContext);
RefPtr<ChromiumCDMChild> self(this);
SetTimerOnMainThread(NewGMPTask([self, aContext]() {
self->TimerExpired(aContext);
}), aDelayMs);
}
cdm::Time
ChromiumCDMChild::GetCurrentWallTime()
{
return base::Time::Now().ToDoubleT();
}
template <typename MethodType, typename... ParamType>
void
ChromiumCDMChild::CallMethod(MethodType aMethod, ParamType&&... aParams)
{
MOZ_ASSERT(IsOnMessageLoopThread());
// Avoid calling member function after destroy.
if (!mDestroyed) {
Unused << (this->*aMethod)(std::forward<ParamType>(aParams)...);
}
}
template<typename MethodType, typename... ParamType>
void
ChromiumCDMChild::CallOnMessageLoopThread(const char* const aName,
MethodType aMethod,
ParamType&&... aParams)
{
if (IsOnMessageLoopThread()) {
CallMethod(aMethod, std::forward<ParamType>(aParams)...);
} else {
auto m = &ChromiumCDMChild::CallMethod<
decltype(aMethod), const typename RemoveReference<ParamType>::Type&...>;
RefPtr<mozilla::Runnable> t =
NewRunnableMethod<decltype(aMethod),
const typename RemoveReference<ParamType>::Type...>(
aName,
this,
m,
aMethod,
std::forward<ParamType>(aParams)...);
mPlugin->GMPMessageLoop()->PostTask(t.forget());
}
}
void
ChromiumCDMChild::OnResolveKeyStatusPromise(uint32_t aPromiseId,
cdm::KeyStatus aKeyStatus) {
GMP_LOG("ChromiumCDMChild::OnResolveKeyStatusPromise(pid=%" PRIu32 "keystatus=%d)",
aPromiseId,
aKeyStatus);
CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnResolveKeyStatusPromise",
&ChromiumCDMChild::SendOnResolvePromiseWithKeyStatus,
aPromiseId,
static_cast<uint32_t>(aKeyStatus));
}
bool
ChromiumCDMChild::OnResolveNewSessionPromiseInternal(uint32_t aPromiseId,
const nsCString& aSessionId)
{
MOZ_ASSERT(IsOnMessageLoopThread());
if (mLoadSessionPromiseIds.Contains(aPromiseId)) {
// As laid out in the Chromium CDM API, if the CDM fails to load
// a session it calls OnResolveNewSessionPromise with nullptr as the sessionId.
// We can safely assume this means that we have failed to load a session
// as the other methods specify calling 'OnRejectPromise' when they fail.
bool loadSuccessful = !aSessionId.IsEmpty();
GMP_LOG("ChromiumCDMChild::OnResolveNewSessionPromise(pid=%u, sid=%s) "
"resolving %s load session ",
aPromiseId,
aSessionId.get(),
(loadSuccessful ? "successful" : "failed"));
mLoadSessionPromiseIds.RemoveElement(aPromiseId);
return SendResolveLoadSessionPromise(aPromiseId, loadSuccessful);
}
return SendOnResolveNewSessionPromise(aPromiseId,
aSessionId);
}
void
ChromiumCDMChild::OnResolveNewSessionPromise(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdSize)
{
GMP_LOG("ChromiumCDMChild::OnResolveNewSessionPromise(pid=%" PRIu32
", sid=%s)",
aPromiseId,
aSessionId);
CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnResolveNewSessionPromise",
&ChromiumCDMChild::OnResolveNewSessionPromiseInternal,
aPromiseId,
nsCString(aSessionId, aSessionIdSize));
}
void ChromiumCDMChild::OnResolvePromise(uint32_t aPromiseId)
{
GMP_LOG("ChromiumCDMChild::OnResolvePromise(pid=%" PRIu32 ")", aPromiseId);
CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnResolvePromise",
&ChromiumCDMChild::SendOnResolvePromise,
aPromiseId);
}
void
ChromiumCDMChild::OnRejectPromise(uint32_t aPromiseId,
cdm::Exception aException,
uint32_t aSystemCode,
const char* aErrorMessage,
uint32_t aErrorMessageSize)
{
GMP_LOG("ChromiumCDMChild::OnRejectPromise(pid=%" PRIu32 ", err=%" PRIu32
" code=%" PRIu32 ", msg='%s')",
aPromiseId,
aException,
aSystemCode,
aErrorMessage);
CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnRejectPromise",
&ChromiumCDMChild::SendOnRejectPromise,
aPromiseId,
static_cast<uint32_t>(aException),
aSystemCode,
nsCString(aErrorMessage, aErrorMessageSize));
}
void
ChromiumCDMChild::OnSessionMessage(const char* aSessionId,
uint32_t aSessionIdSize,
cdm::MessageType aMessageType,
const char* aMessage,
uint32_t aMessageSize)
{
GMP_LOG("ChromiumCDMChild::OnSessionMessage(sid=%s, type=%" PRIu32
" size=%" PRIu32 ")",
aSessionId,
aMessageType,
aMessageSize);
nsTArray<uint8_t> message;
message.AppendElements(aMessage, aMessageSize);
CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnSessionMessage",
&ChromiumCDMChild::SendOnSessionMessage,
nsCString(aSessionId, aSessionIdSize),
static_cast<uint32_t>(aMessageType),
message);
}
static nsCString
ToString(const cdm::KeyInformation* aKeysInfo, uint32_t aKeysInfoCount)
{
nsCString str;
for (uint32_t i = 0; i < aKeysInfoCount; i++) {
if (!str.IsEmpty()) {
str.AppendLiteral(",");
}
const cdm::KeyInformation& key = aKeysInfo[i];
str.Append(ToHexString(key.key_id, key.key_id_size));
str.AppendLiteral("=");
str.AppendInt(key.status);
}
return str;
}
void
ChromiumCDMChild::OnSessionKeysChange(const char *aSessionId,
uint32_t aSessionIdSize,
bool aHasAdditionalUsableKey,
const cdm::KeyInformation* aKeysInfo,
uint32_t aKeysInfoCount)
{
GMP_LOG("ChromiumCDMChild::OnSessionKeysChange(sid=%s) keys={%s}",
aSessionId,
ToString(aKeysInfo, aKeysInfoCount).get());
nsTArray<CDMKeyInformation> keys;
keys.SetCapacity(aKeysInfoCount);
for (uint32_t i = 0; i < aKeysInfoCount; i++) {
const cdm::KeyInformation& key = aKeysInfo[i];
nsTArray<uint8_t> kid;
kid.AppendElements(key.key_id, key.key_id_size);
keys.AppendElement(CDMKeyInformation(kid, key.status, key.system_code));
}
CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnSessionMessage",
&ChromiumCDMChild::SendOnSessionKeysChange,
nsCString(aSessionId, aSessionIdSize),
keys);
}
void
ChromiumCDMChild::OnExpirationChange(const char* aSessionId,
uint32_t aSessionIdSize,
cdm::Time aNewExpiryTime)
{
GMP_LOG("ChromiumCDMChild::OnExpirationChange(sid=%s, time=%lf)",
aSessionId,
aNewExpiryTime);
CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnExpirationChange",
&ChromiumCDMChild::SendOnExpirationChange,
nsCString(aSessionId, aSessionIdSize),
aNewExpiryTime);
}
void
ChromiumCDMChild::OnSessionClosed(const char* aSessionId,
uint32_t aSessionIdSize)
{
GMP_LOG("ChromiumCDMChild::OnSessionClosed(sid=%s)", aSessionId);
CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnSessionClosed",
&ChromiumCDMChild::SendOnSessionClosed,
nsCString(aSessionId, aSessionIdSize));
}
cdm::FileIO*
ChromiumCDMChild::CreateFileIO(cdm::FileIOClient * aClient)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::CreateFileIO()");
if (!mPersistentStateAllowed) {
return nullptr;
}
return new WidevineFileIO(aClient);
}
void
ChromiumCDMChild::RequestStorageId(uint32_t aVersion)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::RequestStorageId() aVersion = %u", aVersion);
// aVersion >= 0x80000000 are reserved.
if (aVersion >= 0x80000000) {
mCDM->OnStorageId(aVersion, nullptr, 0);
return;
}
if (aVersion > CDMStorageIdProvider::kCurrentVersion) {
mCDM->OnStorageId(aVersion, nullptr, 0);
return;
}
mCDM->OnStorageId(CDMStorageIdProvider::kCurrentVersion,
!mStorageId.IsEmpty()
? reinterpret_cast<const uint8_t*>(mStorageId.get())
: nullptr,
mStorageId.Length());
}
ChromiumCDMChild::~ChromiumCDMChild()
{
GMP_LOG("ChromiumCDMChild:: dtor this=%p", this);
}
bool
ChromiumCDMChild::IsOnMessageLoopThread()
{
return mPlugin && mPlugin->GMPMessageLoop() == MessageLoop::current();
}
void
ChromiumCDMChild::PurgeShmems()
{
for (ipc::Shmem& shmem : mBuffers) {
DeallocShmem(shmem);
}
mBuffers.Clear();
}
ipc::IPCResult
ChromiumCDMChild::RecvPurgeShmems()
{
PurgeShmems();
return IPC_OK();
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvInit(const bool& aAllowDistinctiveIdentifier,
const bool& aAllowPersistentState)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::RecvInit(distinctiveId=%s, persistentState=%s)",
aAllowDistinctiveIdentifier ? "true" : "false",
aAllowPersistentState ? "true" : "false");
mPersistentStateAllowed = aAllowPersistentState;
if (mCDM) {
mCDM->Initialize(aAllowDistinctiveIdentifier,
aAllowPersistentState,
// We do not yet support hardware secure codecs
false);
}
return IPC_OK();
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvSetServerCertificate(const uint32_t& aPromiseId,
nsTArray<uint8_t>&& aServerCert)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::RecvSetServerCertificate() certlen=%zu",
aServerCert.Length());
if (mCDM) {
mCDM->SetServerCertificate(aPromiseId,
aServerCert.Elements(),
aServerCert.Length());
}
return IPC_OK();
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvCreateSessionAndGenerateRequest(
const uint32_t& aPromiseId,
const uint32_t& aSessionType,
const uint32_t& aInitDataType,
nsTArray<uint8_t>&& aInitData)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::RecvCreateSessionAndGenerateRequest("
"pid=%" PRIu32 ", sessionType=%" PRIu32 ", initDataType=%" PRIu32
") initDataLen=%zu",
aPromiseId,
aSessionType,
aInitDataType,
aInitData.Length());
MOZ_ASSERT(aSessionType <= cdm::SessionType::kPersistentKeyRelease);
MOZ_ASSERT(aInitDataType <= cdm::InitDataType::kWebM);
if (mCDM) {
mCDM->CreateSessionAndGenerateRequest(aPromiseId,
static_cast<cdm::SessionType>(aSessionType),
static_cast<cdm::InitDataType>(aInitDataType),
aInitData.Elements(),
aInitData.Length());
}
return IPC_OK();
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvLoadSession(const uint32_t& aPromiseId,
const uint32_t& aSessionType,
const nsCString& aSessionId)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::RecvLoadSession(pid=%u, type=%u, sessionId=%s)",
aPromiseId,
aSessionType,
aSessionId.get());
if (mCDM) {
mLoadSessionPromiseIds.AppendElement(aPromiseId);
mCDM->LoadSession(aPromiseId,
static_cast<cdm::SessionType>(aSessionType),
aSessionId.get(),
aSessionId.Length());
}
return IPC_OK();
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvUpdateSession(const uint32_t& aPromiseId,
const nsCString& aSessionId,
nsTArray<uint8_t>&& aResponse)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::RecvUpdateSession(pid=%" PRIu32
", sid=%s) responseLen=%zu",
aPromiseId,
aSessionId.get(),
aResponse.Length());
if (mCDM) {
mCDM->UpdateSession(aPromiseId,
aSessionId.get(),
aSessionId.Length(),
aResponse.Elements(),
aResponse.Length());
}
return IPC_OK();
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvCloseSession(const uint32_t& aPromiseId,
const nsCString& aSessionId)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::RecvCloseSession(pid=%" PRIu32 ", sid=%s)",
aPromiseId,
aSessionId.get());
if (mCDM) {
mCDM->CloseSession(aPromiseId, aSessionId.get(), aSessionId.Length());
}
return IPC_OK();
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvRemoveSession(const uint32_t& aPromiseId,
const nsCString& aSessionId)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::RecvRemoveSession(pid=%" PRIu32 ", sid=%s)",
aPromiseId,
aSessionId.get());
if (mCDM) {
mCDM->RemoveSession(aPromiseId, aSessionId.get(), aSessionId.Length());
}
return IPC_OK();
}
// See https://cs.chromium.org/chromium/src/media/blink/webcontentdecryptionmodule_impl.cc?rcl=9d4e17194fbae2839d269e0b625520eac09efa9b&l=40
static cdm::HdcpVersion
ToCDMHdcpVersion(const nsCString& aMinHdcpVersion)
{
// String compare with ignoring case.
if (aMinHdcpVersion.IsEmpty()) {
return cdm::HdcpVersion::kHdcpVersionNone;
}
if (aMinHdcpVersion.EqualsIgnoreCase("hdcp-1.0")) {
return cdm::HdcpVersion::kHdcpVersion1_0;
}
if (aMinHdcpVersion.EqualsIgnoreCase("hdcp-1.1")) {
return cdm::HdcpVersion::kHdcpVersion1_1;
}
if (aMinHdcpVersion.EqualsIgnoreCase("hdcp-1.2")) {
return cdm::HdcpVersion::kHdcpVersion1_2;
}
if (aMinHdcpVersion.EqualsIgnoreCase("hdcp-1.3")) {
return cdm::HdcpVersion::kHdcpVersion1_3;
}
if (aMinHdcpVersion.EqualsIgnoreCase("hdcp-1.4")) {
return cdm::HdcpVersion::kHdcpVersion1_4;
}
if (aMinHdcpVersion.EqualsIgnoreCase("hdcp-2.0")) {
return cdm::HdcpVersion::kHdcpVersion2_0;
}
if (aMinHdcpVersion.EqualsIgnoreCase("hdcp-2.1")) {
return cdm::HdcpVersion::kHdcpVersion2_1;
}
if (aMinHdcpVersion.EqualsIgnoreCase("hdcp-2.2")) {
return cdm::HdcpVersion::kHdcpVersion2_2;
}
// Invalid hdcp version string.
return cdm::HdcpVersion::kHdcpVersionNone;
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvGetStatusForPolicy(const uint32_t& aPromiseId,
const nsCString& aMinHdcpVersion)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::RecvGetStatusForPolicy(pid=%" PRIu32 ", MinHdcpVersion=%s)",
aPromiseId,
aMinHdcpVersion.get());
if (mCDM) {
cdm::Policy policy;
// We didn't check the return value of ToCDMHdcpVersion.
// Let CDM to handle the cdm::HdcpVersion::kHdcpVersionNone case.
// CDM will callback by OnResolveKeyStatusPromise when it successfully executes.
policy.min_hdcp_version = ToCDMHdcpVersion(aMinHdcpVersion);
mCDM->GetStatusForPolicy(aPromiseId, policy);
}
return IPC_OK();
}
static cdm::EncryptionScheme
ConvertToCdmEncryptionScheme(const GMPEncryptionScheme& aEncryptionScheme)
{
switch (aEncryptionScheme) {
case GMPEncryptionScheme::kGMPEncryptionNone:
return cdm::EncryptionScheme::kUnencrypted;
case GMPEncryptionScheme::kGMPEncryptionCenc:
return cdm::EncryptionScheme::kCenc;
case GMPEncryptionScheme::kGMPEncryptionCbcs:
return cdm::EncryptionScheme::kCbcs;
default:
MOZ_ASSERT_UNREACHABLE("Cannot convert invalid encryption scheme!");
return cdm::EncryptionScheme::kUnencrypted;
}
}
static void
InitInputBuffer(const CDMInputBuffer& aBuffer,
nsTArray<cdm::SubsampleEntry>& aSubSamples,
cdm::InputBuffer_2& aInputBuffer)
{
aInputBuffer.data = aBuffer.mData().get<uint8_t>();
aInputBuffer.data_size = aBuffer.mData().Size<uint8_t>();
if (aBuffer.mEncryptionScheme() > GMPEncryptionScheme::kGMPEncryptionNone) {
// Cbcs is not yet supported, so we expect only cenc if the buffer us
// encrypted
MOZ_ASSERT(aBuffer.mEncryptionScheme() ==
GMPEncryptionScheme::kGMPEncryptionCenc);
aInputBuffer.key_id = aBuffer.mKeyId().Elements();
aInputBuffer.key_id_size = aBuffer.mKeyId().Length();
aInputBuffer.iv = aBuffer.mIV().Elements();
aInputBuffer.iv_size = aBuffer.mIV().Length();
aSubSamples.SetCapacity(aBuffer.mClearBytes().Length());
for (size_t i = 0; i < aBuffer.mCipherBytes().Length(); i++) {
aSubSamples.AppendElement(cdm::SubsampleEntry{
aBuffer.mClearBytes()[i], aBuffer.mCipherBytes()[i] });
}
aInputBuffer.subsamples = aSubSamples.Elements();
aInputBuffer.num_subsamples = aSubSamples.Length();
aInputBuffer.encryption_scheme =
ConvertToCdmEncryptionScheme(aBuffer.mEncryptionScheme());
}
aInputBuffer.timestamp = aBuffer.mTimestamp();
}
bool
ChromiumCDMChild::HasShmemOfSize(size_t aSize) const
{
for (const ipc::Shmem& shmem : mBuffers) {
if (shmem.Size<uint8_t>() == aSize) {
return true;
}
}
return false;
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvDecrypt(const uint32_t& aId,
const CDMInputBuffer& aBuffer)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::RecvDecrypt()");
// Parent should have already gifted us a shmem to use as output.
size_t outputShmemSize = aBuffer.mData().Size<uint8_t>();
MOZ_ASSERT(HasShmemOfSize(outputShmemSize));
// Ensure we deallocate the shmem used to send input.
RefPtr<ChromiumCDMChild> self = this;
auto autoDeallocateInputShmem =
MakeScopeExit([&, self] { self->DeallocShmem(aBuffer.mData()); });
// On failure, we need to ensure that the shmem that the parent sent
// for the CDM to use to return output back to the parent is deallocated.
// Otherwise, it will leak.
auto autoDeallocateOutputShmem = MakeScopeExit([self, outputShmemSize] {
self->mBuffers.RemoveElementsBy([outputShmemSize, self](ipc::Shmem& aShmem) {
if (aShmem.Size<uint8_t>() != outputShmemSize) {
return false;
}
self->DeallocShmem(aShmem);
return true;
});
});
if (!mCDM) {
GMP_LOG("ChromiumCDMChild::RecvDecrypt() no CDM");
Unused << SendDecryptFailed(aId, cdm::kDecryptError);
return IPC_OK();
}
if (aBuffer.mClearBytes().Length() != aBuffer.mCipherBytes().Length()) {
GMP_LOG("ChromiumCDMChild::RecvDecrypt() clear/cipher bytes length doesn't "
"match");
Unused << SendDecryptFailed(aId, cdm::kDecryptError);
return IPC_OK();
}
cdm::InputBuffer_2 input = {};
nsTArray<cdm::SubsampleEntry> subsamples;
InitInputBuffer(aBuffer, subsamples, input);
WidevineDecryptedBlock output;
cdm::Status status = mCDM->Decrypt(input, &output);
// CDM should have allocated a cdm::Buffer for output.
CDMShmemBuffer* buffer =
output.DecryptedBuffer()
? static_cast<CDMShmemBuffer*>(output.DecryptedBuffer())
: nullptr;
MOZ_ASSERT_IF(buffer, buffer->AsShmemBuffer());
if (status != cdm::kSuccess || !buffer) {
Unused << SendDecryptFailed(aId, status);
return IPC_OK();
}
// Success! Return the decrypted sample to parent.
MOZ_ASSERT(!HasShmemOfSize(outputShmemSize));
ipc::Shmem shmem = buffer->ExtractShmem();
if (SendDecrypted(aId, cdm::kSuccess, shmem)) {
// No need to deallocate the output shmem; it should have been returned
// to the content process.
autoDeallocateOutputShmem.release();
}
return IPC_OK();
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvInitializeVideoDecoder(
const CDMVideoDecoderConfig& aConfig)
{
MOZ_ASSERT(IsOnMessageLoopThread());
MOZ_ASSERT(!mDecoderInitialized);
if (!mCDM) {
GMP_LOG("ChromiumCDMChild::RecvInitializeVideoDecoder() no CDM");
Unused << SendOnDecoderInitDone(cdm::kInitializationError);
return IPC_OK();
}
cdm::VideoDecoderConfig_2 config = {};
config.codec = static_cast<cdm::VideoCodec>(aConfig.mCodec());
config.profile = static_cast<cdm::VideoCodecProfile>(aConfig.mProfile());
config.format = static_cast<cdm::VideoFormat>(aConfig.mFormat());
config.coded_size =
mCodedSize = { aConfig.mImageWidth(), aConfig.mImageHeight() };
nsTArray<uint8_t> extraData(aConfig.mExtraData());
config.extra_data = extraData.Elements();
config.extra_data_size = extraData.Length();
config.encryption_scheme =
ConvertToCdmEncryptionScheme(aConfig.mEncryptionScheme());
cdm::Status status = mCDM->InitializeVideoDecoder(config);
GMP_LOG("ChromiumCDMChild::RecvInitializeVideoDecoder() status=%u", status);
Unused << SendOnDecoderInitDone(status);
mDecoderInitialized = status == cdm::kSuccess;
return IPC_OK();
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvDeinitializeVideoDecoder()
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::RecvDeinitializeVideoDecoder()");
MOZ_ASSERT(mDecoderInitialized);
if (mDecoderInitialized && mCDM) {
mCDM->DeinitializeDecoder(cdm::kStreamTypeVideo);
}
mDecoderInitialized = false;
PurgeShmems();
return IPC_OK();
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvResetVideoDecoder()
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::RecvResetVideoDecoder()");
if (mDecoderInitialized && mCDM) {
mCDM->ResetDecoder(cdm::kStreamTypeVideo);
}
Unused << SendResetVideoDecoderComplete();
return IPC_OK();
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvDecryptAndDecodeFrame(const CDMInputBuffer& aBuffer)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::RecvDecryptAndDecodeFrame() t=%" PRId64 ")",
aBuffer.mTimestamp());
MOZ_ASSERT(mDecoderInitialized);
if (!mCDM) {
GMP_LOG("ChromiumCDMChild::RecvDecryptAndDecodeFrame() no CDM");
Unused << SendDecodeFailed(cdm::kDecodeError);
return IPC_OK();
}
RefPtr<ChromiumCDMChild> self = this;
auto autoDeallocateShmem = MakeScopeExit([&, self] {
self->DeallocShmem(aBuffer.mData());
});
// The output frame may not have the same timestamp as the frame we put in.
// We may need to input a number of frames before we receive output. The
// CDM's decoder reorders to ensure frames output are in presentation order.
// So we need to store the durations of the frames input, and retrieve them
// on output.
mFrameDurations.Insert(aBuffer.mTimestamp(), aBuffer.mDuration());
cdm::InputBuffer_2 input = {};
nsTArray<cdm::SubsampleEntry> subsamples;
InitInputBuffer(aBuffer, subsamples, input);
WidevineVideoFrame frame;
cdm::Status rv = mCDM->DecryptAndDecodeFrame(input, &frame);
GMP_LOG("ChromiumCDMChild::RecvDecryptAndDecodeFrame() t=%" PRId64
" CDM decoder rv=%d",
aBuffer.mTimestamp(),
rv);
switch (rv) {
case cdm::kNeedMoreData:
Unused << SendDecodeFailed(rv);
break;
case cdm::kNoKey:
GMP_LOG("NoKey for sample at time=%" PRId64 "!", input.timestamp);
// Somehow our key became unusable. Typically this would happen when
// a stream requires output protection, and the configuration changed
// such that output protection is no longer available. For example, a
// non-compliant monitor was attached. The JS player should notice the
// key status changing to "output-restricted", and is supposed to switch
// to a stream that doesn't require OP. In order to keep the playback
// pipeline rolling, just output a black frame. See bug 1343140.
if (!frame.InitToBlack(mCodedSize.width, mCodedSize.height,
input.timestamp)) {
Unused << SendDecodeFailed(cdm::kDecodeError);
break;
}
MOZ_FALLTHROUGH;
case cdm::kSuccess:
if (frame.FrameBuffer()) {
ReturnOutput(frame);
break;
}
// CDM didn't set a frame buffer on the sample, report it as an error.
MOZ_FALLTHROUGH;
default:
Unused << SendDecodeFailed(rv);
break;
}
return IPC_OK();
}
void
ChromiumCDMChild::ReturnOutput(WidevineVideoFrame& aFrame)
{
MOZ_ASSERT(IsOnMessageLoopThread());
MOZ_ASSERT(aFrame.FrameBuffer());
gmp::CDMVideoFrame output;
output.mFormat() = static_cast<cdm::VideoFormat>(aFrame.Format());
output.mImageWidth() = aFrame.Size().width;
output.mImageHeight() = aFrame.Size().height;
output.mYPlane() = { aFrame.PlaneOffset(cdm::VideoFrame::kYPlane),
aFrame.Stride(cdm::VideoFrame::kYPlane) };
output.mUPlane() = { aFrame.PlaneOffset(cdm::VideoFrame::kUPlane),
aFrame.Stride(cdm::VideoFrame::kUPlane) };
output.mVPlane() = { aFrame.PlaneOffset(cdm::VideoFrame::kVPlane),
aFrame.Stride(cdm::VideoFrame::kVPlane) };
output.mTimestamp() = aFrame.Timestamp();
uint64_t duration = 0;
if (mFrameDurations.Find(aFrame.Timestamp(), duration)) {
output.mDuration() = duration;
}
CDMBuffer* base = reinterpret_cast<CDMBuffer*>(aFrame.FrameBuffer());
if (base->AsShmemBuffer()) {
ipc::Shmem shmem = base->AsShmemBuffer()->ExtractShmem();
Unused << SendDecodedShmem(output, shmem);
} else {
MOZ_ASSERT(base->AsArrayBuffer());
Unused << SendDecodedData(output, base->AsArrayBuffer()->ExtractBuffer());
}
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvDrain()
{
MOZ_ASSERT(IsOnMessageLoopThread());
if (!mCDM) {
GMP_LOG("ChromiumCDMChild::RecvDrain() no CDM");
Unused << SendDrainComplete();
return IPC_OK();
}
WidevineVideoFrame frame;
cdm::InputBuffer_2 sample = {};
cdm::Status rv = mCDM->DecryptAndDecodeFrame(sample, &frame);
GMP_LOG("ChromiumCDMChild::RecvDrain(); DecryptAndDecodeFrame() rv=%d", rv);
if (rv == cdm::kSuccess) {
MOZ_ASSERT(frame.Format() != cdm::kUnknownVideoFormat);
ReturnOutput(frame);
} else {
Unused << SendDrainComplete();
}
return IPC_OK();
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvDestroy()
{
MOZ_ASSERT(IsOnMessageLoopThread());
GMP_LOG("ChromiumCDMChild::RecvDestroy()");
MOZ_ASSERT(!mDecoderInitialized);
if (mCDM) {
mCDM->Destroy();
mCDM = nullptr;
}
mDestroyed = true;
Unused << Send__delete__(this);
return IPC_OK();
}
mozilla::ipc::IPCResult
ChromiumCDMChild::RecvGiveBuffer(ipc::Shmem&& aBuffer)
{
MOZ_ASSERT(IsOnMessageLoopThread());
GiveBuffer(std::move(aBuffer));
return IPC_OK();
}
void
ChromiumCDMChild::GiveBuffer(ipc::Shmem&& aBuffer)
{
MOZ_ASSERT(IsOnMessageLoopThread());
size_t sz = aBuffer.Size<uint8_t>();
mBuffers.AppendElement(std::move(aBuffer));
GMP_LOG("ChromiumCDMChild::RecvGiveBuffer(capacity=%zu"
") bufferSizes={%s} mDecoderInitialized=%d",
sz,
ToString(mBuffers).get(),
mDecoderInitialized);
}
} // namespace gmp
} // namespace mozilla