/* -*- 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" 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_9* aCDM) { MOZ_ASSERT(IsOnMessageLoopThread()); mCDM = aCDM; MOZ_ASSERT(mCDM); } 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()) , 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(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(Move(mShmem)); } } void Destroy() override { GMP_LOG("CDMShmemBuffer::Destroy(size=%" PRIu32 ")", Size()); delete this; } uint32_t Capacity() const override { return mShmem.Size(); } uint8_t* Data() override { return mShmem.get(); } 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 mProtocol; uint32_t mSize; mozilla::ipc::Shmem mShmem; CDMShmemBuffer(const CDMShmemBuffer&); void operator=(const CDMShmemBuffer&); }; static nsCString ToString(const nsTArray& aBuffers) { nsCString s; for (const ipc::Shmem& shmem : aBuffers) { if (!s.IsEmpty()) { s.AppendLiteral(","); } s.AppendInt(static_cast(shmem.Size())); } 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::max(); size_t best = invalid; auto wastedSpace = [this, aCapacity](size_t index) { return mBuffers[index].Size() - aCapacity; }; for (size_t i = 0; i < mBuffers.Length(); i++) { if (mBuffers[i].Size() >= 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 self(this); SetTimerOnMainThread(NewGMPTask([self, aContext]() { self->TimerExpired(aContext); }), aDelayMs); } cdm::Time ChromiumCDMChild::GetCurrentWallTime() { return base::Time::Now().ToDoubleT(); } template void ChromiumCDMChild::CallMethod(MethodType aMethod, ParamType&&... aParams) { MOZ_ASSERT(IsOnMessageLoopThread()); // Avoid calling member function after destroy. if (!mDestroyed) { Unused << (this->*aMethod)(Forward(aParams)...); } } template void ChromiumCDMChild::CallOnMessageLoopThread(const char* const aName, MethodType aMethod, ParamType&&... aParams) { if (IsOnMessageLoopThread()) { CallMethod(aMethod, Forward(aParams)...); } else { auto m = &ChromiumCDMChild::CallMethod< decltype(aMethod), const typename RemoveReference::Type&...>; RefPtr t = NewRunnableMethod::Type...>( aName, this, m, aMethod, Forward(aParams)...); mPlugin->GMPMessageLoop()->PostTask(t.forget()); } } // cdm::Host_9 interface void ChromiumCDMChild::OnResolveKeyStatusPromise(uint32_t aPromiseId, cdm::KeyStatus aKeyStatus) { //TODO: The callback of GetStatusForPolicy, will implement it in Bug 1404230. } 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); } // Align with spec, the Exceptions used by CDM to reject promises . // https://w3c.github.io/encrypted-media/#exceptions cdm::Exception ConvertCDMErrorToCDMException(cdm::Error error) { switch (error) { case cdm::kNotSupportedError: return cdm::Exception::kExceptionNotSupportedError; case cdm::kInvalidStateError: return cdm::Exception::kExceptionInvalidStateError; case cdm::kInvalidAccessError: return cdm::Exception::kExceptionTypeError; case cdm::kQuotaExceededError: return cdm::Exception::kExceptionQuotaExceededError; // cdm8 only error case cdm::kUnknownError: case cdm::kClientError: case cdm::kOutputError: break; } return cdm::Exception::kExceptionInvalidStateError; } // cdm::Host_8 only interface void ChromiumCDMChild::OnRejectPromise(uint32_t aPromiseId, cdm::Error aError, uint32_t aSystemCode, const char* aErrorMessage, uint32_t aErrorMessageSize) { OnRejectPromise(aPromiseId, ConvertCDMErrorToCDMException(aError), aSystemCode, aErrorMessage, aErrorMessageSize); } // cdm::Host_9 interface 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(aException), aSystemCode, nsCString(aErrorMessage, aErrorMessageSize)); } // cdm::Host_8 only interface void ChromiumCDMChild::OnSessionMessage(const char* aSessionId, uint32_t aSessionIdSize, cdm::MessageType aMessageType, const char* aMessage, uint32_t aMessageSize, const char* aLegacyDestinationUrl, uint32_t aLegacyDestinationUrlLength) { OnSessionMessage(aSessionId, aSessionIdSize, aMessageType, aMessage, aMessageSize); } // cdm::Host_9 interface 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 message; message.AppendElements(aMessage, aMessageSize); CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnSessionMessage", &ChromiumCDMChild::SendOnSessionMessage, nsCString(aSessionId, aSessionIdSize), static_cast(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 keys; keys.SetCapacity(aKeysInfoCount); for (uint32_t i = 0; i < aKeysInfoCount; i++) { const cdm::KeyInformation& key = aKeysInfo[i]; nsTArray 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)); } void ChromiumCDMChild::OnLegacySessionError(const char* aSessionId, uint32_t aSessionIdLength, cdm::Error aError, uint32_t aSystemCode, const char* aErrorMessage, uint32_t aErrorMessageLength) { GMP_LOG("ChromiumCDMChild::OnLegacySessionError(sid=%s, error=%" PRIu32 " msg='%s')", aSessionId, aError, aErrorMessage); CallOnMessageLoopThread("gmp::ChromiumCDMChild::OnLegacySessionError", &ChromiumCDMChild::SendOnLegacySessionError, nsCString(aSessionId, aSessionIdLength), static_cast(aError), aSystemCode, nsCString(aErrorMessage, aErrorMessageLength)); } cdm::FileIO* ChromiumCDMChild::CreateFileIO(cdm::FileIOClient * aClient) { MOZ_ASSERT(IsOnMessageLoopThread()); GMP_LOG("ChromiumCDMChild::CreateFileIO()"); if (!mPersistentStateAllowed) { return nullptr; } return new WidevineFileIO(aClient); } 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=%d, persistentState=%d)", aAllowDistinctiveIdentifier, aAllowPersistentState); mPersistentStateAllowed = aAllowPersistentState; if (mCDM) { mCDM->Initialize(aAllowDistinctiveIdentifier, aAllowPersistentState); } return IPC_OK(); } mozilla::ipc::IPCResult ChromiumCDMChild::RecvSetServerCertificate(const uint32_t& aPromiseId, nsTArray&& 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&& 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(aSessionType), static_cast(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(aSessionType), aSessionId.get(), aSessionId.Length()); } return IPC_OK(); } mozilla::ipc::IPCResult ChromiumCDMChild::RecvUpdateSession(const uint32_t& aPromiseId, const nsCString& aSessionId, nsTArray&& 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(); } static void InitInputBuffer(const CDMInputBuffer& aBuffer, nsTArray& aSubSamples, cdm::InputBuffer& aInputBuffer) { aInputBuffer.data = aBuffer.mData().get(); aInputBuffer.data_size = aBuffer.mData().Size(); if (aBuffer.mIsEncrypted()) { 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.timestamp = aBuffer.mTimestamp(); } bool ChromiumCDMChild::HasShmemOfSize(size_t aSize) const { for (const ipc::Shmem& shmem : mBuffers) { if (shmem.Size() == 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(); MOZ_ASSERT(HasShmemOfSize(outputShmemSize)); // Ensure we deallocate the shmem used to send input. RefPtr 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() != 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 input; nsTArray 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(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 config; config.codec = static_cast(aConfig.mCodec()); config.profile = static_cast(aConfig.mProfile()); config.format = static_cast(aConfig.mFormat()); config.coded_size = mCodedSize = { aConfig.mImageWidth(), aConfig.mImageHeight() }; nsTArray extraData(aConfig.mExtraData()); config.extra_data = extraData.Elements(); config.extra_data_size = extraData.Length(); 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 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 input; nsTArray 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(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(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 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(Move(aBuffer)); return IPC_OK(); } void ChromiumCDMChild::GiveBuffer(ipc::Shmem&& aBuffer) { MOZ_ASSERT(IsOnMessageLoopThread()); size_t sz = aBuffer.Size(); mBuffers.AppendElement(Move(aBuffer)); GMP_LOG("ChromiumCDMChild::RecvGiveBuffer(capacity=%zu" ") bufferSizes={%s} mDecoderInitialized=%d", sz, ToString(mBuffers).get(), mDecoderInitialized); } } // namespace gmp } // namespace mozilla