diff options
Diffstat (limited to 'dom/media/gmp/widevine-adapter')
-rw-r--r-- | dom/media/gmp/widevine-adapter/WidevineAdapter.cpp | 168 | ||||
-rw-r--r-- | dom/media/gmp/widevine-adapter/WidevineAdapter.h | 59 | ||||
-rw-r--r-- | dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp | 541 | ||||
-rw-r--r-- | dom/media/gmp/widevine-adapter/WidevineDecryptor.h | 134 | ||||
-rw-r--r-- | dom/media/gmp/widevine-adapter/WidevineFileIO.cpp | 97 | ||||
-rw-r--r-- | dom/media/gmp/widevine-adapter/WidevineFileIO.h | 46 | ||||
-rw-r--r-- | dom/media/gmp/widevine-adapter/WidevineUtils.cpp | 95 | ||||
-rw-r--r-- | dom/media/gmp/widevine-adapter/WidevineUtils.h | 69 | ||||
-rw-r--r-- | dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp | 400 | ||||
-rw-r--r-- | dom/media/gmp/widevine-adapter/WidevineVideoDecoder.h | 80 | ||||
-rw-r--r-- | dom/media/gmp/widevine-adapter/WidevineVideoFrame.cpp | 126 | ||||
-rw-r--r-- | dom/media/gmp/widevine-adapter/WidevineVideoFrame.h | 50 | ||||
-rw-r--r-- | dom/media/gmp/widevine-adapter/content_decryption_module.h | 1199 | ||||
-rw-r--r-- | dom/media/gmp/widevine-adapter/moz.build | 25 |
14 files changed, 3089 insertions, 0 deletions
diff --git a/dom/media/gmp/widevine-adapter/WidevineAdapter.cpp b/dom/media/gmp/widevine-adapter/WidevineAdapter.cpp new file mode 100644 index 000000000..74b5c38e8 --- /dev/null +++ b/dom/media/gmp/widevine-adapter/WidevineAdapter.cpp @@ -0,0 +1,168 @@ +/* -*- 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 "WidevineAdapter.h" +#include "content_decryption_module.h" +#include "VideoUtils.h" +#include "WidevineDecryptor.h" +#include "WidevineUtils.h" +#include "WidevineVideoDecoder.h" +#include "gmp-api/gmp-entrypoints.h" +#include "gmp-api/gmp-decryption.h" +#include "gmp-api/gmp-video-codec.h" +#include "gmp-api/gmp-platform.h" + +static const GMPPlatformAPI* sPlatform = nullptr; + +namespace mozilla { + +GMPErr GMPGetCurrentTime(GMPTimestamp* aOutTime) { + return sPlatform->getcurrenttime(aOutTime); +} + +// Call on main thread only. +GMPErr GMPSetTimerOnMainThread(GMPTask* aTask, int64_t aTimeoutMS) { + return sPlatform->settimer(aTask, aTimeoutMS); +} + +GMPErr GMPCreateRecord(const char* aRecordName, + uint32_t aRecordNameSize, + GMPRecord** aOutRecord, + GMPRecordClient* aClient) +{ + return sPlatform->createrecord(aRecordName, aRecordNameSize, aOutRecord, aClient); +} + +void +WidevineAdapter::SetAdaptee(PRLibrary* aLib) +{ + mLib = aLib; +} + +void* GetCdmHost(int aHostInterfaceVersion, void* aUserData) +{ + Log("GetCdmHostFunc(%d, %p)", aHostInterfaceVersion, aUserData); + WidevineDecryptor* decryptor = reinterpret_cast<WidevineDecryptor*>(aUserData); + MOZ_ASSERT(decryptor); + return static_cast<cdm::Host_8*>(decryptor); +} + +#define STRINGIFY(s) _STRINGIFY(s) +#define _STRINGIFY(s) #s + +GMPErr +WidevineAdapter::GMPInit(const GMPPlatformAPI* aPlatformAPI) +{ +#ifdef ENABLE_WIDEVINE_LOG + if (getenv("GMP_LOG_FILE")) { + // Clear log file. + FILE* f = fopen(getenv("GMP_LOG_FILE"), "w"); + if (f) { + fclose(f); + } + } +#endif + + sPlatform = aPlatformAPI; + if (!mLib) { + return GMPGenericErr; + } + + auto init = reinterpret_cast<decltype(::INITIALIZE_CDM_MODULE)*>( + PR_FindFunctionSymbol(mLib, STRINGIFY(INITIALIZE_CDM_MODULE))); + if (!init) { + return GMPGenericErr; + } + + Log(STRINGIFY(INITIALIZE_CDM_MODULE)"()"); + init(); + + return GMPNoErr; +} + +GMPErr +WidevineAdapter::GMPGetAPI(const char* aAPIName, + void* aHostAPI, + void** aPluginAPI, + uint32_t aDecryptorId) +{ + Log("WidevineAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p", + aAPIName, aHostAPI, aPluginAPI, this, aDecryptorId); + if (!strcmp(aAPIName, GMP_API_DECRYPTOR)) { + if (WidevineDecryptor::GetInstance(aDecryptorId)) { + // We only support one CDM instance per PGMPDecryptor. Fail! + Log("WidevineAdapter::GMPGetAPI() Tried to create more than once CDM per IPDL actor! FAIL!"); + return GMPQuotaExceededErr; + } + auto create = reinterpret_cast<decltype(::CreateCdmInstance)*>( + PR_FindFunctionSymbol(mLib, "CreateCdmInstance")); + if (!create) { + Log("WidevineAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p FAILED to find CreateCdmInstance", + aAPIName, aHostAPI, aPluginAPI, this, aDecryptorId); + return GMPGenericErr; + } + + WidevineDecryptor* decryptor = new WidevineDecryptor(); + + auto cdm = reinterpret_cast<cdm::ContentDecryptionModule*>( + create(cdm::ContentDecryptionModule::kVersion, + kEMEKeySystemWidevine.get(), + kEMEKeySystemWidevine.Length(), + &GetCdmHost, + decryptor)); + if (!cdm) { + Log("WidevineAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p FAILED to create cdm", + aAPIName, aHostAPI, aPluginAPI, this, aDecryptorId); + return GMPGenericErr; + } + Log("cdm: 0x%x", cdm); + RefPtr<CDMWrapper> wrapper(new CDMWrapper(cdm, decryptor)); + decryptor->SetCDM(wrapper, aDecryptorId); + *aPluginAPI = decryptor; + + } else if (!strcmp(aAPIName, GMP_API_VIDEO_DECODER)) { + RefPtr<CDMWrapper> wrapper = WidevineDecryptor::GetInstance(aDecryptorId); + if (!wrapper) { + Log("WidevineAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p No cdm for video decoder", + aAPIName, aHostAPI, aPluginAPI, thiss, aDecryptorId); + return GMPGenericErr; + } + *aPluginAPI = new WidevineVideoDecoder(static_cast<GMPVideoHost*>(aHostAPI), + wrapper); + } + return *aPluginAPI ? GMPNoErr : GMPNotImplementedErr; +} + +void +WidevineAdapter::GMPShutdown() +{ + Log("WidevineAdapter::GMPShutdown()"); + + decltype(::DeinitializeCdmModule)* deinit; + deinit = (decltype(deinit))(PR_FindFunctionSymbol(mLib, "DeinitializeCdmModule")); + if (deinit) { + Log("DeinitializeCdmModule()"); + deinit(); + } +} + +void +WidevineAdapter::GMPSetNodeId(const char* aNodeId, uint32_t aLength) +{ + +} + +/* static */ +bool +WidevineAdapter::Supports(int32_t aModuleVersion, + int32_t aInterfaceVersion, + int32_t aHostVersion) +{ + return aModuleVersion == CDM_MODULE_VERSION && + aInterfaceVersion == cdm::ContentDecryptionModule::kVersion && + aHostVersion == cdm::Host_8::kVersion; +} + +} // namespace mozilla diff --git a/dom/media/gmp/widevine-adapter/WidevineAdapter.h b/dom/media/gmp/widevine-adapter/WidevineAdapter.h new file mode 100644 index 000000000..714e041be --- /dev/null +++ b/dom/media/gmp/widevine-adapter/WidevineAdapter.h @@ -0,0 +1,59 @@ +/* -*- 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/. */ + +#ifndef WidevineAdapter_h_ +#define WidevineAdapter_h_ + +#include "GMPLoader.h" +#include "prlink.h" +#include "GMPUtils.h" + +struct GMPPlatformAPI; + +namespace mozilla { + +class WidevineAdapter : public gmp::GMPAdapter { +public: + + void SetAdaptee(PRLibrary* aLib) override; + + // These are called in place of the corresponding GMP API functions. + GMPErr GMPInit(const GMPPlatformAPI* aPlatformAPI) override; + GMPErr GMPGetAPI(const char* aAPIName, + void* aHostAPI, + void** aPluginAPI, + uint32_t aDecryptorId) override; + void GMPShutdown() override; + void GMPSetNodeId(const char* aNodeId, uint32_t aLength) override; + + static bool Supports(int32_t aModuleVersion, + int32_t aInterfaceVersion, + int32_t aHostVersion); + +private: + PRLibrary* mLib = nullptr; +}; + +GMPErr GMPCreateThread(GMPThread** aThread); +GMPErr GMPRunOnMainThread(GMPTask* aTask); +GMPErr GMPCreateMutex(GMPMutex** aMutex); + +// Call on main thread only. +GMPErr GMPCreateRecord(const char* aRecordName, + uint32_t aRecordNameSize, + GMPRecord** aOutRecord, + GMPRecordClient* aClient); + +// Call on main thread only. +GMPErr GMPSetTimerOnMainThread(GMPTask* aTask, int64_t aTimeoutMS); + +GMPErr GMPGetCurrentTime(GMPTimestamp* aOutTime); + +GMPErr GMPCreateRecordIterator(RecvGMPRecordIteratorPtr aRecvIteratorFunc, + void* aUserArg); + +} // namespace mozilla + +#endif // WidevineAdapter_h_ diff --git a/dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp b/dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp new file mode 100644 index 000000000..149fa1701 --- /dev/null +++ b/dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp @@ -0,0 +1,541 @@ +/* -*- 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 "WidevineDecryptor.h" + +#include "WidevineAdapter.h" +#include "WidevineUtils.h" +#include "WidevineFileIO.h" +#include <mozilla/SizePrintfMacros.h> +#include <stdarg.h> +#include "base/time.h" + +using namespace cdm; +using namespace std; + +namespace mozilla { + +static map<uint32_t, RefPtr<CDMWrapper>> sDecryptors; + +/* static */ +RefPtr<CDMWrapper> +WidevineDecryptor::GetInstance(uint32_t aInstanceId) +{ + auto itr = sDecryptors.find(aInstanceId); + if (itr != sDecryptors.end()) { + return itr->second; + } + return nullptr; +} + + +WidevineDecryptor::WidevineDecryptor() + : mCallback(nullptr) +{ + Log("WidevineDecryptor created this=%p", this); + AddRef(); // Released in DecryptingComplete(). +} + +WidevineDecryptor::~WidevineDecryptor() +{ + Log("WidevineDecryptor destroyed this=%p", this); +} + +void +WidevineDecryptor::SetCDM(RefPtr<CDMWrapper> aCDM, uint32_t aInstanceId) +{ + mCDM = aCDM; + mInstanceId = aInstanceId; + sDecryptors[mInstanceId] = aCDM; +} + +void +WidevineDecryptor::Init(GMPDecryptorCallback* aCallback, + bool aDistinctiveIdentifierRequired, + bool aPersistentStateRequired) +{ + Log("WidevineDecryptor::Init() this=%p distinctiveId=%d persistentState=%d", + this, aDistinctiveIdentifierRequired, aPersistentStateRequired); + MOZ_ASSERT(aCallback); + mCallback = aCallback; + MOZ_ASSERT(mCDM); + mDistinctiveIdentifierRequired = aDistinctiveIdentifierRequired; + mPersistentStateRequired = aPersistentStateRequired; + if (CDM()) { + CDM()->Initialize(aDistinctiveIdentifierRequired, + aPersistentStateRequired); + } +} + +static SessionType +ToCDMSessionType(GMPSessionType aSessionType) +{ + switch (aSessionType) { + case kGMPTemporySession: return kTemporary; + case kGMPPersistentSession: return kPersistentLicense; + case kGMPSessionInvalid: return kTemporary; + // TODO: kPersistentKeyRelease + } + MOZ_ASSERT(false); // Not supposed to get here. + return kTemporary; +} + +void +WidevineDecryptor::CreateSession(uint32_t aCreateSessionToken, + uint32_t aPromiseId, + const char* aInitDataType, + uint32_t aInitDataTypeSize, + const uint8_t* aInitData, + uint32_t aInitDataSize, + GMPSessionType aSessionType) +{ + Log("Decryptor::CreateSession(token=%d, pid=%d)", aCreateSessionToken, aPromiseId); + InitDataType initDataType; + if (!strcmp(aInitDataType, "cenc")) { + initDataType = kCenc; + } else if (!strcmp(aInitDataType, "webm")) { + initDataType = kWebM; + } else if (!strcmp(aInitDataType, "keyids")) { + initDataType = kKeyIds; + } else { + // Invalid init data type + const char* errorMsg = "Invalid init data type when creating session."; + OnRejectPromise(aPromiseId, kNotSupportedError, 0, errorMsg, sizeof(errorMsg)); + return; + } + mPromiseIdToNewSessionTokens[aPromiseId] = aCreateSessionToken; + CDM()->CreateSessionAndGenerateRequest(aPromiseId, + ToCDMSessionType(aSessionType), + initDataType, + aInitData, aInitDataSize); +} + +void +WidevineDecryptor::LoadSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength) +{ + Log("Decryptor::LoadSession(pid=%d, %s)", aPromiseId, aSessionId); + // TODO: session type?? + CDM()->LoadSession(aPromiseId, kPersistentLicense, aSessionId, aSessionIdLength); +} + +void +WidevineDecryptor::UpdateSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength, + const uint8_t* aResponse, + uint32_t aResponseSize) +{ + Log("Decryptor::UpdateSession(pid=%d, session=%s)", aPromiseId, aSessionId); + CDM()->UpdateSession(aPromiseId, aSessionId, aSessionIdLength, aResponse, aResponseSize); +} + +void +WidevineDecryptor::CloseSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength) +{ + Log("Decryptor::CloseSession(pid=%d, session=%s)", aPromiseId, aSessionId); + CDM()->CloseSession(aPromiseId, aSessionId, aSessionIdLength); +} + +void +WidevineDecryptor::RemoveSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength) +{ + Log("Decryptor::RemoveSession(%s)", aSessionId); + CDM()->RemoveSession(aPromiseId, aSessionId, aSessionIdLength); +} + +void +WidevineDecryptor::SetServerCertificate(uint32_t aPromiseId, + const uint8_t* aServerCert, + uint32_t aServerCertSize) +{ + Log("Decryptor::SetServerCertificate()"); + CDM()->SetServerCertificate(aPromiseId, aServerCert, aServerCertSize); +} + +class WidevineDecryptedBlock : public cdm::DecryptedBlock { +public: + + WidevineDecryptedBlock() + : mBuffer(nullptr) + , mTimestamp(0) + { + } + + ~WidevineDecryptedBlock() { + if (mBuffer) { + mBuffer->Destroy(); + mBuffer = nullptr; + } + } + + void SetDecryptedBuffer(cdm::Buffer* aBuffer) override { + mBuffer = aBuffer; + } + + cdm::Buffer* DecryptedBuffer() override { + return mBuffer; + } + + void SetTimestamp(int64_t aTimestamp) override { + mTimestamp = aTimestamp; + } + + int64_t Timestamp() const override { + return mTimestamp; + } + +private: + cdm::Buffer* mBuffer; + int64_t mTimestamp; +}; + +void +WidevineDecryptor::Decrypt(GMPBuffer* aBuffer, + GMPEncryptedBufferMetadata* aMetadata) +{ + if (!mCallback) { + Log("WidevineDecryptor::Decrypt() this=%p FAIL; !mCallback", this); + return; + } + const GMPEncryptedBufferMetadata* crypto = aMetadata; + InputBuffer sample; + nsTArray<SubsampleEntry> subsamples; + InitInputBuffer(crypto, aBuffer->Id(), aBuffer->Data(), aBuffer->Size(), sample, subsamples); + WidevineDecryptedBlock decrypted; + Status rv = CDM()->Decrypt(sample, &decrypted); + Log("Decryptor::Decrypt(timestamp=%lld) rv=%d sz=%d", + sample.timestamp, rv, decrypted.DecryptedBuffer()->Size()); + if (rv == kSuccess) { + aBuffer->Resize(decrypted.DecryptedBuffer()->Size()); + memcpy(aBuffer->Data(), + decrypted.DecryptedBuffer()->Data(), + decrypted.DecryptedBuffer()->Size()); + } + mCallback->Decrypted(aBuffer, ToGMPErr(rv)); +} + +void +WidevineDecryptor::DecryptingComplete() +{ + Log("WidevineDecryptor::DecryptingComplete() this=%p", this); + // Drop our references to the CDMWrapper. When any other references + // held elsewhere are dropped (for example references held by a + // WidevineVideoDecoder, or a runnable), the CDMWrapper destroys + // the CDM. + mCDM = nullptr; + sDecryptors.erase(mInstanceId); + mCallback = nullptr; + Release(); +} + +class WidevineBuffer : public cdm::Buffer { +public: + explicit WidevineBuffer(size_t aSize) { + Log("WidevineBuffer(size=" PRIuSIZE ") created", aSize); + mBuffer.SetLength(aSize); + } + ~WidevineBuffer() { + Log("WidevineBuffer(size=" PRIuSIZE ") destroyed", Size()); + } + void Destroy() override { delete this; } + uint32_t Capacity() const override { return mBuffer.Length(); }; + uint8_t* Data() override { return mBuffer.Elements(); } + void SetSize(uint32_t aSize) override { mBuffer.SetLength(aSize); } + uint32_t Size() const override { return mBuffer.Length(); } + +private: + WidevineBuffer(const WidevineBuffer&); + void operator=(const WidevineBuffer&); + + nsTArray<uint8_t> mBuffer; +}; + +Buffer* +WidevineDecryptor::Allocate(uint32_t aCapacity) +{ + Log("Decryptor::Allocate(capacity=%u)", aCapacity); + return new WidevineBuffer(aCapacity); +} + +class TimerTask : public GMPTask { +public: + TimerTask(WidevineDecryptor* aDecryptor, + RefPtr<CDMWrapper> aCDM, + void* aContext) + : mDecryptor(aDecryptor) + , mCDM(aCDM) + , mContext(aContext) + { + } + ~TimerTask() override {} + void Run() override { + mCDM->GetCDM()->TimerExpired(mContext); + } + void Destroy() override { delete this; } +private: + RefPtr<WidevineDecryptor> mDecryptor; + RefPtr<CDMWrapper> mCDM; + void* mContext; +}; + +void +WidevineDecryptor::SetTimer(int64_t aDelayMs, void* aContext) +{ + Log("Decryptor::SetTimer(delay_ms=%lld, context=0x%x)", aDelayMs, aContext); + if (mCDM) { + GMPSetTimerOnMainThread(new TimerTask(this, mCDM, aContext), aDelayMs); + } +} + +Time +WidevineDecryptor::GetCurrentWallTime() +{ + return base::Time::Now().ToDoubleT(); +} + +void +WidevineDecryptor::OnResolveNewSessionPromise(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdSize) +{ + if (!mCallback) { + Log("Decryptor::OnResolveNewSessionPromise(aPromiseId=0x%d) FAIL; !mCallback", aPromiseId); + return; + } + Log("Decryptor::OnResolveNewSessionPromise(aPromiseId=0x%d)", aPromiseId); + auto iter = mPromiseIdToNewSessionTokens.find(aPromiseId); + if (iter == mPromiseIdToNewSessionTokens.end()) { + Log("FAIL: Decryptor::OnResolveNewSessionPromise(aPromiseId=%d) unknown aPromiseId", aPromiseId); + return; + } + mCallback->SetSessionId(iter->second, aSessionId, aSessionIdSize); + mCallback->ResolvePromise(aPromiseId); + mPromiseIdToNewSessionTokens.erase(iter); +} + +void +WidevineDecryptor::OnResolvePromise(uint32_t aPromiseId) +{ + if (!mCallback) { + Log("Decryptor::OnResolvePromise(aPromiseId=0x%d) FAIL; !mCallback", aPromiseId); + return; + } + Log("Decryptor::OnResolvePromise(aPromiseId=%d)", aPromiseId); + mCallback->ResolvePromise(aPromiseId); +} + +static GMPDOMException +ToGMPDOMException(cdm::Error aError) +{ + switch (aError) { + case kNotSupportedError: return kGMPNotSupportedError; + case kInvalidStateError: return kGMPInvalidStateError; + case kInvalidAccessError: + // Note: Chrome converts kInvalidAccessError to TypeError, since the + // Chromium CDM API doesn't have a type error enum value. The EME spec + // requires TypeError in some places, so we do the same conversion. + // See bug 1313202. + return kGMPTypeError; + case kQuotaExceededError: return kGMPQuotaExceededError; + case kUnknownError: return kGMPInvalidModificationError; // Note: Unique placeholder. + case kClientError: return kGMPAbortError; // Note: Unique placeholder. + case kOutputError: return kGMPSecurityError; // Note: Unique placeholder. + }; + return kGMPTimeoutError; // Note: Unique placeholder. +} + +void +WidevineDecryptor::OnRejectPromise(uint32_t aPromiseId, + Error aError, + uint32_t aSystemCode, + const char* aErrorMessage, + uint32_t aErrorMessageSize) +{ + if (!mCallback) { + Log("Decryptor::OnRejectPromise(aPromiseId=%d, err=%d, sysCode=%u, msg=%s) FAIL; !mCallback", + aPromiseId, (int)aError, aSystemCode, aErrorMessage); + return; + } + Log("Decryptor::OnRejectPromise(aPromiseId=%d, err=%d, sysCode=%u, msg=%s)", + aPromiseId, (int)aError, aSystemCode, aErrorMessage); + mCallback->RejectPromise(aPromiseId, + ToGMPDOMException(aError), + !aErrorMessageSize ? "" : aErrorMessage, + aErrorMessageSize); +} + +static GMPSessionMessageType +ToGMPMessageType(MessageType message_type) +{ + switch (message_type) { + case kLicenseRequest: return kGMPLicenseRequest; + case kLicenseRenewal: return kGMPLicenseRenewal; + case kLicenseRelease: return kGMPLicenseRelease; + } + return kGMPMessageInvalid; +} + +void +WidevineDecryptor::OnSessionMessage(const char* aSessionId, + uint32_t aSessionIdSize, + MessageType aMessageType, + const char* aMessage, + uint32_t aMessageSize, + const char* aLegacyDestinationUrl, + uint32_t aLegacyDestinationUrlLength) +{ + if (!mCallback) { + Log("Decryptor::OnSessionMessage() FAIL; !mCallback"); + return; + } + Log("Decryptor::OnSessionMessage()"); + mCallback->SessionMessage(aSessionId, + aSessionIdSize, + ToGMPMessageType(aMessageType), + reinterpret_cast<const uint8_t*>(aMessage), + aMessageSize); +} + +static GMPMediaKeyStatus +ToGMPKeyStatus(KeyStatus aStatus) +{ + switch (aStatus) { + case kUsable: return kGMPUsable; + case kInternalError: return kGMPInternalError; + case kExpired: return kGMPExpired; + case kOutputRestricted: return kGMPOutputRestricted; + case kOutputDownscaled: return kGMPOutputDownscaled; + case kStatusPending: return kGMPStatusPending; + case kReleased: return kGMPReleased; + } + return kGMPUnknown; +} + +void +WidevineDecryptor::OnSessionKeysChange(const char* aSessionId, + uint32_t aSessionIdSize, + bool aHasAdditionalUsableKey, + const KeyInformation* aKeysInfo, + uint32_t aKeysInfoCount) +{ + if (!mCallback) { + Log("Decryptor::OnSessionKeysChange() FAIL; !mCallback"); + return; + } + Log("Decryptor::OnSessionKeysChange()"); + + nsTArray<GMPMediaKeyInfo> key_infos; + for (uint32_t i = 0; i < aKeysInfoCount; i++) { + key_infos.AppendElement(GMPMediaKeyInfo(aKeysInfo[i].key_id, + aKeysInfo[i].key_id_size, + ToGMPKeyStatus(aKeysInfo[i].status))); + } + mCallback->BatchedKeyStatusChanged(aSessionId, aSessionIdSize, + key_infos.Elements(), key_infos.Length()); +} + +static GMPTimestamp +ToGMPTime(Time aCDMTime) +{ + return static_cast<GMPTimestamp>(aCDMTime * 1000); +} + +void +WidevineDecryptor::OnExpirationChange(const char* aSessionId, + uint32_t aSessionIdSize, + Time aNewExpiryTime) +{ + if (!mCallback) { + Log("Decryptor::OnExpirationChange(sid=%s) t=%lf FAIL; !mCallback", + aSessionId, aNewExpiryTime); + return; + } + Log("Decryptor::OnExpirationChange(sid=%s) t=%lf", aSessionId, aNewExpiryTime); + GMPTimestamp expiry = ToGMPTime(aNewExpiryTime); + if (aNewExpiryTime == 0) { + return; + } + mCallback->ExpirationChange(aSessionId, aSessionIdSize, expiry); +} + +void +WidevineDecryptor::OnSessionClosed(const char* aSessionId, + uint32_t aSessionIdSize) +{ + if (!mCallback) { + Log("Decryptor::OnSessionClosed(sid=%s) FAIL; !mCallback", aSessionId); + return; + } + Log("Decryptor::OnSessionClosed(sid=%s)", aSessionId); + mCallback->SessionClosed(aSessionId, aSessionIdSize); +} + +void +WidevineDecryptor::OnLegacySessionError(const char* aSessionId, + uint32_t aSessionIdLength, + Error aError, + uint32_t aSystemCode, + const char* aErrorMessage, + uint32_t aErrorMessageLength) +{ + if (!mCallback) { + Log("Decryptor::OnLegacySessionError(sid=%s, error=%d) FAIL; !mCallback", + aSessionId, (int)aError); + return; + } + Log("Decryptor::OnLegacySessionError(sid=%s, error=%d)", aSessionId, (int)aError); + mCallback->SessionError(aSessionId, + aSessionIdLength, + ToGMPDOMException(aError), + aSystemCode, + aErrorMessage, + aErrorMessageLength); +} + +void +WidevineDecryptor::SendPlatformChallenge(const char* aServiceId, + uint32_t aServiceIdSize, + const char* aChallenge, + uint32_t aChallengeSize) +{ + Log("Decryptor::SendPlatformChallenge(service_id=%s)", aServiceId); +} + +void +WidevineDecryptor::EnableOutputProtection(uint32_t aDesiredProtectionMask) +{ + Log("Decryptor::EnableOutputProtection(mask=0x%x)", aDesiredProtectionMask); +} + +void +WidevineDecryptor::QueryOutputProtectionStatus() +{ + Log("Decryptor::QueryOutputProtectionStatus()"); +} + +void +WidevineDecryptor::OnDeferredInitializationDone(StreamType aStreamType, + Status aDecoderStatus) +{ + Log("Decryptor::OnDeferredInitializationDone()"); +} + +FileIO* +WidevineDecryptor::CreateFileIO(FileIOClient* aClient) +{ + Log("Decryptor::CreateFileIO()"); + if (!mPersistentStateRequired) { + return nullptr; + } + return new WidevineFileIO(aClient); +} + +} // namespace mozilla diff --git a/dom/media/gmp/widevine-adapter/WidevineDecryptor.h b/dom/media/gmp/widevine-adapter/WidevineDecryptor.h new file mode 100644 index 000000000..d5185192b --- /dev/null +++ b/dom/media/gmp/widevine-adapter/WidevineDecryptor.h @@ -0,0 +1,134 @@ +/* -*- 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/. */ + +#ifndef WidevineDecryptor_h_ +#define WidevineDecryptor_h_ + +#include "stddef.h" +#include "content_decryption_module.h" +#include "gmp-api/gmp-decryption.h" +#include "mozilla/RefPtr.h" +#include "WidevineUtils.h" +#include <map> + +namespace mozilla { + +class WidevineDecryptor : public GMPDecryptor + , public cdm::Host_8 +{ +public: + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WidevineDecryptor) + + WidevineDecryptor(); + + void SetCDM(RefPtr<CDMWrapper> aCDM, uint32_t aDecryptorId); + + static RefPtr<CDMWrapper> GetInstance(uint32_t aDecryptorId); + + // GMPDecryptor + void Init(GMPDecryptorCallback* aCallback, + bool aDistinctiveIdentifierRequired, + bool aPersistentStateRequired) override; + + void CreateSession(uint32_t aCreateSessionToken, + uint32_t aPromiseId, + const char* aInitDataType, + uint32_t aInitDataTypeSize, + const uint8_t* aInitData, + uint32_t aInitDataSize, + GMPSessionType aSessionType) override; + + void LoadSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength) override; + + void UpdateSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength, + const uint8_t* aResponse, + uint32_t aResponseSize) override; + + void CloseSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength) override; + + void RemoveSession(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdLength) override; + + void SetServerCertificate(uint32_t aPromiseId, + const uint8_t* aServerCert, + uint32_t aServerCertSize) override; + + void Decrypt(GMPBuffer* aBuffer, + GMPEncryptedBufferMetadata* aMetadata) override; + + void DecryptingComplete() override; + + + // cdm::Host_8 + cdm::Buffer* Allocate(uint32_t aCapacity) override; + void SetTimer(int64_t aDelayMs, void* aContext) override; + cdm::Time GetCurrentWallTime() override; + void OnResolveNewSessionPromise(uint32_t aPromiseId, + const char* aSessionId, + uint32_t aSessionIdSize) override; + void OnResolvePromise(uint32_t aPromiseId) override; + void OnRejectPromise(uint32_t aPromiseId, + cdm::Error aError, + uint32_t aSystemCode, + const char* aErrorMessage, + uint32_t aErrorMessageSize) override; + void OnSessionMessage(const char* aSessionId, + uint32_t aSessionIdSize, + cdm::MessageType aMessageType, + const char* aMessage, + uint32_t aMessageSize, + const char* aLegacyDestinationUrl, + uint32_t aLegacyDestinationUrlLength) override; + void OnSessionKeysChange(const char* aSessionId, + uint32_t aSessionIdSize, + bool aHasAdditionalUsableKey, + const cdm::KeyInformation* aKeysInfo, + uint32_t aKeysInfoCount) override; + void OnExpirationChange(const char* aSessionId, + uint32_t aSessionIdSize, + cdm::Time aNewExpiryTime) override; + void OnSessionClosed(const char* aSessionId, + uint32_t aSessionIdSize) override; + void OnLegacySessionError(const char* aSessionId, + uint32_t aSessionId_length, + cdm::Error aError, + uint32_t aSystemCode, + const char* aErrorMessage, + uint32_t aErrorMessageLength) override; + void SendPlatformChallenge(const char* aServiceId, + uint32_t aServiceIdSize, + const char* aChallenge, + uint32_t aChallengeSize) override; + void EnableOutputProtection(uint32_t aDesiredProtectionMask) override; + void QueryOutputProtectionStatus() override; + void OnDeferredInitializationDone(cdm::StreamType aStreamType, + cdm::Status aDecoderStatus) override; + cdm::FileIO* CreateFileIO(cdm::FileIOClient* aClient) override; + + GMPDecryptorCallback* Callback() const { return mCallback; } + RefPtr<CDMWrapper> GetCDMWrapper() const { return mCDM; } +private: + ~WidevineDecryptor(); + RefPtr<CDMWrapper> mCDM; + cdm::ContentDecryptionModule_8* CDM() { return mCDM->GetCDM(); } + + GMPDecryptorCallback* mCallback; + std::map<uint32_t, uint32_t> mPromiseIdToNewSessionTokens; + bool mDistinctiveIdentifierRequired = false; + bool mPersistentStateRequired = false; + uint32_t mInstanceId = 0; +}; + +} // namespace mozilla + +#endif // WidevineDecryptor_h_ diff --git a/dom/media/gmp/widevine-adapter/WidevineFileIO.cpp b/dom/media/gmp/widevine-adapter/WidevineFileIO.cpp new file mode 100644 index 000000000..b5fb1d705 --- /dev/null +++ b/dom/media/gmp/widevine-adapter/WidevineFileIO.cpp @@ -0,0 +1,97 @@ +#include "WidevineFileIO.h" +#include "WidevineUtils.h" +#include "WidevineAdapter.h" + +using namespace cdm; + +namespace mozilla { + +void +WidevineFileIO::Open(const char* aFilename, uint32_t aFilenameLength) +{ + mName = std::string(aFilename, aFilename + aFilenameLength); + GMPRecord* record = nullptr; + GMPErr err = GMPCreateRecord(aFilename, aFilenameLength, &record, static_cast<GMPRecordClient*>(this)); + if (GMP_FAILED(err)) { + Log("WidevineFileIO::Open() '%s' GMPCreateRecord failed", mName.c_str()); + mClient->OnOpenComplete(FileIOClient::kError); + return; + } + if (GMP_FAILED(record->Open())) { + Log("WidevineFileIO::Open() '%s' record open failed", mName.c_str()); + mClient->OnOpenComplete(FileIOClient::kError); + return; + } + + Log("WidevineFileIO::Open() '%s'", mName.c_str()); + mRecord = record; +} + +void +WidevineFileIO::Read() +{ + if (!mRecord) { + Log("WidevineFileIO::Read() '%s' used uninitialized!", mName.c_str()); + mClient->OnReadComplete(FileIOClient::kError, nullptr, 0); + return; + } + Log("WidevineFileIO::Read() '%s'", mName.c_str()); + mRecord->Read(); +} + +void +WidevineFileIO::Write(const uint8_t* aData, uint32_t aDataSize) +{ + if (!mRecord) { + Log("WidevineFileIO::Write() '%s' used uninitialized!", mName.c_str()); + mClient->OnWriteComplete(FileIOClient::kError); + return; + } + mRecord->Write(aData, aDataSize); +} + +void +WidevineFileIO::Close() +{ + Log("WidevineFileIO::Close() '%s'", mName.c_str()); + if (mRecord) { + mRecord->Close(); + mRecord = nullptr; + } + delete this; +} + +static FileIOClient::Status +GMPToWidevineFileStatus(GMPErr aStatus) +{ + switch (aStatus) { + case GMPRecordInUse: return FileIOClient::kInUse; + case GMPNoErr: return FileIOClient::kSuccess; + default: return FileIOClient::kError; + } +} + +void +WidevineFileIO::OpenComplete(GMPErr aStatus) +{ + Log("WidevineFileIO::OpenComplete() '%s' status=%d", mName.c_str(), aStatus); + mClient->OnOpenComplete(GMPToWidevineFileStatus(aStatus)); +} + +void +WidevineFileIO::ReadComplete(GMPErr aStatus, + const uint8_t* aData, + uint32_t aDataSize) +{ + Log("WidevineFileIO::OnReadComplete() '%s' status=%d", mName.c_str(), aStatus); + mClient->OnReadComplete(GMPToWidevineFileStatus(aStatus), aData, aDataSize); +} + +void +WidevineFileIO::WriteComplete(GMPErr aStatus) +{ + Log("WidevineFileIO::WriteComplete() '%s' status=%d", mName.c_str(), aStatus); + mClient->OnWriteComplete(GMPToWidevineFileStatus(aStatus)); +} + +} // namespace mozilla diff --git a/dom/media/gmp/widevine-adapter/WidevineFileIO.h b/dom/media/gmp/widevine-adapter/WidevineFileIO.h new file mode 100644 index 000000000..63003d9b6 --- /dev/null +++ b/dom/media/gmp/widevine-adapter/WidevineFileIO.h @@ -0,0 +1,46 @@ +/* -*- 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/. */ + +#ifndef WidevineFileIO_h_ +#define WidevineFileIO_h_ + +#include <stddef.h> +#include "content_decryption_module.h" +#include "gmp-api/gmp-storage.h" +#include <string> + +namespace mozilla { + +class WidevineFileIO : public cdm::FileIO + , public GMPRecordClient +{ +public: + explicit WidevineFileIO(cdm::FileIOClient* aClient) + : mClient(aClient) + , mRecord(nullptr) + {} + + // cdm::FileIO + void Open(const char* aFilename, uint32_t aFilenameLength) override; + void Read() override; + void Write(const uint8_t* aData, uint32_t aDataSize) override; + void Close() override; + + // GMPRecordClient + void OpenComplete(GMPErr aStatus) override; + void ReadComplete(GMPErr aStatus, + const uint8_t* aData, + uint32_t aDataSize) override; + void WriteComplete(GMPErr aStatus) override; + +private: + cdm::FileIOClient* mClient; + GMPRecord* mRecord; + std::string mName; +}; + +} // namespace mozilla + +#endif // WidevineFileIO_h_
\ No newline at end of file diff --git a/dom/media/gmp/widevine-adapter/WidevineUtils.cpp b/dom/media/gmp/widevine-adapter/WidevineUtils.cpp new file mode 100644 index 000000000..925dfe1a1 --- /dev/null +++ b/dom/media/gmp/widevine-adapter/WidevineUtils.cpp @@ -0,0 +1,95 @@ +/* -*- 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 "WidevineUtils.h" +#include "WidevineDecryptor.h" + +#include "gmp-api/gmp-errors.h" +#include <stdarg.h> +#include <stdio.h> + +namespace mozilla { + +#ifdef ENABLE_WIDEVINE_LOG +void +Log(const char* aFormat, ...) +{ + va_list ap; + va_start(ap, aFormat); + const size_t len = 1024; + char buf[len]; + vsnprintf(buf, len, aFormat, ap); + va_end(ap); + if (getenv("GMP_LOG_FILE")) { + FILE* f = fopen(getenv("GMP_LOG_FILE"), "a"); + if (f) { + fprintf(f, "%s\n", buf); + fflush(f); + fclose(f); + f = nullptr; + } + } else { + printf("LOG: %s\n", buf); + } +} +#endif // ENABLE_WIDEVINE_LOG + +GMPErr +ToGMPErr(cdm::Status aStatus) +{ + switch (aStatus) { + case cdm::kSuccess: return GMPNoErr; + case cdm::kNeedMoreData: return GMPGenericErr; + case cdm::kNoKey: return GMPNoKeyErr; + case cdm::kSessionError: return GMPGenericErr; + case cdm::kDecryptError: return GMPCryptoErr; + case cdm::kDecodeError: return GMPDecodeErr; + case cdm::kDeferredInitialization: return GMPGenericErr; + default: return GMPGenericErr; + } +} + +void InitInputBuffer(const GMPEncryptedBufferMetadata* aCrypto, + int64_t aTimestamp, + const uint8_t* aData, + size_t aDataSize, + cdm::InputBuffer &aInputBuffer, + nsTArray<cdm::SubsampleEntry> &aSubsamples) +{ + if (aCrypto) { + aInputBuffer.key_id = aCrypto->KeyId(); + aInputBuffer.key_id_size = aCrypto->KeyIdSize(); + aInputBuffer.iv = aCrypto->IV(); + aInputBuffer.iv_size = aCrypto->IVSize(); + aInputBuffer.num_subsamples = aCrypto->NumSubsamples(); + aSubsamples.SetCapacity(aInputBuffer.num_subsamples); + const uint16_t* clear = aCrypto->ClearBytes(); + const uint32_t* cipher = aCrypto->CipherBytes(); + for (size_t i = 0; i < aCrypto->NumSubsamples(); i++) { + aSubsamples.AppendElement(cdm::SubsampleEntry(clear[i], cipher[i])); + } + } + aInputBuffer.data = aData; + aInputBuffer.data_size = aDataSize; + aInputBuffer.subsamples = aSubsamples.Elements(); + aInputBuffer.timestamp = aTimestamp; +} + +CDMWrapper::CDMWrapper(cdm::ContentDecryptionModule_8* aCDM, + WidevineDecryptor* aDecryptor) + : mCDM(aCDM) + , mDecryptor(aDecryptor) +{ + MOZ_ASSERT(mCDM); +} + +CDMWrapper::~CDMWrapper() +{ + Log("CDMWrapper destroying CDM=%p", mCDM); + mCDM->Destroy(); + mCDM = nullptr; +} + +} // namespace mozilla diff --git a/dom/media/gmp/widevine-adapter/WidevineUtils.h b/dom/media/gmp/widevine-adapter/WidevineUtils.h new file mode 100644 index 000000000..57c004a87 --- /dev/null +++ b/dom/media/gmp/widevine-adapter/WidevineUtils.h @@ -0,0 +1,69 @@ +/* -*- 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/. */ + +#ifndef WidevineUtils_h_ +#define WidevineUtils_h_ + +#include "stddef.h" +#include "content_decryption_module.h" +#include "gmp-api/gmp-decryption.h" +#include "gmp-api/gmp-platform.h" +#include "nsISupportsImpl.h" +#include "nsTArray.h" + +namespace mozilla { + +// Uncomment for logging... +//#define ENABLE_WIDEVINE_LOG 1 +#ifdef ENABLE_WIDEVINE_LOG +void +Log(const char* aFormat, ...); +#else +#define Log(...) +#endif // ENABLE_WIDEVINE_LOG + + +#define ENSURE_TRUE(condition, rv) { \ + if (!(condition)) {\ + Log("ENSURE_TRUE FAILED %s:%d", __FILE__, __LINE__); \ + return rv; \ + } \ +} \ + +#define ENSURE_GMP_SUCCESS(err, rv) { \ + if (GMP_FAILED(err)) {\ + Log("ENSURE_GMP_SUCCESS FAILED %s:%d", __FILE__, __LINE__); \ + return rv; \ + } \ +} \ + +GMPErr +ToGMPErr(cdm::Status aStatus); + +class WidevineDecryptor; + +class CDMWrapper { +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CDMWrapper) + + explicit CDMWrapper(cdm::ContentDecryptionModule_8* aCDM, + WidevineDecryptor* aDecryptor); + cdm::ContentDecryptionModule_8* GetCDM() const { return mCDM; } +private: + ~CDMWrapper(); + cdm::ContentDecryptionModule_8* mCDM; + RefPtr<WidevineDecryptor> mDecryptor; +}; + +void InitInputBuffer(const GMPEncryptedBufferMetadata* aCrypto, + int64_t aTimestamp, + const uint8_t* aData, + size_t aDataSize, + cdm::InputBuffer &aInputBuffer, + nsTArray<cdm::SubsampleEntry> &aSubsamples); + +} // namespace mozilla + +#endif // WidevineUtils_h_ diff --git a/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp b/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp new file mode 100644 index 000000000..70d2fd8e0 --- /dev/null +++ b/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp @@ -0,0 +1,400 @@ +/* -*- 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 "WidevineVideoDecoder.h" + +#include "mp4_demuxer/AnnexB.h" +#include "WidevineUtils.h" +#include "WidevineVideoFrame.h" +#include "mozilla/Move.h" + +using namespace cdm; + +namespace mozilla { + +WidevineVideoDecoder::WidevineVideoDecoder(GMPVideoHost* aVideoHost, + RefPtr<CDMWrapper> aCDMWrapper) + : mVideoHost(aVideoHost) + , mCDMWrapper(Move(aCDMWrapper)) + , mExtraData(new MediaByteBuffer()) + , mSentInput(false) + , mCodecType(kGMPVideoCodecInvalid) + , mReturnOutputCallDepth(0) + , mDrainPending(false) + , mResetInProgress(false) +{ + // Expect to start with a CDM wrapper, will release it in DecodingComplete(). + MOZ_ASSERT(mCDMWrapper); + Log("WidevineVideoDecoder created this=%p", this); + + // Corresponding Release is in DecodingComplete(). + AddRef(); +} + +WidevineVideoDecoder::~WidevineVideoDecoder() +{ + Log("WidevineVideoDecoder destroyed this=%p", this); +} + +static +VideoDecoderConfig::VideoCodecProfile +ToCDMH264Profile(uint8_t aProfile) +{ + switch (aProfile) { + case 66: return VideoDecoderConfig::kH264ProfileBaseline; + case 77: return VideoDecoderConfig::kH264ProfileMain; + case 88: return VideoDecoderConfig::kH264ProfileExtended; + case 100: return VideoDecoderConfig::kH264ProfileHigh; + case 110: return VideoDecoderConfig::kH264ProfileHigh10; + case 122: return VideoDecoderConfig::kH264ProfileHigh422; + case 144: return VideoDecoderConfig::kH264ProfileHigh444Predictive; + } + return VideoDecoderConfig::kUnknownVideoCodecProfile; +} + +void +WidevineVideoDecoder::InitDecode(const GMPVideoCodec& aCodecSettings, + const uint8_t* aCodecSpecific, + uint32_t aCodecSpecificLength, + GMPVideoDecoderCallback* aCallback, + int32_t aCoreCount) +{ + mCallback = aCallback; + VideoDecoderConfig config; + mCodecType = aCodecSettings.mCodecType; + if (mCodecType == kGMPVideoCodecH264) { + config.codec = VideoDecoderConfig::kCodecH264; + const GMPVideoCodecH264* h264 = (const GMPVideoCodecH264*)(aCodecSpecific); + config.profile = ToCDMH264Profile(h264->mAVCC.mProfile); + } else if (mCodecType == kGMPVideoCodecVP8) { + config.codec = VideoDecoderConfig::kCodecVp8; + config.profile = VideoDecoderConfig::kProfileNotNeeded; + } else if (mCodecType == kGMPVideoCodecVP9) { + config.codec = VideoDecoderConfig::kCodecVp9; + config.profile = VideoDecoderConfig::kProfileNotNeeded; + } else { + mCallback->Error(GMPInvalidArgErr); + return; + } + config.format = kYv12; + config.coded_size = Size(aCodecSettings.mWidth, aCodecSettings.mHeight); + mExtraData->AppendElements(aCodecSpecific + 1, aCodecSpecificLength); + config.extra_data = mExtraData->Elements(); + config.extra_data_size = mExtraData->Length(); + Status rv = CDM()->InitializeVideoDecoder(config); + if (rv != kSuccess) { + mCallback->Error(ToGMPErr(rv)); + return; + } + Log("WidevineVideoDecoder::InitDecode() rv=%d", rv); + mAnnexB = mp4_demuxer::AnnexB::ConvertExtraDataToAnnexB(mExtraData); +} + +void +WidevineVideoDecoder::Decode(GMPVideoEncodedFrame* aInputFrame, + bool aMissingFrames, + const uint8_t* aCodecSpecificInfo, + uint32_t aCodecSpecificInfoLength, + int64_t aRenderTimeMs) +{ + // We should not be given new input if a drain has been initiated + MOZ_ASSERT(!mDrainPending); + // We may not get the same out of the CDM decoder as we put in, and there + // may be some latency, i.e. we may need to input (say) 30 frames before + // we receive output. So we need to store the durations of the frames input, + // and retrieve them on output. + mFrameDurations[aInputFrame->TimeStamp()] = aInputFrame->Duration(); + + mSentInput = true; + InputBuffer sample; + + RefPtr<MediaRawData> raw( + new MediaRawData(aInputFrame->Buffer(), aInputFrame->Size())); + if (aInputFrame->Size() && !raw->Data()) { + // OOM. + mCallback->Error(GMPAllocErr); + return; + } + raw->mExtraData = mExtraData; + raw->mKeyframe = (aInputFrame->FrameType() == kGMPKeyFrame); + if (mCodecType == kGMPVideoCodecH264) { + // Convert input from AVCC, which GMPAPI passes in, to AnnexB, which + // Chromium uses internally. + mp4_demuxer::AnnexB::ConvertSampleToAnnexB(raw); + } + + const GMPEncryptedBufferMetadata* crypto = aInputFrame->GetDecryptionData(); + nsTArray<SubsampleEntry> subsamples; + InitInputBuffer(crypto, aInputFrame->TimeStamp(), raw->Data(), raw->Size(), sample, subsamples); + + // For keyframes, ConvertSampleToAnnexB will stick the AnnexB extra data + // at the start of the input. So we need to account for that as clear data + // in the subsamples. + if (raw->mKeyframe && !subsamples.IsEmpty() && mCodecType == kGMPVideoCodecH264) { + subsamples[0].clear_bytes += mAnnexB->Length(); + } + + WidevineVideoFrame frame; + Status rv = CDM()->DecryptAndDecodeFrame(sample, &frame); + Log("WidevineVideoDecoder::Decode(timestamp=%lld) rv=%d", sample.timestamp, rv); + + // Destroy frame, so that the shmem is now free to be used to return + // output to the Gecko process. + aInputFrame->Destroy(); + aInputFrame = nullptr; + + if (rv == kSuccess) { + if (!ReturnOutput(frame)) { + Log("WidevineVideoDecoder::Decode() Failed in ReturnOutput()"); + mCallback->Error(GMPDecodeErr); + return; + } + // A reset should only be started at most at level mReturnOutputCallDepth 1, + // and if it's started it should be finished by that call by the time + // the it returns, so it should always be false by this point. + MOZ_ASSERT(!mResetInProgress); + // Only request more data if we don't have pending samples. + if (mFrameAllocationQueue.empty()) { + MOZ_ASSERT(mCDMWrapper); + mCallback->InputDataExhausted(); + } + } else if (rv == kNeedMoreData) { + MOZ_ASSERT(mCDMWrapper); + mCallback->InputDataExhausted(); + } else { + mCallback->Error(ToGMPErr(rv)); + } + // Finish a drain if pending and we have no pending ReturnOutput calls on the stack. + if (mDrainPending && mReturnOutputCallDepth == 0) { + Drain(); + } +} + +// Util class to assist with counting mReturnOutputCallDepth. +class CounterHelper { +public: + // RAII, increment counter + explicit CounterHelper(int32_t& counter) + : mCounter(counter) + { + mCounter++; + } + + // RAII, decrement counter + ~CounterHelper() + { + mCounter--; + } + +private: + int32_t& mCounter; +}; + +// Util class to make sure GMP frames are freed. Holds a GMPVideoi420Frame* +// and will destroy it when the helper is destroyed unless the held frame +// if forgotten with ForgetFrame. +class FrameDestroyerHelper { +public: + explicit FrameDestroyerHelper(GMPVideoi420Frame*& frame) + : frame(frame) + { + } + + // RAII, destroy frame if held. + ~FrameDestroyerHelper() + { + if (frame) { + frame->Destroy(); + } + frame = nullptr; + } + + // Forget the frame without destroying it. + void ForgetFrame() + { + frame = nullptr; + } + +private: + GMPVideoi420Frame* frame; +}; + + +// Special handing is needed around ReturnOutput as it spins the IPC message +// queue when creating an empty frame and can end up with reentrant calls into +// the class methods. +bool +WidevineVideoDecoder::ReturnOutput(WidevineVideoFrame& aCDMFrame) +{ + MOZ_ASSERT(mReturnOutputCallDepth >= 0); + CounterHelper counterHelper(mReturnOutputCallDepth); + mFrameAllocationQueue.push_back(Move(aCDMFrame)); + if (mReturnOutputCallDepth > 1) { + // In a reentrant call. + return true; + } + while (!mFrameAllocationQueue.empty()) { + MOZ_ASSERT(mReturnOutputCallDepth == 1); + // If we're at call level 1 a reset should not have been started. A + // reset may be received during CreateEmptyFrame below, but we should not + // be in a reset at this stage -- this would indicate receiving decode + // messages before completing our reset, which we should not. + MOZ_ASSERT(!mResetInProgress); + WidevineVideoFrame currentCDMFrame = Move(mFrameAllocationQueue.front()); + mFrameAllocationQueue.pop_front(); + GMPVideoFrame* f = nullptr; + auto err = mVideoHost->CreateFrame(kGMPI420VideoFrame, &f); + if (GMP_FAILED(err) || !f) { + Log("Failed to create i420 frame!\n"); + return false; + } + auto gmpFrame = static_cast<GMPVideoi420Frame*>(f); + FrameDestroyerHelper frameDestroyerHelper(gmpFrame); + Size size = currentCDMFrame.Size(); + const int32_t yStride = currentCDMFrame.Stride(VideoFrame::kYPlane); + const int32_t uStride = currentCDMFrame.Stride(VideoFrame::kUPlane); + const int32_t vStride = currentCDMFrame.Stride(VideoFrame::kVPlane); + const int32_t halfHeight = size.height / 2; + // This call can cause a shmem alloc, during this alloc other calls + // may be made to this class and placed on the stack. ***WARNING***: + // other IPC calls can happen during this call, resulting in calls + // being made to the CDM. After this call state can have changed, + // and should be reevaluated. + err = gmpFrame->CreateEmptyFrame(size.width, + size.height, + yStride, + uStride, + vStride); + // Assert possible reentrant calls or resets haven't altered level unexpectedly. + MOZ_ASSERT(mReturnOutputCallDepth == 1); + ENSURE_GMP_SUCCESS(err, false); + + // If a reset started we need to dump the current frame and complete the reset. + if (mResetInProgress) { + MOZ_ASSERT(mCDMWrapper); + MOZ_ASSERT(mFrameAllocationQueue.empty()); + CompleteReset(); + return true; + } + + err = gmpFrame->SetWidth(size.width); + ENSURE_GMP_SUCCESS(err, false); + + err = gmpFrame->SetHeight(size.height); + ENSURE_GMP_SUCCESS(err, false); + + Buffer* buffer = currentCDMFrame.FrameBuffer(); + uint8_t* outBuffer = gmpFrame->Buffer(kGMPYPlane); + ENSURE_TRUE(outBuffer != nullptr, false); + MOZ_ASSERT(gmpFrame->AllocatedSize(kGMPYPlane) >= yStride*size.height); + memcpy(outBuffer, + buffer->Data() + currentCDMFrame.PlaneOffset(VideoFrame::kYPlane), + yStride * size.height); + + outBuffer = gmpFrame->Buffer(kGMPUPlane); + ENSURE_TRUE(outBuffer != nullptr, false); + MOZ_ASSERT(gmpFrame->AllocatedSize(kGMPUPlane) >= uStride * halfHeight); + memcpy(outBuffer, + buffer->Data() + currentCDMFrame.PlaneOffset(VideoFrame::kUPlane), + uStride * halfHeight); + + outBuffer = gmpFrame->Buffer(kGMPVPlane); + ENSURE_TRUE(outBuffer != nullptr, false); + MOZ_ASSERT(gmpFrame->AllocatedSize(kGMPVPlane) >= vStride * halfHeight); + memcpy(outBuffer, + buffer->Data() + currentCDMFrame.PlaneOffset(VideoFrame::kVPlane), + vStride * halfHeight); + + gmpFrame->SetTimestamp(currentCDMFrame.Timestamp()); + + auto d = mFrameDurations.find(currentCDMFrame.Timestamp()); + if (d != mFrameDurations.end()) { + gmpFrame->SetDuration(d->second); + mFrameDurations.erase(d); + } + + // Forget frame so it's not deleted, call back taking ownership. + frameDestroyerHelper.ForgetFrame(); + mCallback->Decoded(gmpFrame); + } + + return true; +} + +void +WidevineVideoDecoder::Reset() +{ + Log("WidevineVideoDecoder::Reset() mSentInput=%d", mSentInput); + // We shouldn't reset if a drain is pending. + MOZ_ASSERT(!mDrainPending); + mResetInProgress = true; + if (mSentInput) { + CDM()->ResetDecoder(kStreamTypeVideo); + } + // Remove queued frames, but do not reset mReturnOutputCallDepth, let the + // ReturnOutput calls unwind and decrement the counter as needed. + mFrameAllocationQueue.clear(); + mFrameDurations.clear(); + // Only if no ReturnOutput calls are in progress can we complete, otherwise + // ReturnOutput needs to finalize the reset. + if (mReturnOutputCallDepth == 0) { + CompleteReset(); + } +} + +void +WidevineVideoDecoder::CompleteReset() +{ + mCallback->ResetComplete(); + mSentInput = false; + mResetInProgress = false; +} + +void +WidevineVideoDecoder::Drain() +{ + Log("WidevineVideoDecoder::Drain()"); + if (mReturnOutputCallDepth > 0) { + Log("Drain call is reentrant, postponing drain"); + mDrainPending = true; + return; + } + + Status rv = kSuccess; + while (rv == kSuccess) { + WidevineVideoFrame frame; + InputBuffer sample; + Status rv = CDM()->DecryptAndDecodeFrame(sample, &frame); + Log("WidevineVideoDecoder::Drain(); DecryptAndDecodeFrame() rv=%d", rv); + if (frame.Format() == kUnknownVideoFormat) { + break; + } + if (rv == kSuccess) { + if (!ReturnOutput(frame)) { + Log("WidevineVideoDecoder::Decode() Failed in ReturnOutput()"); + } + } + } + // Shouldn't be reset while draining. + MOZ_ASSERT(!mResetInProgress); + + CDM()->ResetDecoder(kStreamTypeVideo); + mDrainPending = false; + mCallback->DrainComplete(); +} + +void +WidevineVideoDecoder::DecodingComplete() +{ + Log("WidevineVideoDecoder::DecodingComplete()"); + if (mCDMWrapper) { + CDM()->DeinitializeDecoder(kStreamTypeVideo); + mCDMWrapper = nullptr; + } + // Release that corresponds to AddRef() in constructor. + Release(); +} + +} // namespace mozilla diff --git a/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.h b/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.h new file mode 100644 index 000000000..b143f75f7 --- /dev/null +++ b/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.h @@ -0,0 +1,80 @@ +/* -*- 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/. */ + +#ifndef WidevineVideoDecoder_h_ +#define WidevineVideoDecoder_h_ + +#include "stddef.h" +#include "content_decryption_module.h" +#include "gmp-api/gmp-video-decode.h" +#include "gmp-api/gmp-video-host.h" +#include "MediaData.h" +#include "nsISupportsImpl.h" +#include "nsTArray.h" +#include "WidevineDecryptor.h" +#include "WidevineVideoFrame.h" +#include <map> +#include <deque> + +namespace mozilla { + +class WidevineVideoDecoder : public GMPVideoDecoder { +public: + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WidevineVideoDecoder) + + WidevineVideoDecoder(GMPVideoHost* aVideoHost, + RefPtr<CDMWrapper> aCDMWrapper); + void InitDecode(const GMPVideoCodec& aCodecSettings, + const uint8_t* aCodecSpecific, + uint32_t aCodecSpecificLength, + GMPVideoDecoderCallback* aCallback, + int32_t aCoreCount) override; + void Decode(GMPVideoEncodedFrame* aInputFrame, + bool aMissingFrames, + const uint8_t* aCodecSpecificInfo, + uint32_t aCodecSpecificInfoLength, + int64_t aRenderTimeMs = -1) override; + void Reset() override; + void Drain() override; + void DecodingComplete() override; + +private: + + ~WidevineVideoDecoder(); + + cdm::ContentDecryptionModule_8* CDM() const { + // CDM should only be accessed before 'DecodingComplete'. + MOZ_ASSERT(mCDMWrapper); + // CDMWrapper ensure the CDM is non-null, no need to check again. + return mCDMWrapper->GetCDM(); + } + + bool ReturnOutput(WidevineVideoFrame& aFrame); + void CompleteReset(); + + GMPVideoHost* mVideoHost; + RefPtr<CDMWrapper> mCDMWrapper; + RefPtr<MediaByteBuffer> mExtraData; + RefPtr<MediaByteBuffer> mAnnexB; + GMPVideoDecoderCallback* mCallback; + std::map<uint64_t, uint64_t> mFrameDurations; + bool mSentInput; + GMPVideoCodecType mCodecType; + // Frames waiting on allocation + std::deque<WidevineVideoFrame> mFrameAllocationQueue; + // Number of calls of ReturnOutput currently in progress. + int32_t mReturnOutputCallDepth; + // If we're waiting to drain. Used to prevent drain completing while + // ReturnOutput calls are still on the stack. + bool mDrainPending; + // If a reset is being performed. Used to track if ReturnOutput should + // dump current frame. + bool mResetInProgress; +}; + +} // namespace mozilla + +#endif // WidevineVideoDecoder_h_ diff --git a/dom/media/gmp/widevine-adapter/WidevineVideoFrame.cpp b/dom/media/gmp/widevine-adapter/WidevineVideoFrame.cpp new file mode 100644 index 000000000..4221bf15b --- /dev/null +++ b/dom/media/gmp/widevine-adapter/WidevineVideoFrame.cpp @@ -0,0 +1,126 @@ +/* -*- 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 "WidevineVideoFrame.h" + +#include "WidevineUtils.h" + +using namespace cdm; + +namespace mozilla { + +WidevineVideoFrame::WidevineVideoFrame() + : mFormat(kUnknownVideoFormat) + , mSize(0,0) + , mBuffer(nullptr) + , mTimestamp(0) +{ + Log("WidevineVideoFrame::WidevineVideoFrame() this=%p", this); + memset(mPlaneOffsets, 0, sizeof(mPlaneOffsets)); + memset(mPlaneStrides, 0, sizeof(mPlaneStrides)); +} + +WidevineVideoFrame::WidevineVideoFrame(WidevineVideoFrame&& aOther) + : mFormat(aOther.mFormat) + , mSize(aOther.mSize) + , mBuffer(aOther.mBuffer) + , mTimestamp(aOther.mTimestamp) +{ + Log("WidevineVideoFrame::WidevineVideoFrame(WidevineVideoFrame&&) this=%p, other=%p", + this, &aOther); + memcpy(mPlaneOffsets, aOther.mPlaneOffsets, sizeof(mPlaneOffsets)); + memcpy(mPlaneStrides, aOther.mPlaneStrides, sizeof(mPlaneStrides)); + aOther.mBuffer = nullptr; +} + +WidevineVideoFrame::~WidevineVideoFrame() +{ + if (mBuffer) { + mBuffer->Destroy(); + mBuffer = nullptr; + } +} + +void +WidevineVideoFrame::SetFormat(cdm::VideoFormat aFormat) +{ + Log("WidevineVideoFrame::SetFormat(%d) this=%p", aFormat, this); + mFormat = aFormat; +} + +cdm::VideoFormat +WidevineVideoFrame::Format() const +{ + return mFormat; +} + +void +WidevineVideoFrame::SetSize(cdm::Size aSize) +{ + Log("WidevineVideoFrame::SetSize(%d,%d) this=%p", aSize.width, aSize.height, this); + mSize.width = aSize.width; + mSize.height = aSize.height; +} + +cdm::Size +WidevineVideoFrame::Size() const +{ + return mSize; +} + +void +WidevineVideoFrame::SetFrameBuffer(cdm::Buffer* aFrameBuffer) +{ + Log("WidevineVideoFrame::SetFrameBuffer(%p) this=%p", aFrameBuffer, this); + MOZ_ASSERT(!mBuffer); + mBuffer = aFrameBuffer; +} + +cdm::Buffer* +WidevineVideoFrame::FrameBuffer() +{ + return mBuffer; +} + +void +WidevineVideoFrame::SetPlaneOffset(cdm::VideoFrame::VideoPlane aPlane, uint32_t aOffset) +{ + Log("WidevineVideoFrame::SetPlaneOffset(%d, %d) this=%p", aPlane, aOffset, this); + mPlaneOffsets[aPlane] = aOffset; +} + +uint32_t +WidevineVideoFrame::PlaneOffset(cdm::VideoFrame::VideoPlane aPlane) +{ + return mPlaneOffsets[aPlane]; +} + +void +WidevineVideoFrame::SetStride(cdm::VideoFrame::VideoPlane aPlane, uint32_t aStride) +{ + Log("WidevineVideoFrame::SetStride(%d, %d) this=%p", aPlane, aStride, this); + mPlaneStrides[aPlane] = aStride; +} + +uint32_t +WidevineVideoFrame::Stride(cdm::VideoFrame::VideoPlane aPlane) +{ + return mPlaneStrides[aPlane]; +} + +void +WidevineVideoFrame::SetTimestamp(int64_t timestamp) +{ + Log("WidevineVideoFrame::SetTimestamp(%lld) this=%p", timestamp, this); + mTimestamp = timestamp; +} + +int64_t +WidevineVideoFrame::Timestamp() const +{ + return mTimestamp; +} + +} // namespace mozilla diff --git a/dom/media/gmp/widevine-adapter/WidevineVideoFrame.h b/dom/media/gmp/widevine-adapter/WidevineVideoFrame.h new file mode 100644 index 000000000..96d4f20f8 --- /dev/null +++ b/dom/media/gmp/widevine-adapter/WidevineVideoFrame.h @@ -0,0 +1,50 @@ +/* -*- 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/. */ + +#ifndef WidevineVideoFrame_h_ +#define WidevineVideoFrame_h_ + +#include "stddef.h" +#include "content_decryption_module.h" +#include <vector> + +namespace mozilla { + +class WidevineVideoFrame : public cdm::VideoFrame { +public: + WidevineVideoFrame(); + WidevineVideoFrame(WidevineVideoFrame&& other); + ~WidevineVideoFrame(); + + void SetFormat(cdm::VideoFormat aFormat) override; + cdm::VideoFormat Format() const override; + + void SetSize(cdm::Size aSize) override; + cdm::Size Size() const override; + + void SetFrameBuffer(cdm::Buffer* aFrameBuffer) override; + cdm::Buffer* FrameBuffer() override; + + void SetPlaneOffset(cdm::VideoFrame::VideoPlane aPlane, uint32_t aOffset) override; + uint32_t PlaneOffset(cdm::VideoFrame::VideoPlane aPlane) override; + + void SetStride(cdm::VideoFrame::VideoPlane aPlane, uint32_t aStride) override; + uint32_t Stride(cdm::VideoFrame::VideoPlane aPlane) override; + + void SetTimestamp(int64_t aTimestamp) override; + int64_t Timestamp() const override; + +protected: + cdm::VideoFormat mFormat; + cdm::Size mSize; + cdm::Buffer* mBuffer; + uint32_t mPlaneOffsets[kMaxPlanes]; + uint32_t mPlaneStrides[kMaxPlanes]; + int64_t mTimestamp; +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/gmp/widevine-adapter/content_decryption_module.h b/dom/media/gmp/widevine-adapter/content_decryption_module.h new file mode 100644 index 000000000..512ca9768 --- /dev/null +++ b/dom/media/gmp/widevine-adapter/content_decryption_module.h @@ -0,0 +1,1199 @@ +// Copyright (c) 2012 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 CDM_CONTENT_DECRYPTION_MODULE_H_ +#define CDM_CONTENT_DECRYPTION_MODULE_H_ + +#if defined(_MSC_VER) +typedef unsigned char uint8_t; +typedef unsigned int uint32_t; +typedef int int32_t; +typedef __int64 int64_t; +#else +#include <stdint.h> +#endif + +// Define CDM_EXPORT so that functionality implemented by the CDM module +// can be exported to consumers. +#if defined(WIN32) + +#if defined(CDM_IMPLEMENTATION) +#define CDM_EXPORT __declspec(dllexport) +#else +#define CDM_EXPORT __declspec(dllimport) +#endif // defined(CDM_IMPLEMENTATION) + +#else // defined(WIN32) + +#if defined(CDM_IMPLEMENTATION) +#define CDM_EXPORT __attribute__((visibility("default"))) +#else +#define CDM_EXPORT +#endif + +#endif // defined(WIN32) + +// The version number must be rolled when the exported functions are updated! +// If the CDM and the adapter use different versions of these functions, the +// adapter will fail to load or crash! +#define CDM_MODULE_VERSION 4 + +// Build the versioned entrypoint name. +// The extra macros are necessary to expand version to an actual value. +#define INITIALIZE_CDM_MODULE \ + BUILD_ENTRYPOINT(InitializeCdmModule, CDM_MODULE_VERSION) +#define BUILD_ENTRYPOINT(name, version) \ + BUILD_ENTRYPOINT_NO_EXPANSION(name, version) +#define BUILD_ENTRYPOINT_NO_EXPANSION(name, version) name##_##version + +extern "C" { +CDM_EXPORT void INITIALIZE_CDM_MODULE(); + +CDM_EXPORT void DeinitializeCdmModule(); + +// Returns a pointer to the requested CDM Host interface upon success. +// Returns NULL if the requested CDM Host interface is not supported. +// The caller should cast the returned pointer to the type matching +// |host_interface_version|. +typedef void* (*GetCdmHostFunc)(int host_interface_version, void* user_data); + +// Returns a pointer to the requested CDM upon success. +// Returns NULL if an error occurs or the requested |cdm_interface_version| or +// |key_system| is not supported or another error occurs. +// The caller should cast the returned pointer to the type matching +// |cdm_interface_version|. +// Caller retains ownership of arguments and must call Destroy() on the returned +// object. +CDM_EXPORT void* CreateCdmInstance( + int cdm_interface_version, + const char* key_system, uint32_t key_system_size, + GetCdmHostFunc get_cdm_host_func, void* user_data); + +CDM_EXPORT const char* GetCdmVersion(); +} + +namespace cdm { + +class AudioFrames; +class DecryptedBlock; +class VideoFrame; + +class Host_7; +class Host_8; + +enum Status { + kSuccess = 0, + kNeedMoreData, // Decoder needs more data to produce a decoded frame/sample. + kNoKey, // The required decryption key is not available. + kSessionError, // Session management error. + kDecryptError, // Decryption failed. + kDecodeError, // Error decoding audio or video. + kDeferredInitialization // Decoder is not ready for initialization. +}; + +// This must at least contain the exceptions defined in the spec: +// https://w3c.github.io/encrypted-media/#exceptions +// The following starts with the list of DOM4 exceptions from: +// http://www.w3.org/TR/dom/#domexception +// Some DOM4 exceptions are not included as they are not expected to be used. +enum Error { + kNotSupportedError = 9, + kInvalidStateError = 11, + kInvalidAccessError = 15, + kQuotaExceededError = 22, + + // Additional exceptions that do not have assigned codes. + // There are other non-EME-specific values, not included in this list. + kUnknownError = 30, + + // Additional values from previous EME versions. They currently have no + // matching DOMException. + kClientError = 100, + kOutputError = 101 +}; + +// Time is defined as the number of seconds since the +// Epoch (00:00:00 UTC, January 1, 1970). +typedef double Time; + +// An input buffer can be split into several continuous subsamples. +// A SubsampleEntry specifies the number of clear and cipher bytes in each +// subsample. For example, the following buffer has three subsamples: +// +// |<----- subsample1 ----->|<----- subsample2 ----->|<----- subsample3 ----->| +// | clear1 | cipher1 | clear2 | cipher2 | clear3 | cipher3 | +// +// For decryption, all of the cipher bytes in a buffer should be concatenated +// (in the subsample order) into a single logical stream. The clear bytes should +// not be considered as part of decryption. +// +// Stream to decrypt: | cipher1 | cipher2 | cipher3 | +// Decrypted stream: | decrypted1| decrypted2 | decrypted3 | +// +// After decryption, the decrypted bytes should be copied over the position +// of the corresponding cipher bytes in the original buffer to form the output +// buffer. Following the above example, the decrypted buffer should be: +// +// |<----- subsample1 ----->|<----- subsample2 ----->|<----- subsample3 ----->| +// | clear1 | decrypted1| clear2 | decrypted2 | clear3 | decrypted3 | +// +struct SubsampleEntry { + SubsampleEntry(uint32_t clear_bytes, uint32_t cipher_bytes) + : clear_bytes(clear_bytes), cipher_bytes(cipher_bytes) {} + + uint32_t clear_bytes; + uint32_t cipher_bytes; +}; + +// Represents an input buffer to be decrypted (and possibly decoded). It does +// not own any pointers in this struct. If |iv_size| = 0, the data is +// unencrypted. +struct InputBuffer { + InputBuffer() + : data(NULL), + data_size(0), + key_id(NULL), + key_id_size(0), + iv(NULL), + iv_size(0), + subsamples(NULL), + num_subsamples(0), + timestamp(0) {} + + const uint8_t* data; // Pointer to the beginning of the input data. + uint32_t data_size; // Size (in bytes) of |data|. + + const uint8_t* key_id; // Key ID to identify the decryption key. + uint32_t key_id_size; // Size (in bytes) of |key_id|. + + const uint8_t* iv; // Initialization vector. + uint32_t iv_size; // Size (in bytes) of |iv|. + + const struct SubsampleEntry* subsamples; + uint32_t num_subsamples; // Number of subsamples in |subsamples|. + + int64_t timestamp; // Presentation timestamp in microseconds. +}; + +struct AudioDecoderConfig { + enum AudioCodec { + kUnknownAudioCodec = 0, + kCodecVorbis, + kCodecAac + }; + + AudioDecoderConfig() + : codec(kUnknownAudioCodec), + channel_count(0), + bits_per_channel(0), + samples_per_second(0), + extra_data(NULL), + extra_data_size(0) {} + + AudioCodec codec; + int32_t channel_count; + int32_t bits_per_channel; + int32_t samples_per_second; + + // Optional byte data required to initialize audio decoders, such as the + // vorbis setup header. + uint8_t* extra_data; + uint32_t extra_data_size; +}; + +// Supported sample formats for AudioFrames. +enum AudioFormat { + kUnknownAudioFormat = 0, // Unknown format value. Used for error reporting. + kAudioFormatU8, // Interleaved unsigned 8-bit w/ bias of 128. + kAudioFormatS16, // Interleaved signed 16-bit. + kAudioFormatS32, // Interleaved signed 32-bit. + kAudioFormatF32, // Interleaved float 32-bit. + kAudioFormatPlanarS16, // Signed 16-bit planar. + kAudioFormatPlanarF32, // Float 32-bit planar. +}; + +// Surface formats based on FOURCC labels, see: http://www.fourcc.org/yuv.php +enum VideoFormat { + kUnknownVideoFormat = 0, // Unknown format value. Used for error reporting. + kYv12, // 12bpp YVU planar 1x1 Y, 2x2 VU samples. + kI420 // 12bpp YVU planar 1x1 Y, 2x2 UV samples. +}; + +struct Size { + Size() : width(0), height(0) {} + Size(int32_t width, int32_t height) : width(width), height(height) {} + + int32_t width; + int32_t height; +}; + +struct VideoDecoderConfig { + enum VideoCodec { + kUnknownVideoCodec = 0, + kCodecVp8, + kCodecH264, + kCodecVp9 + }; + + enum VideoCodecProfile { + kUnknownVideoCodecProfile = 0, + kProfileNotNeeded, + kH264ProfileBaseline, + kH264ProfileMain, + kH264ProfileExtended, + kH264ProfileHigh, + kH264ProfileHigh10, + kH264ProfileHigh422, + kH264ProfileHigh444Predictive + }; + + VideoDecoderConfig() + : codec(kUnknownVideoCodec), + profile(kUnknownVideoCodecProfile), + format(kUnknownVideoFormat), + extra_data(NULL), + extra_data_size(0) {} + + VideoCodec codec; + VideoCodecProfile profile; + VideoFormat format; + + // Width and height of video frame immediately post-decode. Not all pixels + // in this region are valid. + Size coded_size; + + // Optional byte data required to initialize video decoders, such as H.264 + // AAVC data. + uint8_t* extra_data; + uint32_t extra_data_size; +}; + +enum StreamType { + kStreamTypeAudio = 0, + kStreamTypeVideo = 1 +}; + +// Structure provided to ContentDecryptionModule::OnPlatformChallengeResponse() +// after a platform challenge was initiated via Host::SendPlatformChallenge(). +// All values will be NULL / zero in the event of a challenge failure. +struct PlatformChallengeResponse { + // |challenge| provided during Host::SendPlatformChallenge() combined with + // nonce data and signed with the platform's private key. + const uint8_t* signed_data; + uint32_t signed_data_length; + + // RSASSA-PKCS1-v1_5-SHA256 signature of the |signed_data| block. + const uint8_t* signed_data_signature; + uint32_t signed_data_signature_length; + + // X.509 device specific certificate for the |service_id| requested. + const uint8_t* platform_key_certificate; + uint32_t platform_key_certificate_length; +}; + +// Used when passing arrays of binary data. Does not own the referenced data. +struct BinaryData { + BinaryData() : data(NULL), length(0) {} + const uint8_t* data; + uint32_t length; +}; + +// The current status of the associated key. The valid types are defined in the +// spec: https://w3c.github.io/encrypted-media/#idl-def-MediaKeyStatus +enum KeyStatus { + kUsable = 0, + kInternalError = 1, + kExpired = 2, + kOutputRestricted = 3, + kOutputDownscaled = 4, + kStatusPending = 5, + kReleased = 6 +}; + +// Used when passing arrays of key information. Does not own the referenced +// data. |system_code| is an additional error code for unusable keys and +// should be 0 when |status| == kUsable. +struct KeyInformation { + KeyInformation() + : key_id(NULL), key_id_size(0), status(kInternalError), system_code(0) {} + const uint8_t* key_id; + uint32_t key_id_size; + KeyStatus status; + uint32_t system_code; +}; + +// Supported output protection methods for use with EnableOutputProtection() and +// returned by OnQueryOutputProtectionStatus(). +enum OutputProtectionMethods { + kProtectionNone = 0, + kProtectionHDCP = 1 << 0 +}; + +// Connected output link types returned by OnQueryOutputProtectionStatus(). +enum OutputLinkTypes { + kLinkTypeNone = 0, + kLinkTypeUnknown = 1 << 0, + kLinkTypeInternal = 1 << 1, + kLinkTypeVGA = 1 << 2, + kLinkTypeHDMI = 1 << 3, + kLinkTypeDVI = 1 << 4, + kLinkTypeDisplayPort = 1 << 5, + kLinkTypeNetwork = 1 << 6 +}; + +// Result of the QueryOutputProtectionStatus() call. +enum QueryResult { + kQuerySucceeded = 0, + kQueryFailed +}; + +// The Initialization Data Type. The valid types are defined in the spec: +// http://w3c.github.io/encrypted-media/initdata-format-registry.html#registry +enum InitDataType { + kCenc = 0, + kKeyIds = 1, + kWebM = 2 +}; + +// The type of session to create. The valid types are defined in the spec: +// https://w3c.github.io/encrypted-media/#idl-def-SessionType +enum SessionType { + kTemporary = 0, + kPersistentLicense = 1, + kPersistentKeyRelease = 2 +}; + +// The type of the message event. The valid types are defined in the spec: +// https://w3c.github.io/encrypted-media/#idl-def-MediaKeyMessageType +enum MessageType { + kLicenseRequest = 0, + kLicenseRenewal = 1, + kLicenseRelease = 2 +}; + +// FileIO interface provides a way for the CDM to store data in a file in +// persistent storage. This interface aims only at providing basic read/write +// capabilities and should not be used as a full fledged file IO API. +// Each CDM and origin (e.g. HTTPS, "foo.example.com", 443) combination has +// its own persistent storage. All instances of a given CDM associated with a +// given origin share the same persistent storage. +// Note to implementors of this interface: +// Per-origin storage and the ability for users to clear it are important. +// See http://www.w3.org/TR/encrypted-media/#privacy-storedinfo. +class FileIO { + public: + // Opens the file with |file_name| for read and write. + // FileIOClient::OnOpenComplete() will be called after the opening + // operation finishes. + // - When the file is opened by a CDM instance, it will be classified as "in + // use". In this case other CDM instances in the same domain may receive + // kInUse status when trying to open it. + // - |file_name| must not contain forward slash ('/') or backslash ('\'), and + // must not start with an underscore ('_'). + virtual void Open(const char* file_name, uint32_t file_name_size) = 0; + + // Reads the contents of the file. FileIOClient::OnReadComplete() will be + // called with the read status. Read() should not be called if a previous + // Read() or Write() call is still pending; otherwise OnReadComplete() will + // be called with kInUse. + virtual void Read() = 0; + + // Writes |data_size| bytes of |data| into the file. + // FileIOClient::OnWriteComplete() will be called with the write status. + // All existing contents in the file will be overwritten. Calling Write() with + // NULL |data| will clear all contents in the file. Write() should not be + // called if a previous Write() or Read() call is still pending; otherwise + // OnWriteComplete() will be called with kInUse. + virtual void Write(const uint8_t* data, uint32_t data_size) = 0; + + // Closes the file if opened, destroys this FileIO object and releases any + // resources allocated. The CDM must call this method when it finished using + // this object. A FileIO object must not be used after Close() is called. + virtual void Close() = 0; + + protected: + FileIO() {} + virtual ~FileIO() {} +}; + +// Responses to FileIO calls. All responses will be called asynchronously. +// When kError is returned, the FileIO object could be in an error state. All +// following calls (other than Close()) could return kError. The CDM should +// still call Close() to destroy the FileIO object. +class FileIOClient { + public: + enum Status { + kSuccess = 0, + kInUse, + kError + }; + + // Response to a FileIO::Open() call with the open |status|. + virtual void OnOpenComplete(Status status) = 0; + + // Response to a FileIO::Read() call to provide |data_size| bytes of |data| + // read from the file. + // - kSuccess indicates that all contents of the file has been successfully + // read. In this case, 0 |data_size| means that the file is empty. + // - kInUse indicates that there are other read/write operations pending. + // - kError indicates read failure, e.g. the storage is not open or cannot be + // fully read. + virtual void OnReadComplete(Status status, + const uint8_t* data, uint32_t data_size) = 0; + + // Response to a FileIO::Write() call. + // - kSuccess indicates that all the data has been written into the file + // successfully. + // - kInUse indicates that there are other read/write operations pending. + // - kError indicates write failure, e.g. the storage is not open or cannot be + // fully written. Upon write failure, the contents of the file should be + // regarded as corrupt and should not used. + virtual void OnWriteComplete(Status status) = 0; + + protected: + FileIOClient() {} + virtual ~FileIOClient() {} +}; + +// ContentDecryptionModule interface that all CDMs need to implement. +// The interface is versioned for backward compatibility. +// Note: ContentDecryptionModule implementations must use the allocator +// provided in CreateCdmInstance() to allocate any Buffer that needs to +// be passed back to the caller. Implementations must call Buffer::Destroy() +// when a Buffer is created that will never be returned to the caller. +class ContentDecryptionModule_7 { + public: + static const int kVersion = 7; + typedef Host_7 Host; + + // SetServerCertificate(), CreateSessionAndGenerateRequest(), LoadSession(), + // UpdateSession(), CloseSession(), and RemoveSession() all accept a + // |promise_id|, which must be passed to the completion Host method + // (e.g. Host::OnResolveNewSessionPromise()). + + // Provides a server certificate to be used to encrypt messages to the + // license server. The CDM must respond by calling either + // Host::OnResolvePromise() or Host::OnRejectPromise(). + virtual void SetServerCertificate(uint32_t promise_id, + const uint8_t* server_certificate_data, + uint32_t server_certificate_data_size) = 0; + + // Creates a session given |session_type|, |init_data_type|, and |init_data|. + // The CDM must respond by calling either Host::OnResolveNewSessionPromise() + // or Host::OnRejectPromise(). + virtual void CreateSessionAndGenerateRequest(uint32_t promise_id, + SessionType session_type, + const char* init_data_type, + uint32_t init_data_type_size, + const uint8_t* init_data, + uint32_t init_data_size) = 0; + + // Loads the session of type |session_type| specified by |session_id|. + // The CDM must respond by calling either Host::OnResolveNewSessionPromise() + // or Host::OnRejectPromise(). If the session is not found, call + // Host::OnResolveNewSessionPromise() with session_id = NULL. + virtual void LoadSession(uint32_t promise_id, + SessionType session_type, + const char* session_id, + uint32_t session_id_size) = 0; + + // Updates the session with |response|. The CDM must respond by calling + // either Host::OnResolvePromise() or Host::OnRejectPromise(). + virtual void UpdateSession(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size, + const uint8_t* response, + uint32_t response_size) = 0; + + // Requests that the CDM close the session. The CDM must respond by calling + // either Host::OnResolvePromise() or Host::OnRejectPromise() when the request + // has been processed. This may be before the session is closed. Once the + // session is closed, Host::OnSessionClosed() must also be called. + virtual void CloseSession(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size) = 0; + + // Removes any stored session data associated with this session. Will only be + // called for persistent sessions. The CDM must respond by calling either + // Host::OnResolvePromise() or Host::OnRejectPromise() when the request has + // been processed. + virtual void RemoveSession(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size) = 0; + + // Performs scheduled operation with |context| when the timer fires. + virtual void TimerExpired(void* context) = 0; + + // Decrypts the |encrypted_buffer|. + // + // Returns kSuccess if decryption succeeded, in which case the callee + // should have filled the |decrypted_buffer| and passed the ownership of + // |data| in |decrypted_buffer| to the caller. + // Returns kNoKey if the CDM did not have the necessary decryption key + // to decrypt. + // Returns kDecryptError if any other error happened. + // If the return value is not kSuccess, |decrypted_buffer| should be ignored + // by the caller. + virtual Status Decrypt(const InputBuffer& encrypted_buffer, + DecryptedBlock* decrypted_buffer) = 0; + + // Initializes the CDM audio decoder with |audio_decoder_config|. This + // function must be called before DecryptAndDecodeSamples() is called. + // + // Returns kSuccess if the |audio_decoder_config| is supported and the CDM + // audio decoder is successfully initialized. + // Returns kSessionError if |audio_decoder_config| is not supported. The CDM + // may still be able to do Decrypt(). + // Returns kDeferredInitialization if the CDM is not ready to initialize the + // decoder at this time. Must call Host::OnDeferredInitializationDone() once + // initialization is complete. + virtual Status InitializeAudioDecoder( + const AudioDecoderConfig& audio_decoder_config) = 0; + + // Initializes the CDM video decoder with |video_decoder_config|. This + // function must be called before DecryptAndDecodeFrame() is called. + // + // Returns kSuccess if the |video_decoder_config| is supported and the CDM + // video decoder is successfully initialized. + // Returns kSessionError if |video_decoder_config| is not supported. The CDM + // may still be able to do Decrypt(). + // Returns kDeferredInitialization if the CDM is not ready to initialize the + // decoder at this time. Must call Host::OnDeferredInitializationDone() once + // initialization is complete. + virtual Status InitializeVideoDecoder( + const VideoDecoderConfig& video_decoder_config) = 0; + + // De-initializes the CDM decoder and sets it to an uninitialized state. The + // caller can initialize the decoder again after this call to re-initialize + // it. This can be used to reconfigure the decoder if the configuration + // changes. + virtual void DeinitializeDecoder(StreamType decoder_type) = 0; + + // Resets the CDM decoder to an initialized clean state. All internal buffers + // MUST be flushed. + virtual void ResetDecoder(StreamType decoder_type) = 0; + + // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into a + // |video_frame|. Upon end-of-stream, the caller should call this function + // repeatedly with empty |encrypted_buffer| (|data| == NULL) until only empty + // |video_frame| (|format| == kEmptyVideoFrame) is produced. + // + // Returns kSuccess if decryption and decoding both succeeded, in which case + // the callee will have filled the |video_frame| and passed the ownership of + // |frame_buffer| in |video_frame| to the caller. + // Returns kNoKey if the CDM did not have the necessary decryption key + // to decrypt. + // Returns kNeedMoreData if more data was needed by the decoder to generate + // a decoded frame (e.g. during initialization and end-of-stream). + // Returns kDecryptError if any decryption error happened. + // Returns kDecodeError if any decoding error happened. + // If the return value is not kSuccess, |video_frame| should be ignored by + // the caller. + virtual Status DecryptAndDecodeFrame(const InputBuffer& encrypted_buffer, + VideoFrame* video_frame) = 0; + + // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into + // |audio_frames|. Upon end-of-stream, the caller should call this function + // repeatedly with empty |encrypted_buffer| (|data| == NULL) until only empty + // |audio_frames| is produced. + // + // Returns kSuccess if decryption and decoding both succeeded, in which case + // the callee will have filled |audio_frames| and passed the ownership of + // |data| in |audio_frames| to the caller. + // Returns kNoKey if the CDM did not have the necessary decryption key + // to decrypt. + // Returns kNeedMoreData if more data was needed by the decoder to generate + // audio samples (e.g. during initialization and end-of-stream). + // Returns kDecryptError if any decryption error happened. + // Returns kDecodeError if any decoding error happened. + // If the return value is not kSuccess, |audio_frames| should be ignored by + // the caller. + virtual Status DecryptAndDecodeSamples(const InputBuffer& encrypted_buffer, + AudioFrames* audio_frames) = 0; + + // Called by the host after a platform challenge was initiated via + // Host::SendPlatformChallenge(). + virtual void OnPlatformChallengeResponse( + const PlatformChallengeResponse& response) = 0; + + // Called by the host after a call to Host::QueryOutputProtectionStatus(). The + // |link_mask| is a bit mask of OutputLinkTypes and |output_protection_mask| + // is a bit mask of OutputProtectionMethods. If |result| is kQueryFailed, + // then |link_mask| and |output_protection_mask| are undefined and should + // be ignored. + virtual void OnQueryOutputProtectionStatus( + QueryResult result, + uint32_t link_mask, + uint32_t output_protection_mask) = 0; + + // Destroys the object in the same context as it was created. + virtual void Destroy() = 0; + + protected: + ContentDecryptionModule_7() {} + virtual ~ContentDecryptionModule_7() {} +}; + +// ContentDecryptionModule interface that all CDMs need to implement. +// The interface is versioned for backward compatibility. +// Note: ContentDecryptionModule implementations must use the allocator +// provided in CreateCdmInstance() to allocate any Buffer that needs to +// be passed back to the caller. Implementations must call Buffer::Destroy() +// when a Buffer is created that will never be returned to the caller. +class ContentDecryptionModule_8 { + public: + static const int kVersion = 8; + typedef Host_8 Host; + + // Initializes the CDM instance, providing information about permitted + // functionalities. + // If |allow_distinctive_identifier| is false, messages from the CDM, + // such as message events, must not contain a Distinctive Identifier, + // even in an encrypted form. + // If |allow_persistent_state| is false, the CDM must not attempt to + // persist state. Calls to CreateFileIO() will fail. + virtual void Initialize(bool allow_distinctive_identifier, + bool allow_persistent_state) = 0; + + // SetServerCertificate(), CreateSessionAndGenerateRequest(), LoadSession(), + // UpdateSession(), CloseSession(), and RemoveSession() all accept a + // |promise_id|, which must be passed to the completion Host method + // (e.g. Host::OnResolveNewSessionPromise()). + + // Provides a server certificate to be used to encrypt messages to the + // license server. The CDM must respond by calling either + // Host::OnResolvePromise() or Host::OnRejectPromise(). + virtual void SetServerCertificate(uint32_t promise_id, + const uint8_t* server_certificate_data, + uint32_t server_certificate_data_size) = 0; + + // Creates a session given |session_type|, |init_data_type|, and |init_data|. + // The CDM must respond by calling either Host::OnResolveNewSessionPromise() + // or Host::OnRejectPromise(). + virtual void CreateSessionAndGenerateRequest(uint32_t promise_id, + SessionType session_type, + InitDataType init_data_type, + const uint8_t* init_data, + uint32_t init_data_size) = 0; + + // Loads the session of type |session_type| specified by |session_id|. + // The CDM must respond by calling either Host::OnResolveNewSessionPromise() + // or Host::OnRejectPromise(). If the session is not found, call + // Host::OnResolveNewSessionPromise() with session_id = NULL. + virtual void LoadSession(uint32_t promise_id, + SessionType session_type, + const char* session_id, + uint32_t session_id_size) = 0; + + // Updates the session with |response|. The CDM must respond by calling + // either Host::OnResolvePromise() or Host::OnRejectPromise(). + virtual void UpdateSession(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size, + const uint8_t* response, + uint32_t response_size) = 0; + + // Requests that the CDM close the session. The CDM must respond by calling + // either Host::OnResolvePromise() or Host::OnRejectPromise() when the request + // has been processed. This may be before the session is closed. Once the + // session is closed, Host::OnSessionClosed() must also be called. + virtual void CloseSession(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size) = 0; + + // Removes any stored session data associated with this session. Will only be + // called for persistent sessions. The CDM must respond by calling either + // Host::OnResolvePromise() or Host::OnRejectPromise() when the request has + // been processed. + virtual void RemoveSession(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size) = 0; + + // Performs scheduled operation with |context| when the timer fires. + virtual void TimerExpired(void* context) = 0; + + // Decrypts the |encrypted_buffer|. + // + // Returns kSuccess if decryption succeeded, in which case the callee + // should have filled the |decrypted_buffer| and passed the ownership of + // |data| in |decrypted_buffer| to the caller. + // Returns kNoKey if the CDM did not have the necessary decryption key + // to decrypt. + // Returns kDecryptError if any other error happened. + // If the return value is not kSuccess, |decrypted_buffer| should be ignored + // by the caller. + virtual Status Decrypt(const InputBuffer& encrypted_buffer, + DecryptedBlock* decrypted_buffer) = 0; + + // Initializes the CDM audio decoder with |audio_decoder_config|. This + // function must be called before DecryptAndDecodeSamples() is called. + // + // Returns kSuccess if the |audio_decoder_config| is supported and the CDM + // audio decoder is successfully initialized. + // Returns kSessionError if |audio_decoder_config| is not supported. The CDM + // may still be able to do Decrypt(). + // Returns kDeferredInitialization if the CDM is not ready to initialize the + // decoder at this time. Must call Host::OnDeferredInitializationDone() once + // initialization is complete. + virtual Status InitializeAudioDecoder( + const AudioDecoderConfig& audio_decoder_config) = 0; + + // Initializes the CDM video decoder with |video_decoder_config|. This + // function must be called before DecryptAndDecodeFrame() is called. + // + // Returns kSuccess if the |video_decoder_config| is supported and the CDM + // video decoder is successfully initialized. + // Returns kSessionError if |video_decoder_config| is not supported. The CDM + // may still be able to do Decrypt(). + // Returns kDeferredInitialization if the CDM is not ready to initialize the + // decoder at this time. Must call Host::OnDeferredInitializationDone() once + // initialization is complete. + virtual Status InitializeVideoDecoder( + const VideoDecoderConfig& video_decoder_config) = 0; + + // De-initializes the CDM decoder and sets it to an uninitialized state. The + // caller can initialize the decoder again after this call to re-initialize + // it. This can be used to reconfigure the decoder if the configuration + // changes. + virtual void DeinitializeDecoder(StreamType decoder_type) = 0; + + // Resets the CDM decoder to an initialized clean state. All internal buffers + // MUST be flushed. + virtual void ResetDecoder(StreamType decoder_type) = 0; + + // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into a + // |video_frame|. Upon end-of-stream, the caller should call this function + // repeatedly with empty |encrypted_buffer| (|data| == NULL) until only empty + // |video_frame| (|format| == kEmptyVideoFrame) is produced. + // + // Returns kSuccess if decryption and decoding both succeeded, in which case + // the callee will have filled the |video_frame| and passed the ownership of + // |frame_buffer| in |video_frame| to the caller. + // Returns kNoKey if the CDM did not have the necessary decryption key + // to decrypt. + // Returns kNeedMoreData if more data was needed by the decoder to generate + // a decoded frame (e.g. during initialization and end-of-stream). + // Returns kDecryptError if any decryption error happened. + // Returns kDecodeError if any decoding error happened. + // If the return value is not kSuccess, |video_frame| should be ignored by + // the caller. + virtual Status DecryptAndDecodeFrame(const InputBuffer& encrypted_buffer, + VideoFrame* video_frame) = 0; + + // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into + // |audio_frames|. Upon end-of-stream, the caller should call this function + // repeatedly with empty |encrypted_buffer| (|data| == NULL) until only empty + // |audio_frames| is produced. + // + // Returns kSuccess if decryption and decoding both succeeded, in which case + // the callee will have filled |audio_frames| and passed the ownership of + // |data| in |audio_frames| to the caller. + // Returns kNoKey if the CDM did not have the necessary decryption key + // to decrypt. + // Returns kNeedMoreData if more data was needed by the decoder to generate + // audio samples (e.g. during initialization and end-of-stream). + // Returns kDecryptError if any decryption error happened. + // Returns kDecodeError if any decoding error happened. + // If the return value is not kSuccess, |audio_frames| should be ignored by + // the caller. + virtual Status DecryptAndDecodeSamples(const InputBuffer& encrypted_buffer, + AudioFrames* audio_frames) = 0; + + // Called by the host after a platform challenge was initiated via + // Host::SendPlatformChallenge(). + virtual void OnPlatformChallengeResponse( + const PlatformChallengeResponse& response) = 0; + + // Called by the host after a call to Host::QueryOutputProtectionStatus(). The + // |link_mask| is a bit mask of OutputLinkTypes and |output_protection_mask| + // is a bit mask of OutputProtectionMethods. If |result| is kQueryFailed, + // then |link_mask| and |output_protection_mask| are undefined and should + // be ignored. + virtual void OnQueryOutputProtectionStatus( + QueryResult result, + uint32_t link_mask, + uint32_t output_protection_mask) = 0; + + // Destroys the object in the same context as it was created. + virtual void Destroy() = 0; + + protected: + ContentDecryptionModule_8() {} + virtual ~ContentDecryptionModule_8() {} +}; + +typedef ContentDecryptionModule_8 ContentDecryptionModule; + +// Represents a buffer created by Allocator implementations. +class Buffer { + public: + // Destroys the buffer in the same context as it was created. + virtual void Destroy() = 0; + + virtual uint32_t Capacity() const = 0; + virtual uint8_t* Data() = 0; + virtual void SetSize(uint32_t size) = 0; + virtual uint32_t Size() const = 0; + + protected: + Buffer() {} + virtual ~Buffer() {} + + private: + Buffer(const Buffer&); + void operator=(const Buffer&); +}; + +class Host_7 { + public: + static const int kVersion = 7; + + // Returns a Buffer* containing non-zero members upon success, or NULL on + // failure. The caller owns the Buffer* after this call. The buffer is not + // guaranteed to be zero initialized. The capacity of the allocated Buffer + // is guaranteed to be not less than |capacity|. + virtual Buffer* Allocate(uint32_t capacity) = 0; + + // Requests the host to call ContentDecryptionModule::TimerFired() |delay_ms| + // from now with |context|. + virtual void SetTimer(int64_t delay_ms, void* context) = 0; + + // Returns the current wall time in seconds. + virtual Time GetCurrentWallTime() = 0; + + // Called by the CDM when a session is created or loaded and the value for the + // MediaKeySession's sessionId attribute is available (|session_id|). + // This must be called before OnSessionMessage() or + // OnSessionKeysChange() is called for the same session. |session_id_size| + // should not include null termination. + // When called in response to LoadSession(), the |session_id| must be the + // same as the |session_id| passed in LoadSession(), or NULL if the + // session could not be loaded. + virtual void OnResolveNewSessionPromise(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size) = 0; + + // Called by the CDM when a session is updated or released. + virtual void OnResolvePromise(uint32_t promise_id) = 0; + + // Called by the CDM when an error occurs as a result of one of the + // ContentDecryptionModule calls that accept a |promise_id|. + // |error| must be specified, |error_message| and |system_code| + // are optional. |error_message_size| should not include null termination. + virtual void OnRejectPromise(uint32_t promise_id, + Error error, + uint32_t system_code, + const char* error_message, + uint32_t error_message_size) = 0; + + // Called by the CDM when it has a message for session |session_id|. + // Size parameters should not include null termination. + // |legacy_destination_url| is only for supporting the prefixed EME API and + // is ignored by unprefixed EME. It should only be non-null if |message_type| + // is kLicenseRenewal. + virtual void OnSessionMessage(const char* session_id, + uint32_t session_id_size, + MessageType message_type, + const char* message, + uint32_t message_size, + const char* legacy_destination_url, + uint32_t legacy_destination_url_length) = 0; + + // Called by the CDM when there has been a change in keys or their status for + // session |session_id|. |has_additional_usable_key| should be set if a + // key is newly usable (e.g. new key available, previously expired key has + // been renewed, etc.) and the browser should attempt to resume playback. + // |key_ids| is the list of key ids for this session along with their + // current status. |key_ids_count| is the number of entries in |key_ids|. + // Size parameter for |session_id| should not include null termination. + virtual void OnSessionKeysChange(const char* session_id, + uint32_t session_id_size, + bool has_additional_usable_key, + const KeyInformation* keys_info, + uint32_t keys_info_count) = 0; + + // Called by the CDM when there has been a change in the expiration time for + // session |session_id|. This can happen as the result of an Update() call + // or some other event. If this happens as a result of a call to Update(), + // it must be called before resolving the Update() promise. |new_expiry_time| + // can be 0 to represent "undefined". Size parameter should not include + // null termination. + virtual void OnExpirationChange(const char* session_id, + uint32_t session_id_size, + Time new_expiry_time) = 0; + + // Called by the CDM when session |session_id| is closed. Size + // parameter should not include null termination. + virtual void OnSessionClosed(const char* session_id, + uint32_t session_id_size) = 0; + + // Called by the CDM when an error occurs in session |session_id| + // unrelated to one of the ContentDecryptionModule calls that accept a + // |promise_id|. |error| must be specified, |error_message| and + // |system_code| are optional. Length parameters should not include null + // termination. + // Note: + // - This method is only for supporting prefixed EME API. + // - This method will be ignored by unprefixed EME. All errors reported + // in this method should probably also be reported by one of other methods. + virtual void OnLegacySessionError( + const char* session_id, uint32_t session_id_length, + Error error, + uint32_t system_code, + const char* error_message, uint32_t error_message_length) = 0; + + // The following are optional methods that may not be implemented on all + // platforms. + + // Sends a platform challenge for the given |service_id|. |challenge| is at + // most 256 bits of data to be signed. Once the challenge has been completed, + // the host will call ContentDecryptionModule::OnPlatformChallengeResponse() + // with the signed challenge response and platform certificate. Size + // parameters should not include null termination. + virtual void SendPlatformChallenge(const char* service_id, + uint32_t service_id_size, + const char* challenge, + uint32_t challenge_size) = 0; + + // Attempts to enable output protection (e.g. HDCP) on the display link. The + // |desired_protection_mask| is a bit mask of OutputProtectionMethods. No + // status callback is issued, the CDM must call QueryOutputProtectionStatus() + // periodically to ensure the desired protections are applied. + virtual void EnableOutputProtection(uint32_t desired_protection_mask) = 0; + + // Requests the current output protection status. Once the host has the status + // it will call ContentDecryptionModule::OnQueryOutputProtectionStatus(). + virtual void QueryOutputProtectionStatus() = 0; + + // Must be called by the CDM if it returned kDeferredInitialization during + // InitializeAudioDecoder() or InitializeVideoDecoder(). + virtual void OnDeferredInitializationDone(StreamType stream_type, + Status decoder_status) = 0; + + // Creates a FileIO object from the host to do file IO operation. Returns NULL + // if a FileIO object cannot be obtained. Once a valid FileIO object is + // returned, |client| must be valid until FileIO::Close() is called. The + // CDM can call this method multiple times to operate on different files. + virtual FileIO* CreateFileIO(FileIOClient* client) = 0; + + protected: + Host_7() {} + virtual ~Host_7() {} +}; + +class Host_8 { + public: + static const int kVersion = 8; + + // Returns a Buffer* containing non-zero members upon success, or NULL on + // failure. The caller owns the Buffer* after this call. The buffer is not + // guaranteed to be zero initialized. The capacity of the allocated Buffer + // is guaranteed to be not less than |capacity|. + virtual Buffer* Allocate(uint32_t capacity) = 0; + + // Requests the host to call ContentDecryptionModule::TimerFired() |delay_ms| + // from now with |context|. + virtual void SetTimer(int64_t delay_ms, void* context) = 0; + + // Returns the current wall time in seconds. + virtual Time GetCurrentWallTime() = 0; + + // Called by the CDM when a session is created or loaded and the value for the + // MediaKeySession's sessionId attribute is available (|session_id|). + // This must be called before OnSessionMessage() or + // OnSessionKeysChange() is called for the same session. |session_id_size| + // should not include null termination. + // When called in response to LoadSession(), the |session_id| must be the + // same as the |session_id| passed in LoadSession(), or NULL if the + // session could not be loaded. + virtual void OnResolveNewSessionPromise(uint32_t promise_id, + const char* session_id, + uint32_t session_id_size) = 0; + + // Called by the CDM when a session is updated or released. + virtual void OnResolvePromise(uint32_t promise_id) = 0; + + // Called by the CDM when an error occurs as a result of one of the + // ContentDecryptionModule calls that accept a |promise_id|. + // |error| must be specified, |error_message| and |system_code| + // are optional. |error_message_size| should not include null termination. + virtual void OnRejectPromise(uint32_t promise_id, + Error error, + uint32_t system_code, + const char* error_message, + uint32_t error_message_size) = 0; + + // Called by the CDM when it has a message for session |session_id|. + // Size parameters should not include null termination. + // |legacy_destination_url| is only for supporting the prefixed EME API and + // is ignored by unprefixed EME. It should only be non-null if |message_type| + // is kLicenseRenewal. + virtual void OnSessionMessage(const char* session_id, + uint32_t session_id_size, + MessageType message_type, + const char* message, + uint32_t message_size, + const char* legacy_destination_url, + uint32_t legacy_destination_url_length) = 0; + + // Called by the CDM when there has been a change in keys or their status for + // session |session_id|. |has_additional_usable_key| should be set if a + // key is newly usable (e.g. new key available, previously expired key has + // been renewed, etc.) and the browser should attempt to resume playback. + // |key_ids| is the list of key ids for this session along with their + // current status. |key_ids_count| is the number of entries in |key_ids|. + // Size parameter for |session_id| should not include null termination. + virtual void OnSessionKeysChange(const char* session_id, + uint32_t session_id_size, + bool has_additional_usable_key, + const KeyInformation* keys_info, + uint32_t keys_info_count) = 0; + + // Called by the CDM when there has been a change in the expiration time for + // session |session_id|. This can happen as the result of an Update() call + // or some other event. If this happens as a result of a call to Update(), + // it must be called before resolving the Update() promise. |new_expiry_time| + // can be 0 to represent "undefined". Size parameter should not include + // null termination. + virtual void OnExpirationChange(const char* session_id, + uint32_t session_id_size, + Time new_expiry_time) = 0; + + // Called by the CDM when session |session_id| is closed. Size + // parameter should not include null termination. + virtual void OnSessionClosed(const char* session_id, + uint32_t session_id_size) = 0; + + // Called by the CDM when an error occurs in session |session_id| + // unrelated to one of the ContentDecryptionModule calls that accept a + // |promise_id|. |error| must be specified, |error_message| and + // |system_code| are optional. Length parameters should not include null + // termination. + // Note: + // - This method is only for supporting prefixed EME API. + // - This method will be ignored by unprefixed EME. All errors reported + // in this method should probably also be reported by one of other methods. + virtual void OnLegacySessionError( + const char* session_id, uint32_t session_id_length, + Error error, + uint32_t system_code, + const char* error_message, uint32_t error_message_length) = 0; + + // The following are optional methods that may not be implemented on all + // platforms. + + // Sends a platform challenge for the given |service_id|. |challenge| is at + // most 256 bits of data to be signed. Once the challenge has been completed, + // the host will call ContentDecryptionModule::OnPlatformChallengeResponse() + // with the signed challenge response and platform certificate. Size + // parameters should not include null termination. + virtual void SendPlatformChallenge(const char* service_id, + uint32_t service_id_size, + const char* challenge, + uint32_t challenge_size) = 0; + + // Attempts to enable output protection (e.g. HDCP) on the display link. The + // |desired_protection_mask| is a bit mask of OutputProtectionMethods. No + // status callback is issued, the CDM must call QueryOutputProtectionStatus() + // periodically to ensure the desired protections are applied. + virtual void EnableOutputProtection(uint32_t desired_protection_mask) = 0; + + // Requests the current output protection status. Once the host has the status + // it will call ContentDecryptionModule::OnQueryOutputProtectionStatus(). + virtual void QueryOutputProtectionStatus() = 0; + + // Must be called by the CDM if it returned kDeferredInitialization during + // InitializeAudioDecoder() or InitializeVideoDecoder(). + virtual void OnDeferredInitializationDone(StreamType stream_type, + Status decoder_status) = 0; + + // Creates a FileIO object from the host to do file IO operation. Returns NULL + // if a FileIO object cannot be obtained. Once a valid FileIO object is + // returned, |client| must be valid until FileIO::Close() is called. The + // CDM can call this method multiple times to operate on different files. + virtual FileIO* CreateFileIO(FileIOClient* client) = 0; + + protected: + Host_8() {} + virtual ~Host_8() {} +}; + +// Represents a decrypted block that has not been decoded. +class DecryptedBlock { + public: + virtual void SetDecryptedBuffer(Buffer* buffer) = 0; + virtual Buffer* DecryptedBuffer() = 0; + + // TODO(tomfinegan): Figure out if timestamp is really needed. If it is not, + // we can just pass Buffer pointers around. + virtual void SetTimestamp(int64_t timestamp) = 0; + virtual int64_t Timestamp() const = 0; + + protected: + DecryptedBlock() {} + virtual ~DecryptedBlock() {} +}; + +class VideoFrame { + public: + enum VideoPlane { + kYPlane = 0, + kUPlane = 1, + kVPlane = 2, + kMaxPlanes = 3, + }; + + virtual void SetFormat(VideoFormat format) = 0; + virtual VideoFormat Format() const = 0; + + virtual void SetSize(cdm::Size size) = 0; + virtual cdm::Size Size() const = 0; + + virtual void SetFrameBuffer(Buffer* frame_buffer) = 0; + virtual Buffer* FrameBuffer() = 0; + + virtual void SetPlaneOffset(VideoPlane plane, uint32_t offset) = 0; + virtual uint32_t PlaneOffset(VideoPlane plane) = 0; + + virtual void SetStride(VideoPlane plane, uint32_t stride) = 0; + virtual uint32_t Stride(VideoPlane plane) = 0; + + virtual void SetTimestamp(int64_t timestamp) = 0; + virtual int64_t Timestamp() const = 0; + + protected: + VideoFrame() {} + virtual ~VideoFrame() {} +}; + +// Represents decrypted and decoded audio frames. AudioFrames can contain +// multiple audio output buffers, which are serialized into this format: +// +// |<------------------- serialized audio buffer ------------------->| +// | int64_t timestamp | int64_t length | length bytes of audio data | +// +// For example, with three audio output buffers, the AudioFrames will look +// like this: +// +// |<----------------- AudioFrames ------------------>| +// | audio buffer 0 | audio buffer 1 | audio buffer 2 | +class AudioFrames { + public: + virtual void SetFrameBuffer(Buffer* buffer) = 0; + virtual Buffer* FrameBuffer() = 0; + + // The CDM must call this method, providing a valid format, when providing + // frame buffers. Planar data should be stored end to end; e.g., + // |ch1 sample1||ch1 sample2|....|ch1 sample_last||ch2 sample1|... + virtual void SetFormat(AudioFormat format) = 0; + virtual AudioFormat Format() const = 0; + + protected: + AudioFrames() {} + virtual ~AudioFrames() {} +}; + +} // namespace cdm + +#endif // CDM_CONTENT_DECRYPTION_MODULE_H_ diff --git a/dom/media/gmp/widevine-adapter/moz.build b/dom/media/gmp/widevine-adapter/moz.build new file mode 100644 index 000000000..a689a6393 --- /dev/null +++ b/dom/media/gmp/widevine-adapter/moz.build @@ -0,0 +1,25 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +SOURCES += [ + 'WidevineAdapter.cpp', + 'WidevineDecryptor.cpp', + 'WidevineFileIO.cpp', + 'WidevineUtils.cpp', + 'WidevineVideoDecoder.cpp', + 'WidevineVideoFrame.cpp', +] + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '/dom/media/gmp', +] + +if CONFIG['CLANG_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] + +include('/ipc/chromium/chromium-config.mozbuild') |