summaryrefslogtreecommitdiffstats
path: root/dom/media/gmp/ChromiumCDMChild.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/gmp/ChromiumCDMChild.cpp')
-rw-r--r--dom/media/gmp/ChromiumCDMChild.cpp866
1 files changed, 866 insertions, 0 deletions
diff --git a/dom/media/gmp/ChromiumCDMChild.cpp b/dom/media/gmp/ChromiumCDMChild.cpp
new file mode 100644
index 0000000000..4592f2e291
--- /dev/null
+++ b/dom/media/gmp/ChromiumCDMChild.cpp
@@ -0,0 +1,866 @@
+/* -*- 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"
+#include "nsReadableUtils.h"
+
+#include <type_traits>
+
+namespace mozilla::gmp {
+
+ChromiumCDMChild::ChromiumCDMChild(GMPContentChild* aPlugin)
+ : mPlugin(aPlugin) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG("ChromiumCDMChild:: ctor this=%p", this);
+}
+
+void ChromiumCDMChild::Init(cdm::ContentDecryptionModule_10* aCDM,
+ const nsACString& aStorageId) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ mCDM = aCDM;
+ MOZ_ASSERT(mCDM);
+ mStorageId = aStorageId;
+}
+
+void ChromiumCDMChild::TimerExpired(void* aContext) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG("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_DEBUG("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_DEBUG("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_DEBUG("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_DEBUG("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 auto ToString(const nsTArray<ipc::Shmem>& aBuffers) {
+ return StringJoin(","_ns, aBuffers, [](auto& s, const ipc::Shmem& shmem) {
+ s.AppendInt(static_cast<uint32_t>(shmem.Size<uint8_t>()));
+ });
+}
+
+cdm::Buffer* ChromiumCDMChild::Allocate(uint32_t aCapacity) {
+ GMP_LOG_DEBUG("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_DEBUG("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 (NS_WARN_IF(!mPlugin)) {
+ return;
+ }
+
+ if (IsOnMessageLoopThread()) {
+ CallMethod(aMethod, std::forward<ParamType>(aParams)...);
+ } else {
+ auto m = &ChromiumCDMChild::CallMethod<
+ decltype(aMethod), const std::remove_reference_t<ParamType>&...>;
+ RefPtr<mozilla::Runnable> t =
+ NewRunnableMethod<decltype(aMethod),
+ const std::remove_reference_t<ParamType>...>(
+ aName, this, m, aMethod, std::forward<ParamType>(aParams)...);
+ mPlugin->GMPMessageLoop()->PostTask(t.forget());
+ }
+}
+
+void ChromiumCDMChild::OnResolveKeyStatusPromise(uint32_t aPromiseId,
+ cdm::KeyStatus aKeyStatus) {
+ GMP_LOG_DEBUG("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 nsACString& 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_DEBUG(
+ "ChromiumCDMChild::OnResolveNewSessionPromise(pid=%u, sid=%s) "
+ "resolving %s load session ",
+ aPromiseId, PromiseFlatCString(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_DEBUG("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_DEBUG("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_DEBUG("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_DEBUG("ChromiumCDMChild::OnSessionMessage(sid=%s, type=%" PRIu32
+ " size=%" PRIu32 ")",
+ aSessionId, aMessageType, aMessageSize);
+ CopyableTArray<uint8_t> message;
+ message.AppendElements(aMessage, aMessageSize);
+ CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnSessionMessage",
+ &ChromiumCDMChild::SendOnSessionMessage,
+ nsCString(aSessionId, aSessionIdSize),
+ static_cast<uint32_t>(aMessageType), message);
+}
+
+static auto ToString(const cdm::KeyInformation* aKeysInfo,
+ uint32_t aKeysInfoCount) {
+ return StringJoin(","_ns, Span{aKeysInfo, aKeysInfoCount},
+ [](auto& str, const cdm::KeyInformation& key) {
+ str.Append(ToHexString(key.key_id, key.key_id_size));
+ str.AppendLiteral("=");
+ str.AppendInt(key.status);
+ });
+}
+
+void ChromiumCDMChild::OnSessionKeysChange(const char* aSessionId,
+ uint32_t aSessionIdSize,
+ bool aHasAdditionalUsableKey,
+ const cdm::KeyInformation* aKeysInfo,
+ uint32_t aKeysInfoCount) {
+ GMP_LOG_DEBUG("ChromiumCDMChild::OnSessionKeysChange(sid=%s) keys={%s}",
+ aSessionId, ToString(aKeysInfo, aKeysInfoCount).get());
+
+ CopyableTArray<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_DEBUG("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_DEBUG("ChromiumCDMChild::OnSessionClosed(sid=%s)", aSessionId);
+ CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnSessionClosed",
+ &ChromiumCDMChild::SendOnSessionClosed,
+ nsCString(aSessionId, aSessionIdSize));
+}
+
+void ChromiumCDMChild::QueryOutputProtectionStatus() {
+ GMP_LOG_DEBUG("ChromiumCDMChild::QueryOutputProtectionStatus()");
+ // We'll handle the response in `CompleteQueryOutputProtectionStatus`.
+ CallOnMessageLoopThread("gmp::ChromiumCDMChild::QueryOutputProtectionStatus",
+ &ChromiumCDMChild::SendOnQueryOutputProtectionStatus);
+}
+
+void ChromiumCDMChild::OnInitialized(bool aSuccess) {
+ MOZ_ASSERT(!mInitPromise.IsEmpty(),
+ "mInitPromise should exist during init callback!");
+ if (!aSuccess) {
+ mInitPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
+ }
+ mInitPromise.ResolveIfExists(true, __func__);
+}
+
+cdm::FileIO* ChromiumCDMChild::CreateFileIO(cdm::FileIOClient* aClient) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG("ChromiumCDMChild::CreateFileIO()");
+ if (!mPersistentStateAllowed) {
+ return nullptr;
+ }
+ return new WidevineFileIO(aClient);
+}
+
+void ChromiumCDMChild::RequestStorageId(uint32_t aVersion) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG("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_DEBUG("ChromiumCDMChild:: dtor this=%p", this);
+}
+
+bool ChromiumCDMChild::IsOnMessageLoopThread() {
+ return mPlugin && mPlugin->GMPMessageLoop() == MessageLoop::current();
+}
+
+void ChromiumCDMChild::ActorDestroy(ActorDestroyReason aReason) {
+ mPlugin = nullptr;
+}
+
+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,
+ InitResolver&& aResolver) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG(
+ "ChromiumCDMChild::RecvInit(distinctiveId=%s, persistentState=%s)",
+ aAllowDistinctiveIdentifier ? "true" : "false",
+ aAllowPersistentState ? "true" : "false");
+ mPersistentStateAllowed = aAllowPersistentState;
+
+ RefPtr<ChromiumCDMChild::InitPromise> promise = mInitPromise.Ensure(__func__);
+ promise->Then(
+ mPlugin->GMPMessageLoop()->SerialEventTarget(), __func__,
+ [aResolver](bool /* unused */) { aResolver(true); },
+ [aResolver](nsresult rv) {
+ GMP_LOG_DEBUG(
+ "ChromiumCDMChild::RecvInit() init promise rejected with "
+ "rv=%" PRIu32,
+ static_cast<uint32_t>(rv));
+ aResolver(false);
+ });
+
+ if (mCDM) {
+ // Once the CDM is initialized we expect it to resolve mInitPromise via
+ // ChromiumCDMChild::OnInitialized
+ mCDM->Initialize(aAllowDistinctiveIdentifier, aAllowPersistentState,
+ // We do not yet support hardware secure codecs
+ false);
+ } else {
+ GMP_LOG_DEBUG(
+ "ChromiumCDMChild::RecvInit() mCDM not set! Is GMP shutting down?");
+ mInitPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult ChromiumCDMChild::RecvSetServerCertificate(
+ const uint32_t& aPromiseId, nsTArray<uint8_t>&& aServerCert)
+
+{
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG("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_DEBUG(
+ "ChromiumCDMChild::RecvCreateSessionAndGenerateRequest("
+ "pid=%" PRIu32 ", sessionType=%" PRIu32 ", initDataType=%" PRIu32
+ ") initDataLen=%zu",
+ aPromiseId, aSessionType, aInitDataType, aInitData.Length());
+ MOZ_ASSERT(aSessionType <= cdm::SessionType::kPersistentUsageRecord);
+ 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 nsACString& aSessionId) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG(
+ "ChromiumCDMChild::RecvLoadSession(pid=%u, type=%u, sessionId=%s)",
+ aPromiseId, aSessionType, PromiseFlatCString(aSessionId).get());
+ if (mCDM) {
+ mLoadSessionPromiseIds.AppendElement(aPromiseId);
+ mCDM->LoadSession(aPromiseId, static_cast<cdm::SessionType>(aSessionType),
+ aSessionId.BeginReading(), aSessionId.Length());
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult ChromiumCDMChild::RecvUpdateSession(
+ const uint32_t& aPromiseId, const nsACString& aSessionId,
+ nsTArray<uint8_t>&& aResponse) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG("ChromiumCDMChild::RecvUpdateSession(pid=%" PRIu32
+ ", sid=%s) responseLen=%zu",
+ aPromiseId, PromiseFlatCString(aSessionId).get(),
+ aResponse.Length());
+ if (mCDM) {
+ mCDM->UpdateSession(aPromiseId, aSessionId.BeginReading(),
+ aSessionId.Length(), aResponse.Elements(),
+ aResponse.Length());
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult ChromiumCDMChild::RecvCloseSession(
+ const uint32_t& aPromiseId, const nsACString& aSessionId) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG("ChromiumCDMChild::RecvCloseSession(pid=%" PRIu32 ", sid=%s)",
+ aPromiseId, PromiseFlatCString(aSessionId).get());
+ if (mCDM) {
+ mCDM->CloseSession(aPromiseId, aSessionId.BeginReading(),
+ aSessionId.Length());
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult ChromiumCDMChild::RecvRemoveSession(
+ const uint32_t& aPromiseId, const nsACString& aSessionId) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG("ChromiumCDMChild::RecvRemoveSession(pid=%" PRIu32 ", sid=%s)",
+ aPromiseId, PromiseFlatCString(aSessionId).get());
+ if (mCDM) {
+ mCDM->RemoveSession(aPromiseId, aSessionId.BeginReading(),
+ aSessionId.Length());
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+ChromiumCDMChild::RecvCompleteQueryOutputProtectionStatus(
+ const bool& aSuccess, const uint32_t& aLinkMask,
+ const uint32_t& aProtectionMask) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG(
+ "ChromiumCDMChild::RecvCompleteQueryOutputProtectionStatus(aSuccess=%s, "
+ "aLinkMask=%" PRIu32 ", aProtectionMask=%" PRIu32 ")",
+ aSuccess ? "true" : "false", aLinkMask, aProtectionMask);
+
+ if (mCDM) {
+ cdm::QueryResult queryResult = aSuccess ? cdm::QueryResult::kQuerySucceeded
+ : cdm::QueryResult::kQueryFailed;
+ mCDM->OnQueryOutputProtectionStatus(queryResult, aLinkMask,
+ aProtectionMask);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult ChromiumCDMChild::RecvGetStatusForPolicy(
+ const uint32_t& aPromiseId, const cdm::HdcpVersion& aMinHdcpVersion) {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ GMP_LOG_DEBUG("ChromiumCDMChild::RecvGetStatusForPolicy(pid=%" PRIu32
+ ", MinHdcpVersion=%" PRIu32 ")",
+ aPromiseId, static_cast<uint32_t>(aMinHdcpVersion));
+ if (mCDM) {
+ cdm::Policy policy;
+ policy.min_hdcp_version = aMinHdcpVersion;
+ mCDM->GetStatusForPolicy(aPromiseId, policy);
+ }
+ return IPC_OK();
+}
+
+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() != cdm::EncryptionScheme::kUnencrypted) {
+ MOZ_ASSERT(aBuffer.mEncryptionScheme() == cdm::EncryptionScheme::kCenc ||
+ aBuffer.mEncryptionScheme() == cdm::EncryptionScheme::kCbcs);
+ 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 = aBuffer.mEncryptionScheme();
+ }
+ aInputBuffer.pattern.crypt_byte_block = aBuffer.mCryptByteBlock();
+ aInputBuffer.pattern.skip_byte_block = aBuffer.mSkipByteBlock();
+ 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_DEBUG("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_DEBUG("ChromiumCDMChild::RecvDecrypt() no CDM");
+ Unused << SendDecryptFailed(aId, cdm::kDecryptError);
+ return IPC_OK();
+ }
+ if (aBuffer.mClearBytes().Length() != aBuffer.mCipherBytes().Length()) {
+ GMP_LOG_DEBUG(
+ "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, std::move(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_DEBUG("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().Clone());
+ config.extra_data = extraData.Elements();
+ config.extra_data_size = extraData.Length();
+ config.encryption_scheme = aConfig.mEncryptionScheme();
+ cdm::Status status = mCDM->InitializeVideoDecoder(config);
+ GMP_LOG_DEBUG("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_DEBUG("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_DEBUG("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_DEBUG("ChromiumCDMChild::RecvDecryptAndDecodeFrame() t=%" PRId64 ")",
+ aBuffer.mTimestamp());
+ MOZ_ASSERT(mDecoderInitialized);
+
+ if (!mCDM) {
+ GMP_LOG_DEBUG("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_DEBUG("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_DEBUG("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;
+ }
+ [[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.
+ [[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::VideoPlane::kYPlane),
+ aFrame.Stride(cdm::VideoPlane::kYPlane)};
+ output.mUPlane() = {aFrame.PlaneOffset(cdm::VideoPlane::kUPlane),
+ aFrame.Stride(cdm::VideoPlane::kUPlane)};
+ output.mVPlane() = {aFrame.PlaneOffset(cdm::VideoPlane::kVPlane),
+ aFrame.Stride(cdm::VideoPlane::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, std::move(shmem));
+ } else {
+ MOZ_ASSERT(base->AsArrayBuffer());
+ Unused << SendDecodedData(output, base->AsArrayBuffer()->ExtractBuffer());
+ }
+}
+
+mozilla::ipc::IPCResult ChromiumCDMChild::RecvDrain() {
+ MOZ_ASSERT(IsOnMessageLoopThread());
+ if (!mCDM) {
+ GMP_LOG_DEBUG("ChromiumCDMChild::RecvDrain() no CDM");
+ Unused << SendDrainComplete();
+ return IPC_OK();
+ }
+ WidevineVideoFrame frame;
+ cdm::InputBuffer_2 sample = {};
+ cdm::Status rv = mCDM->DecryptAndDecodeFrame(sample, &frame);
+ GMP_LOG_DEBUG("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_DEBUG("ChromiumCDMChild::RecvDestroy()");
+
+ MOZ_ASSERT(!mDecoderInitialized);
+
+ mInitPromise.RejectIfExists(NS_ERROR_ABORT, __func__);
+
+ 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_DEBUG(
+ "ChromiumCDMChild::RecvGiveBuffer(capacity=%zu"
+ ") bufferSizes={%s} mDecoderInitialized=%d",
+ sz, ToString(mBuffers).get(), mDecoderInitialized);
+}
+
+} // namespace mozilla::gmp