Backed out 3 changesets (bug 1318965) for frequent media test failures a=backout

Backed out changeset 3f756d8ee4cf (bug 1318965)
Backed out changeset 4bdf65d60c9e (bug 1318965)
Backed out changeset c1e2b6c14a7f (bug 1318965)

MozReview-Commit-ID: 6CPk5oS5AOw

--HG--
extra : source : babe3f8a0258fb592e17a590450de6ceb09460c3
This commit is contained in:
Wes Kocher 2017-01-13 13:23:31 -08:00
parent 565fdbe5d8
commit 07fd8cebf7
30 changed files with 3318 additions and 1485 deletions

View File

@ -72,7 +72,6 @@ EXPORTS += [
'GMPVideoHost.h',
'GMPVideoi420FrameImpl.h',
'GMPVideoPlaneImpl.h',
'widevine-adapter/content_decryption_module.h',
]
# We link GMPLoader into xul on Android and Linux as its code does not
@ -120,7 +119,7 @@ UNIFIED_SOURCES += [
'GMPVideoEncoderParent.cpp',
'GMPVideoHost.cpp',
'GMPVideoi420FrameImpl.cpp',
'GMPVideoPlaneImpl.cpp'
'GMPVideoPlaneImpl.cpp',
]
DIRS += [

View File

@ -313,17 +313,6 @@ WidevineDecryptor::OnResolveNewSessionPromise(uint32_t aPromiseId,
Log("Decryptor::OnResolveNewSessionPromise(aPromiseId=0x%d) FAIL; !mCallback", aPromiseId);
return;
}
// This is laid out in the API. If we fail to load a session we should
// call OnResolveNewSessionPromise with nullptr as the sessionId.
// We can safely assume this means that we have failed to load a session
// as the other methods specify calling 'OnRejectPromise' when they fail.
if (!aSessionId) {
Log("Decryptor::OnResolveNewSessionPromise(aPromiseId=0x%d) Failed to load session", aPromiseId);
mCallback->ResolveLoadSessionPromise(aPromiseId, false);
return;
}
Log("Decryptor::OnResolveNewSessionPromise(aPromiseId=0x%d)", aPromiseId);
auto iter = mPromiseIdToNewSessionTokens.find(aPromiseId);
if (iter == mPromiseIdToNewSessionTokens.end()) {

View File

@ -0,0 +1,79 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "AnnexB.h"
#include "BigEndian.h"
#include <cstring>
using mozilla::BigEndian;
static const uint8_t kAnnexBDelimiter[] = { 0, 0, 0, 1 };
/* static */ void
AnnexB::ConvertFrameInPlace(std::vector<uint8_t>& aBuffer)
{
for (size_t i = 0; i < aBuffer.size() - 4 - sizeof(kAnnexBDelimiter) + 1; ) {
uint32_t nalLen = BigEndian::readUint32(&aBuffer[i]);
memcpy(&aBuffer[i], kAnnexBDelimiter, sizeof(kAnnexBDelimiter));
i += nalLen + 4;
}
}
static void
ConvertParamSetToAnnexB(std::vector<uint8_t>::const_iterator& aIter,
size_t aCount,
std::vector<uint8_t>& aOutAnnexB)
{
for (size_t i = 0; i < aCount; i++) {
aOutAnnexB.insert(aOutAnnexB.end(), kAnnexBDelimiter,
kAnnexBDelimiter + sizeof(kAnnexBDelimiter));
uint16_t len = BigEndian::readUint16(&*aIter); aIter += 2;
aOutAnnexB.insert(aOutAnnexB.end(), aIter, aIter + len); aIter += len;
}
}
/* static */ void
AnnexB::ConvertConfig(const std::vector<uint8_t>& aBuffer,
std::vector<uint8_t>& aOutAnnexB)
{
// Skip past irrelevant headers
auto it = aBuffer.begin() + 5;
if (it >= aBuffer.end()) {
return;
}
size_t count = *(it++) & 31;
// Check that we have enough bytes for the Annex B conversion
// and the next size field. Bail if not.
if (it + count * 2 >= aBuffer.end()) {
return;
}
ConvertParamSetToAnnexB(it, count, aOutAnnexB);
// Check that we have enough bytes for the Annex B conversion.
count = *(it++);
if (it + count * 2 > aBuffer.end()) {
aOutAnnexB.clear();
return;
}
ConvertParamSetToAnnexB(it, count, aOutAnnexB);
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __AnnexB_h__
#define __AnnexB_h__
#include <cstdint>
#include <vector>
class AnnexB
{
public:
static void ConvertFrameInPlace(std::vector<uint8_t>& aBuffer);
static void ConvertConfig(const std::vector<uint8_t>& aBuffer,
std::vector<uint8_t>& aOutAnnexB);
};
#endif // __AnnexB_h__

View File

@ -0,0 +1,45 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ClearKeyAsyncShutdown.h"
#include "gmp-task-utils.h"
ClearKeyAsyncShutdown::ClearKeyAsyncShutdown(GMPAsyncShutdownHost *aHostAPI)
: mHost(aHostAPI)
{
CK_LOGD("ClearKeyAsyncShutdown::ClearKeyAsyncShutdown");
AddRef();
}
ClearKeyAsyncShutdown::~ClearKeyAsyncShutdown()
{
CK_LOGD("ClearKeyAsyncShutdown::~ClearKeyAsyncShutdown");
}
void ShutdownTask(ClearKeyAsyncShutdown* aSelf, GMPAsyncShutdownHost* aHost)
{
// Dumb implementation that just immediately reports completion.
// Real GMPs should ensure they are properly shutdown.
CK_LOGD("ClearKeyAsyncShutdown::BeginShutdown calling ShutdownComplete");
aHost->ShutdownComplete();
aSelf->Release();
}
void ClearKeyAsyncShutdown::BeginShutdown()
{
CK_LOGD("ClearKeyAsyncShutdown::BeginShutdown dispatching asynchronous shutdown task");
GetPlatform()->runonmainthread(WrapTaskNM(ShutdownTask, this, mHost));
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __ClearKeyAsyncShutdown_h__
#define __ClearKeyAsyncShutdown_h__
#include "gmp-api/gmp-async-shutdown.h"
#include "RefCounted.h"
class ClearKeyAsyncShutdown : public GMPAsyncShutdown
, public RefCounted
{
public:
explicit ClearKeyAsyncShutdown(GMPAsyncShutdownHost *aHostAPI);
void BeginShutdown() override;
private:
virtual ~ClearKeyAsyncShutdown();
GMPAsyncShutdownHost* mHost;
};
#endif // __ClearKeyAsyncShutdown_h__

View File

@ -1,195 +0,0 @@
#include "ClearKeyCDM.h"
#include "ClearKeyUtils.h"
using namespace cdm;
ClearKeyCDM::ClearKeyCDM(Host_8* aHost)
{
mHost = aHost;
mSessionManager = new ClearKeySessionManager(mHost);
}
void
ClearKeyCDM::Initialize(bool aAllowDistinctiveIdentifier,
bool aAllowPersistentState)
{
mSessionManager->Init(aAllowDistinctiveIdentifier,
aAllowPersistentState);
#ifdef ENABLE_WMF
mVideoDecoder = new VideoDecoder(mHost);
#endif
}
void
ClearKeyCDM::SetServerCertificate(uint32_t aPromiseId,
const uint8_t* aServerCertificateData,
uint32_t aServerCertificateDataSize)
{
mSessionManager->SetServerCertificate(aPromiseId,
aServerCertificateData,
aServerCertificateDataSize);
}
void
ClearKeyCDM::CreateSessionAndGenerateRequest(uint32_t aPromiseId,
SessionType aSessionType,
InitDataType aInitDataType,
const uint8_t* aInitData,
uint32_t aInitDataSize)
{
mSessionManager->CreateSession(aPromiseId,
aInitDataType,
aInitData,
aInitDataSize,
aSessionType);
}
void
ClearKeyCDM::LoadSession(uint32_t aPromiseId,
SessionType aSessionType,
const char* aSessionId,
uint32_t aSessionIdSize)
{
mSessionManager->LoadSession(aPromiseId,
aSessionId,
aSessionIdSize);
}
void
ClearKeyCDM::UpdateSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdSize,
const uint8_t* aResponse,
uint32_t aResponseSize)
{
mSessionManager->UpdateSession(aPromiseId,
aSessionId,
aSessionIdSize,
aResponse,
aResponseSize);
}
void
ClearKeyCDM::CloseSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdSize)
{
mSessionManager->CloseSession(aPromiseId,
aSessionId,
aSessionIdSize);
}
void
ClearKeyCDM::RemoveSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdSize)
{
mSessionManager->RemoveSession(aPromiseId,
aSessionId,
aSessionIdSize);
}
void
ClearKeyCDM::TimerExpired(void* aContext)
{
// Clearkey is not interested in timers, so this method has not been
// implemented.
assert(false);
}
Status
ClearKeyCDM::Decrypt(const InputBuffer& aEncryptedBuffer,
DecryptedBlock* aDecryptedBuffer)
{
return mSessionManager->Decrypt(aEncryptedBuffer, aDecryptedBuffer);
}
Status
ClearKeyCDM::InitializeAudioDecoder(
const AudioDecoderConfig& aAudioDecoderConfig)
{
// Audio decoding is not supported by Clearkey because Widevine doesn't
// support it and Clearkey's raison d'etre is to provide test coverage
// for paths that Widevine will exercise in the wild.
return Status::kDecodeError;
}
Status
ClearKeyCDM::InitializeVideoDecoder(
const VideoDecoderConfig& aVideoDecoderConfig)
{
#ifdef ENABLE_WMF
return mVideoDecoder->InitDecode(aVideoDecoderConfig);
#else
return Status::kDecodeError;
#endif
}
void
ClearKeyCDM::DeinitializeDecoder(StreamType aDecoderType)
{
#ifdef ENABLE_WMF
if (aDecoderType == StreamType::kStreamTypeVideo) {
mVideoDecoder->Reset();
}
#endif
}
void
ClearKeyCDM::ResetDecoder(StreamType aDecoderType)
{
#ifdef ENABLE_WMF
if (aDecoderType == StreamType::kStreamTypeVideo) {
mVideoDecoder->Reset();
}
#endif
}
Status
ClearKeyCDM::DecryptAndDecodeFrame(const InputBuffer& aEncryptedBuffer,
VideoFrame* aVideoFrame)
{
#ifdef ENABLE_WMF
return mVideoDecoder->Decode(aEncryptedBuffer, aVideoFrame);
#else
return Status::kDecodeError;
#endif
}
Status
ClearKeyCDM::DecryptAndDecodeSamples(const InputBuffer& aEncryptedBuffer,
AudioFrames* aAudioFrame)
{
// Audio decoding is not supported by Clearkey because Widevine doesn't
// support it and Clearkey's raison d'etre is to provide test coverage
// for paths that Widevine will exercise in the wild.
return Status::kDecodeError;
}
void
ClearKeyCDM::OnPlatformChallengeResponse(
const PlatformChallengeResponse& aResponse)
{
// This function should never be called and is not supported.
assert(false);
}
void
ClearKeyCDM::OnQueryOutputProtectionStatus(QueryResult aResult,
uint32_t aLinkMask,
uint32_t aOutputProtectionMask)
{
// This function should never be called and is not supported.
assert(false);
}
void
ClearKeyCDM::Destroy()
{
mSessionManager->DecryptingComplete();
#ifdef ENABLE_WMF
mVideoDecoder->DecodingComplete();
#endif
}

View File

@ -1,98 +0,0 @@
#ifndef ClearKeyCDM_h_
#define ClearKeyCDM_h_
#include "ClearKeySessionManager.h"
// This include is required in order for content_decryption_module to work
// on Unix systems.
#include "stddef.h"
#include "content_decryption_module.h"
#ifdef ENABLE_WMF
#include "WMFUtils.h"
#include "VideoDecoder.h"
#endif
class ClearKeyCDM : public cdm::ContentDecryptionModule_8
{
private:
RefPtr<ClearKeySessionManager> mSessionManager;
#ifdef ENABLE_WMF
RefPtr<VideoDecoder> mVideoDecoder;
#endif
protected:
cdm::Host_8* mHost;
public:
explicit ClearKeyCDM(cdm::Host_8* mHost);
void Initialize(bool aAllowDistinctiveIdentifier,
bool aAllowPersistentState) override;
void SetServerCertificate(uint32_t aPromiseId,
const uint8_t* aServerCertificateData,
uint32_t aServerCertificateDataSize)
override;
void CreateSessionAndGenerateRequest(uint32_t aPromiseId,
cdm::SessionType aSessionType,
cdm::InitDataType aInitDataType,
const uint8_t* aInitData,
uint32_t aInitDataSize)
override;
void LoadSession(uint32_t aPromiseId,
cdm::SessionType aSessionType,
const char* aSessionId,
uint32_t aSessionIdSize) override;
void UpdateSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdSize,
const uint8_t* aResponse,
uint32_t aResponseSize) override;
void CloseSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdSize) override;
void RemoveSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdSize) override;
void TimerExpired(void* aContext) override;
cdm::Status Decrypt(const cdm::InputBuffer& aEncryptedBuffer,
cdm::DecryptedBlock* aDecryptedBuffer) override;
cdm::Status InitializeAudioDecoder(
const cdm::AudioDecoderConfig& aAudioDecoderConfig) override;
cdm::Status InitializeVideoDecoder(
const cdm::VideoDecoderConfig& aVideoDecoderConfig) override;
void DeinitializeDecoder(cdm::StreamType aDecoderType) override;
void ResetDecoder(cdm::StreamType aDecoderType) override;
cdm::Status DecryptAndDecodeFrame(
const cdm::InputBuffer& aEncryptedBuffer,
cdm::VideoFrame* aVideoFrame) override;
cdm::Status DecryptAndDecodeSamples(
const cdm::InputBuffer& aEncryptedBuffer,
cdm::AudioFrames* aAudioFrame) override;
void OnPlatformChallengeResponse(
const cdm::PlatformChallengeResponse& aResponse) override;
void
OnQueryOutputProtectionStatus(cdm::QueryResult aResult,
uint32_t aLinkMask,
uint32_t aOutputProtectionMask) override;
void Destroy() override;
};
#endif

View File

@ -14,15 +14,13 @@
* limitations under the License.
*/
#include "ClearKeyDecryptionManager.h"
#include "psshparser/PsshParser.h"
#include <assert.h>
#include <string.h>
#include <vector>
using namespace cdm;
#include "ClearKeyDecryptionManager.h"
#include "psshparser/PsshParser.h"
#include "gmp-api/gmp-decryption.h"
#include <assert.h>
class ClearKeyDecryptor : public RefCounted
{
@ -32,7 +30,7 @@ public:
void InitKey(const Key& aKey);
bool HasKey() const { return !!mKey.size(); }
Status Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
GMPErr Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
const CryptoMetaData& aMetadata);
const Key& DecryptionKey() const { return mKey; }
@ -44,8 +42,7 @@ private:
};
/* static */ ClearKeyDecryptionManager*
ClearKeyDecryptionManager::sInstance = nullptr;
/* static */ ClearKeyDecryptionManager* ClearKeyDecryptionManager::sInstance = nullptr;
/* static */ ClearKeyDecryptionManager*
ClearKeyDecryptionManager::Get()
@ -76,17 +73,14 @@ ClearKeyDecryptionManager::~ClearKeyDecryptionManager()
bool
ClearKeyDecryptionManager::HasSeenKeyId(const KeyId& aKeyId) const
{
CK_LOGD("ClearKeyDecryptionManager::SeenKeyId %s",
mDecryptors.find(aKeyId) != mDecryptors.end() ? "t" : "f");
CK_LOGD("ClearKeyDecryptionManager::SeenKeyId %s", mDecryptors.find(aKeyId) != mDecryptors.end() ? "t" : "f");
return mDecryptors.find(aKeyId) != mDecryptors.end();
}
bool
ClearKeyDecryptionManager::IsExpectingKeyForKeyId(const KeyId& aKeyId) const
{
CK_LOGARRAY("ClearKeyDecryptionManager::IsExpectingKeyForId ",
aKeyId.data(),
aKeyId.size());
CK_LOGD("ClearKeyDecryptionManager::IsExpectingKeyForId %08x...", *(uint32_t*)&aKeyId[0]);
const auto& decryptor = mDecryptors.find(aKeyId);
return decryptor != mDecryptors.end() && !decryptor->second->HasKey();
}
@ -109,23 +103,16 @@ ClearKeyDecryptionManager::GetDecryptionKey(const KeyId& aKeyId)
void
ClearKeyDecryptionManager::InitKey(KeyId aKeyId, Key aKey)
{
CK_LOGD("ClearKeyDecryptionManager::InitKey ",
aKeyId.data(),
aKeyId.size());
CK_LOGD("ClearKeyDecryptionManager::InitKey %08x...", *(uint32_t*)&aKeyId[0]);
if (IsExpectingKeyForKeyId(aKeyId)) {
CK_LOGARRAY("Initialized Key ", aKeyId.data(), aKeyId.size());
mDecryptors[aKeyId]->InitKey(aKey);
} else {
CK_LOGARRAY("Failed to initialize key ", aKeyId.data(), aKeyId.size());
}
}
void
ClearKeyDecryptionManager::ExpectKeyId(KeyId aKeyId)
{
CK_LOGD("ClearKeyDecryptionManager::ExpectKeyId ",
aKeyId.data(),
aKeyId.size());
CK_LOGD("ClearKeyDecryptionManager::ExpectKeyId %08x...", *(uint32_t*)&aKeyId[0]);
if (!HasSeenKeyId(aKeyId)) {
mDecryptors[aKeyId] = new ClearKeyDecryptor();
}
@ -144,31 +131,23 @@ ClearKeyDecryptionManager::ReleaseKeyId(KeyId aKeyId)
}
}
Status
GMPErr
ClearKeyDecryptionManager::Decrypt(std::vector<uint8_t>& aBuffer,
const CryptoMetaData& aMetadata)
{
return Decrypt(&aBuffer[0], aBuffer.size(), aMetadata);
}
Status
GMPErr
ClearKeyDecryptionManager::Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
const CryptoMetaData& aMetadata)
{
CK_LOGD("ClearKeyDecryptionManager::Decrypt");
if (!HasKeyForKeyId(aMetadata.mKeyId)) {
CK_LOGARRAY("Unable to find decryptor for keyId: ",
aMetadata.mKeyId.data(),
aMetadata.mKeyId.size());
return Status::kNoKey;
return GMPNoKeyErr;
}
CK_LOGARRAY("Found decryptor for keyId: ",
aMetadata.mKeyId.data(),
aMetadata.mKeyId.size());
return mDecryptors[aMetadata.mKeyId]->Decrypt(aBuffer,
aBufferSize,
aMetadata);
return mDecryptors[aMetadata.mKeyId]->Decrypt(aBuffer, aBufferSize, aMetadata);
}
ClearKeyDecryptor::ClearKeyDecryptor()
@ -179,9 +158,7 @@ ClearKeyDecryptor::ClearKeyDecryptor()
ClearKeyDecryptor::~ClearKeyDecryptor()
{
if (HasKey()) {
CK_LOGARRAY("ClearKeyDecryptor dtor; key = ",
mKey.data(),
mKey.size());
CK_LOGD("ClearKeyDecryptor dtor; key = %08x...", *(uint32_t*)&mKey[0]);
} else {
CK_LOGD("ClearKeyDecryptor dtor");
}
@ -193,7 +170,7 @@ ClearKeyDecryptor::InitKey(const Key& aKey)
mKey = aKey;
}
Status
GMPErr
ClearKeyDecryptor::Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
const CryptoMetaData& aMetadata)
{
@ -212,7 +189,7 @@ ClearKeyDecryptor::Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
uint32_t cipherBytes = aMetadata.mCipherBytes[i];
if (data + cipherBytes > aBuffer + aBufferSize) {
// Trying to read past the end of the buffer!
return Status::kDecryptError;
return GMPCryptoErr;
}
memcpy(iter, data, cipherBytes);
@ -250,5 +227,5 @@ ClearKeyDecryptor::Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
memcpy(aBuffer, &tmp[0], aBufferSize);
}
return Status::kSuccess;
return GMPNoErr;
}

View File

@ -17,43 +17,32 @@
#ifndef __ClearKeyDecryptionManager_h__
#define __ClearKeyDecryptionManager_h__
#include "ClearKeyUtils.h"
// This include is required in order for content_decryption_module to work
// on Unix systems.
#include "stddef.h"
#include "content_decryption_module.h"
#include "RefCounted.h"
#include <map>
#include "ClearKeyUtils.h"
#include "RefCounted.h"
class ClearKeyDecryptor;
class CryptoMetaData
{
class CryptoMetaData {
public:
CryptoMetaData() {}
explicit CryptoMetaData(const cdm::InputBuffer* aInputBuffer)
explicit CryptoMetaData(const GMPEncryptedBufferMetadata* aCrypto)
{
Init(aInputBuffer);
Init(aCrypto);
}
void Init(const cdm::InputBuffer* aInputBuffer)
void Init(const GMPEncryptedBufferMetadata* aCrypto)
{
if (!aInputBuffer) {
if (!aCrypto) {
assert(!IsValid());
return;
}
Assign(mKeyId, aInputBuffer->key_id, aInputBuffer->key_id_size);
Assign(mIV, aInputBuffer->iv, aInputBuffer->iv_size);
for (uint32_t i = 0; i < aInputBuffer->num_subsamples; ++i) {
const cdm::SubsampleEntry& subsample = aInputBuffer->subsamples[i];
mCipherBytes.push_back(subsample.cipher_bytes);
mClearBytes.push_back(subsample.clear_bytes);
}
Assign(mKeyId, aCrypto->KeyId(), aCrypto->KeyIdSize());
Assign(mIV, aCrypto->IV(), aCrypto->IVSize());
Assign(mClearBytes, aCrypto->ClearBytes(), aCrypto->NumSubsamples());
Assign(mCipherBytes, aCrypto->CipherBytes(), aCrypto->NumSubsamples());
}
bool IsValid() const {
@ -70,7 +59,7 @@ public:
std::vector<uint8_t> mKeyId;
std::vector<uint8_t> mIV;
std::vector<uint32_t> mClearBytes;
std::vector<uint16_t> mClearBytes;
std::vector<uint32_t> mCipherBytes;
};
@ -96,10 +85,12 @@ public:
void ReleaseKeyId(KeyId aKeyId);
// Decrypts buffer *in place*.
cdm::Status Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
const CryptoMetaData& aMetadata);
cdm::Status Decrypt(std::vector<uint8_t>& aBuffer,
const CryptoMetaData& aMetadata);
GMPErr Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
const CryptoMetaData& aMetadata);
GMPErr Decrypt(std::vector<uint8_t>& aBuffer,
const CryptoMetaData& aMetadata);
void Shutdown();
private:
bool IsExpectingKeyForKeyId(const KeyId& aKeyId) const;

View File

@ -15,123 +15,81 @@
*/
#include "ClearKeyPersistence.h"
#include "ClearKeyUtils.h"
#include "ClearKeyStorage.h"
#include "ClearKeySessionManager.h"
#include "RefCounted.h"
#include <assert.h>
#include <stdint.h>
#include <sstream>
#include <string.h>
#include <set>
#include <vector>
#include <sstream>
#include <assert.h>
using namespace std;
using namespace cdm;
void
ClearKeyPersistence::ReadAllRecordsFromIndex(function<void()>&& aOnComplete) {
// Clear what we think the index file contains, we're about to read it again.
mPersistentSessionIds.clear();
// Whether we've loaded the persistent session ids from GMPStorage yet.
enum PersistentKeyState {
UNINITIALIZED,
LOADING,
LOADED
};
static PersistentKeyState sPersistentKeyState = UNINITIALIZED;
// Hold a reference to the persistence manager, so it isn't released before
// we try and use it.
RefPtr<ClearKeyPersistence> self(this);
function<void(const uint8_t*, uint32_t)> onIndexSuccess =
[self, aOnComplete] (const uint8_t* data, uint32_t size)
{
CK_LOGD("ClearKeyPersistence: Loaded index file!");
const char* charData = (const char*)data;
// Set of session Ids of the persistent sessions created or residing in
// storage.
static set<uint32_t> sPersistentSessionIds;
stringstream ss(string(charData, charData + size));
string name;
while (getline(ss, name)) {
if (ClearKeyUtils::IsValidSessionId(name.data(), name.size())) {
self->mPersistentSessionIds.insert(atoi(name.c_str()));
static vector<GMPTask*> sTasksBlockedOnSessionIdLoad;
static void
ReadAllRecordsFromIterator(GMPRecordIterator* aRecordIterator,
void* aUserArg,
GMPErr aStatus)
{
assert(sPersistentKeyState == LOADING);
if (GMP_SUCCEEDED(aStatus)) {
// Extract the record names which are valid uint32_t's; they're
// the persistent session ids.
const char* name = nullptr;
uint32_t len = 0;
while (GMP_SUCCEEDED(aRecordIterator->GetName(&name, &len))) {
if (ClearKeyUtils::IsValidSessionId(name, len)) {
assert(name[len] == 0);
sPersistentSessionIds.insert(atoi(name));
}
aRecordIterator->NextRecord();
}
self->mPersistentKeyState = PersistentKeyState::LOADED;
aOnComplete();
};
function<void()> onIndexFailed =
[self, aOnComplete] ()
{
CK_LOGD("ClearKeyPersistence: Failed to load index file (it might not exist");
self->mPersistentKeyState = PersistentKeyState::LOADED;
aOnComplete();
};
string filename = "index";
ReadData(mHost, filename, move(onIndexSuccess), move(onIndexFailed));
}
void
ClearKeyPersistence::WriteIndex() {
function <void()> onIndexSuccess =
[] ()
{
CK_LOGD("ClearKeyPersistence: Wrote index file");
};
function <void()> onIndexFail =
[] ()
{
CK_LOGD("ClearKeyPersistence: Failed to write index file (this is bad)");
};
stringstream ss;
for (const uint32_t& sessionId : mPersistentSessionIds) {
ss << sessionId;
ss << '\n';
}
sPersistentKeyState = LOADED;
aRecordIterator->Close();
string dataString = ss.str();
uint8_t* dataArray = (uint8_t*)dataString.data();
vector<uint8_t> data(dataArray, dataArray + dataString.size());
string filename = "index";
WriteData(mHost,
filename,
data,
move(onIndexSuccess),
move(onIndexFail));
for (size_t i = 0; i < sTasksBlockedOnSessionIdLoad.size(); i++) {
sTasksBlockedOnSessionIdLoad[i]->Run();
sTasksBlockedOnSessionIdLoad[i]->Destroy();
}
sTasksBlockedOnSessionIdLoad.clear();
}
ClearKeyPersistence::ClearKeyPersistence(Host_8* aHost)
/* static */ void
ClearKeyPersistence::EnsureInitialized()
{
this->mHost = aHost;
}
void
ClearKeyPersistence::EnsureInitialized(bool aPersistentStateAllowed,
function<void()>&& aOnInitialized)
{
if (aPersistentStateAllowed &&
mPersistentKeyState == PersistentKeyState::UNINITIALIZED) {
mPersistentKeyState = LOADING;
ReadAllRecordsFromIndex(move(aOnInitialized));
} else {
mPersistentKeyState = PersistentKeyState::LOADED;
aOnInitialized();
if (sPersistentKeyState == UNINITIALIZED) {
sPersistentKeyState = LOADING;
if (GMP_FAILED(EnumRecordNames(&ReadAllRecordsFromIterator))) {
sPersistentKeyState = LOADED;
}
}
}
bool ClearKeyPersistence::IsLoaded() const
{
return mPersistentKeyState == PersistentKeyState::LOADED;
}
string
ClearKeyPersistence::GetNewSessionId(SessionType aSessionType)
/* static */ string
ClearKeyPersistence::GetNewSessionId(GMPSessionType aSessionType)
{
static uint32_t sNextSessionId = 1;
// Ensure we don't re-use a session id that was persisted.
while (Contains(mPersistentSessionIds, sNextSessionId)) {
while (Contains(sPersistentSessionIds, sNextSessionId)) {
sNextSessionId++;
}
@ -140,11 +98,8 @@ ClearKeyPersistence::GetNewSessionId(SessionType aSessionType)
ss << sNextSessionId;
ss >> sessionId;
if (aSessionType == SessionType::kPersistentLicense) {
mPersistentSessionIds.insert(sNextSessionId);
// Save the updated index file.
WriteIndex();
if (aSessionType == kGMPPersistentSession) {
sPersistentSessionIds.insert(sNextSessionId);
}
sNextSessionId++;
@ -152,17 +107,154 @@ ClearKeyPersistence::GetNewSessionId(SessionType aSessionType)
return sessionId;
}
bool
class CreateSessionTask : public GMPTask {
public:
CreateSessionTask(ClearKeySessionManager* aTarget,
uint32_t aCreateSessionToken,
uint32_t aPromiseId,
const string& aInitDataType,
const uint8_t* aInitData,
uint32_t aInitDataSize,
GMPSessionType aSessionType)
: mTarget(aTarget)
, mCreateSessionToken(aCreateSessionToken)
, mPromiseId(aPromiseId)
, mInitDataType(aInitDataType)
, mSessionType(aSessionType)
{
mInitData.insert(mInitData.end(),
aInitData,
aInitData + aInitDataSize);
}
virtual void Run() override {
mTarget->CreateSession(mCreateSessionToken,
mPromiseId,
mInitDataType.c_str(),
mInitDataType.size(),
&mInitData.front(),
mInitData.size(),
mSessionType);
}
virtual void Destroy() override {
delete this;
}
private:
RefPtr<ClearKeySessionManager> mTarget;
uint32_t mCreateSessionToken;
uint32_t mPromiseId;
const string mInitDataType;
vector<uint8_t> mInitData;
GMPSessionType mSessionType;
};
/* static */ bool
ClearKeyPersistence::DeferCreateSessionIfNotReady(ClearKeySessionManager* aInstance,
uint32_t aCreateSessionToken,
uint32_t aPromiseId,
const string& aInitDataType,
const uint8_t* aInitData,
uint32_t aInitDataSize,
GMPSessionType aSessionType)
{
if (sPersistentKeyState >= LOADED) {
return false;
}
GMPTask* t = new CreateSessionTask(aInstance,
aCreateSessionToken,
aPromiseId,
aInitDataType,
aInitData,
aInitDataSize,
aSessionType);
sTasksBlockedOnSessionIdLoad.push_back(t);
return true;
}
class LoadSessionTask : public GMPTask {
public:
LoadSessionTask(ClearKeySessionManager* aTarget,
uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength)
: mTarget(aTarget)
, mPromiseId(aPromiseId)
, mSessionId(aSessionId, aSessionId + aSessionIdLength)
{
}
virtual void Run() override {
mTarget->LoadSession(mPromiseId,
mSessionId.c_str(),
mSessionId.size());
}
virtual void Destroy() override {
delete this;
}
private:
RefPtr<ClearKeySessionManager> mTarget;
uint32_t mPromiseId;
string mSessionId;
};
/* static */ bool
ClearKeyPersistence::DeferLoadSessionIfNotReady(ClearKeySessionManager* aInstance,
uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength)
{
if (sPersistentKeyState >= LOADED) {
return false;
}
GMPTask* t = new LoadSessionTask(aInstance,
aPromiseId,
aSessionId,
aSessionIdLength);
sTasksBlockedOnSessionIdLoad.push_back(t);
return true;
}
/* static */ bool
ClearKeyPersistence::IsPersistentSessionId(const string& aSessionId)
{
return Contains(mPersistentSessionIds, atoi(aSessionId.c_str()));
return Contains(sPersistentSessionIds, atoi(aSessionId.c_str()));
}
void
ClearKeyPersistence::PersistentSessionRemoved(string& aSessionId)
class LoadSessionFromKeysTask : public ReadContinuation {
public:
LoadSessionFromKeysTask(ClearKeySessionManager* aTarget,
const string& aSessionId,
uint32_t aPromiseId)
: mTarget(aTarget)
, mSessionId(aSessionId)
, mPromiseId(aPromiseId)
{
}
virtual void ReadComplete(GMPErr aStatus,
const uint8_t* aData,
uint32_t aLength) override
{
mTarget->PersistentSessionDataLoaded(aStatus, mPromiseId, mSessionId, aData, aLength);
}
private:
RefPtr<ClearKeySessionManager> mTarget;
string mSessionId;
uint32_t mPromiseId;
};
/* static */ void
ClearKeyPersistence::LoadSessionData(ClearKeySessionManager* aInstance,
const string& aSid,
uint32_t aPromiseId)
{
mPersistentSessionIds.erase(atoi(aSessionId.c_str()));
// Update the index file.
WriteIndex();
LoadSessionFromKeysTask* loadTask =
new LoadSessionFromKeysTask(aInstance, aSid, aPromiseId);
ReadData(aSid, loadTask);
}
/* static */ void
ClearKeyPersistence::PersistentSessionRemoved(const string& aSessionId)
{
sPersistentSessionIds.erase(atoi(aSessionId.c_str()));
}

View File

@ -17,51 +17,37 @@
#ifndef __ClearKeyPersistence_h__
#define __ClearKeyPersistence_h__
// This include is required in order for content_decryption_module to work
// on Unix systems.
#include "stddef.h"
#include "content_decryption_module.h"
#include "RefCounted.h"
#include <functional>
#include <set>
#include <string>
#include <vector>
#include "gmp-api/gmp-decryption.h"
class ClearKeySessionManager;
// Whether we've loaded the persistent session ids yet.
enum PersistentKeyState {
UNINITIALIZED,
LOADING,
LOADED
};
class ClearKeyPersistence : public RefCounted
{
class ClearKeyPersistence {
public:
explicit ClearKeyPersistence(cdm::Host_8* aHost);
static void EnsureInitialized();
void EnsureInitialized(bool aPersistentStateAllowed,
std::function<void()>&& aOnInitialized);
static std::string GetNewSessionId(GMPSessionType aSessionType);
bool IsLoaded() const;
static bool DeferCreateSessionIfNotReady(ClearKeySessionManager* aInstance,
uint32_t aCreateSessionToken,
uint32_t aPromiseId,
const std::string& aInitDataType,
const uint8_t* aInitData,
uint32_t aInitDataSize,
GMPSessionType aSessionType);
std::string GetNewSessionId(cdm::SessionType aSessionType);
static bool DeferLoadSessionIfNotReady(ClearKeySessionManager* aInstance,
uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength);
bool IsPersistentSessionId(const std::string& aSid);
static bool IsPersistentSessionId(const std::string& aSid);
void PersistentSessionRemoved(std::string& aSid);
private:
cdm::Host_8* mHost = nullptr;
static void LoadSessionData(ClearKeySessionManager* aInstance,
const std::string& aSid,
uint32_t aPromiseId);
PersistentKeyState mPersistentKeyState = PersistentKeyState::UNINITIALIZED;
std::set<uint32_t> mPersistentSessionIds;
void ReadAllRecordsFromIndex(std::function<void()>&& aOnComplete);
void WriteIndex();
static void PersistentSessionRemoved(const std::string& aSid);
};
#endif // __ClearKeyPersistence_h__

View File

@ -20,17 +20,18 @@
#include "ClearKeyUtils.h"
#include "ClearKeyStorage.h"
#include "psshparser/PsshParser.h"
#include "gmp-task-utils.h"
#include "gmp-api/gmp-decryption.h"
#include <assert.h>
#include <string.h>
using namespace mozilla;
using namespace cdm;
using namespace std;
ClearKeySession::ClearKeySession(const std::string& aSessionId,
SessionType aSessionType)
GMPDecryptorCallback* aCallback,
GMPSessionType aSessionType)
: mSessionId(aSessionId)
, mCallback(aCallback)
, mSessionType(aSessionType)
{
CK_LOGD("ClearKeySession ctor %p", this);
@ -39,21 +40,30 @@ ClearKeySession::ClearKeySession(const std::string& aSessionId,
ClearKeySession::~ClearKeySession()
{
CK_LOGD("ClearKeySession dtor %p", this);
std::vector<GMPMediaKeyInfo> key_infos;
for (const KeyId& keyId : mKeyIds) {
assert(ClearKeyDecryptionManager::Get()->HasSeenKeyId(keyId));
ClearKeyDecryptionManager::Get()->ReleaseKeyId(keyId);
key_infos.push_back(GMPMediaKeyInfo(&keyId[0], keyId.size(), kGMPUnknown));
}
mCallback->BatchedKeyStatusChanged(&mSessionId[0], mSessionId.size(),
key_infos.data(), key_infos.size());
}
bool
ClearKeySession::Init(InitDataType aInitDataType,
const uint8_t* aInitData,
uint32_t aInitDataSize)
void
ClearKeySession::Init(uint32_t aCreateSessionToken,
uint32_t aPromiseId,
const std::string& aInitDataType,
const uint8_t* aInitData, uint32_t aInitDataSize)
{
CK_LOGD("ClearKeySession::Init");
if (aInitDataType == InitDataType::kCenc) {
if (aInitDataType == "cenc") {
ParseCENCInitData(aInitData, aInitDataSize, mKeyIds);
} else if (aInitDataType == InitDataType::kKeyIds) {
} else if (aInitDataType == "keyids") {
ClearKeyUtils::ParseKeyIdsInitData(aInitData, aInitDataSize, mKeyIds);
} else if (aInitDataType == InitDataType::kWebM &&
aInitDataSize <= kMaxWebmInitDataSize) {
} else if (aInitDataType == "webm" && aInitDataSize <= kMaxWebmInitDataSize) {
// "webm" initData format is simply the raw bytes of the keyId.
vector<uint8_t> keyId;
keyId.assign(aInitData, aInitData+aInitDataSize);
@ -61,13 +71,17 @@ ClearKeySession::Init(InitDataType aInitDataType,
}
if (!mKeyIds.size()) {
return false;
const char message[] = "Couldn't parse init data";
mCallback->RejectPromise(aPromiseId, kGMPTypeError, message, strlen(message));
return;
}
return true;
mCallback->SetSessionId(aCreateSessionToken, &mSessionId[0], mSessionId.length());
mCallback->ResolvePromise(aPromiseId);
}
SessionType
GMPSessionType
ClearKeySession::Type() const
{
return mSessionType;

View File

@ -18,28 +18,30 @@
#define __ClearKeySession_h__
#include "ClearKeyUtils.h"
// This include is required in order for content_decryption_module to work
// on Unix systems.
#include "stddef.h"
#include "content_decryption_module.h"
#include "gmp-api/gmp-decryption.h"
#include <string>
#include <vector>
class GMPBuffer;
class GMPDecryptorCallback;
class GMPDecryptorHost;
class GMPEncryptedBufferMetadata;
class ClearKeySession
{
public:
explicit ClearKeySession(const std::string& aSessionId,
cdm::SessionType aSessionType);
GMPDecryptorCallback* aCallback,
GMPSessionType aSessionType);
~ClearKeySession();
const std::vector<KeyId>& GetKeyIds() const { return mKeyIds; }
bool Init(cdm::InitDataType aInitDataType,
void Init(uint32_t aCreateSessionToken,
uint32_t aPromiseId,
const string& aInitDataType,
const uint8_t* aInitData, uint32_t aInitDataSize);
cdm::SessionType Type() const;
GMPSessionType Type() const;
void AddKeyId(const KeyId& aKeyId);
@ -49,7 +51,8 @@ private:
const std::string mSessionId;
std::vector<KeyId> mKeyIds;
const cdm::SessionType mSessionType;
GMPDecryptorCallback* mCallback;
const GMPSessionType mSessionType;
};
#endif // __ClearKeySession_h__

View File

@ -14,33 +14,30 @@
* limitations under the License.
*/
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "ClearKeyDecryptionManager.h"
#include "ClearKeySessionManager.h"
#include "ClearKeyUtils.h"
#include "ClearKeyStorage.h"
#include "ClearKeyPersistence.h"
// This include is required in order for content_decryption_module to work
// on Unix systems.
#include "stddef.h"
#include "content_decryption_module.h"
#include "psshparser/PsshParser.h"
#include "gmp-task-utils.h"
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
using namespace std;
using namespace cdm;
ClearKeySessionManager::ClearKeySessionManager(Host_8* aHost)
ClearKeySessionManager::ClearKeySessionManager()
: mDecryptionManager(ClearKeyDecryptionManager::Get())
{
CK_LOGD("ClearKeySessionManager ctor %p", this);
AddRef();
mHost = aHost;
mPersistence = new ClearKeyPersistence(mHost);
if (GetPlatform()->createthread(&mThread) != GMPNoErr) {
CK_LOGD("failed to create thread in clearkey cdm");
mThread = nullptr;
}
}
ClearKeySessionManager::~ClearKeySessionManager()
@ -49,106 +46,56 @@ ClearKeySessionManager::~ClearKeySessionManager()
}
void
ClearKeySessionManager::Init(bool aDistinctiveIdentifierAllowed,
ClearKeySessionManager::Init(GMPDecryptorCallback* aCallback,
bool aDistinctiveIdentifierAllowed,
bool aPersistentStateAllowed)
{
CK_LOGD("ClearKeySessionManager::Init");
RefPtr<ClearKeySessionManager> self(this);
function<void()> onPersistentStateLoaded =
[self] ()
{
while (!self->mDeferredInitialize.empty()) {
function<void()> func = self->mDeferredInitialize.front();
self->mDeferredInitialize.pop();
func();
}
};
mPersistence->EnsureInitialized(aPersistentStateAllowed,
move(onPersistentStateLoaded));
mCallback = aCallback;
ClearKeyPersistence::EnsureInitialized();
}
void
ClearKeySessionManager::CreateSession(uint32_t aPromiseId,
InitDataType aInitDataType,
ClearKeySessionManager::CreateSession(uint32_t aCreateSessionToken,
uint32_t aPromiseId,
const char* aInitDataType,
uint32_t aInitDataTypeSize,
const uint8_t* aInitData,
uint32_t aInitDataSize,
SessionType aSessionType)
GMPSessionType aSessionType)
{
// Copy the init data so it is correctly captured by the lambda
vector<uint8_t> initData(aInitData, aInitData + aInitDataSize);
RefPtr<ClearKeySessionManager> self(this);
function<void()> deferrer =
[self, aPromiseId, aInitDataType, initData, aSessionType] ()
{
self->CreateSession(aPromiseId,
aInitDataType,
initData.data(),
initData.size(),
aSessionType);
};
// If we haven't loaded, don't do this yet
if (MaybeDeferTillInitialized(deferrer)) {
return;
}
CK_LOGD("ClearKeySessionManager::CreateSession type:%s", aInitDataType);
CK_LOGARRAY("ClearKeySessionManager::CreateSession initdata: ",
aInitData,
aInitDataSize);
// If 'DecryptingComplete' has been called mHost will be null so we can't
// won't be able to resolve our promise
if (!mHost) {
CK_LOGD("ClearKeySessionManager::CreateSession: mHost is nullptr")
return;
}
string initDataType(aInitDataType, aInitDataType + aInitDataTypeSize);
// initDataType must be "cenc", "keyids", or "webm".
if (aInitDataType != InitDataType::kCenc &&
aInitDataType != InitDataType::kKeyIds &&
aInitDataType != InitDataType::kWebM) {
string message = "initDataType is not supported by ClearKey";
mHost->OnRejectPromise(aPromiseId,
Error::kNotSupportedError,
0,
message.c_str(),
message.size());
if (initDataType != "cenc" &&
initDataType != "keyids" &&
initDataType != "webm") {
string message = "'" + initDataType + "' is an initDataType unsupported by ClearKey";
mCallback->RejectPromise(aPromiseId, kGMPNotSupportedError,
message.c_str(), message.size());
return;
}
string sessionId = mPersistence->GetNewSessionId(aSessionType);
if (ClearKeyPersistence::DeferCreateSessionIfNotReady(this,
aCreateSessionToken,
aPromiseId,
initDataType,
aInitData,
aInitDataSize,
aSessionType)) {
return;
}
string sessionId = ClearKeyPersistence::GetNewSessionId(aSessionType);
assert(mSessions.find(sessionId) == mSessions.end());
ClearKeySession* session = new ClearKeySession(sessionId,
aSessionType);
if (!session->Init(aInitDataType, aInitData, aInitDataSize)) {
CK_LOGD("Failed to initialize session: %s", sessionId.c_str());
const static char* message = "Failed to initialize session";
mHost->OnRejectPromise(aPromiseId,
Error::kUnknownError,
0,
message,
strlen(message));
return;
}
ClearKeySession* session = new ClearKeySession(sessionId, mCallback, aSessionType);
session->Init(aCreateSessionToken, aPromiseId, initDataType, aInitData, aInitDataSize);
mSessions[sessionId] = session;
const vector<KeyId>& sessionKeys = session->GetKeyIds();
vector<KeyId> neededKeys;
for (auto it = sessionKeys.begin(); it != sessionKeys.end(); it++) {
// Need to request this key ID from the client. We always send a key
// request, whether or not another session has sent a request with the same
@ -166,19 +113,9 @@ ClearKeySessionManager::CreateSession(uint32_t aPromiseId,
// Send a request for needed key data.
string request;
ClearKeyUtils::MakeKeyRequest(neededKeys, request, aSessionType);
// Resolve the promise with the new session information.
mHost->OnResolveNewSessionPromise(aPromiseId,
sessionId.c_str(),
sessionId.size());
mHost->OnSessionMessage(sessionId.c_str(),
sessionId.size(),
MessageType::kLicenseRequest,
request.c_str(),
request.size(),
nullptr,
0);
mCallback->SessionMessage(&sessionId[0], sessionId.length(),
kGMPLicenseRequest,
(uint8_t*)&request[0], request.length());
}
void
@ -186,90 +123,53 @@ ClearKeySessionManager::LoadSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength)
{
// Copy the sessionId into a string so the lambda captures it properly.
string sessionId(aSessionId, aSessionId + aSessionIdLength);
// Hold a reference to the SessionManager so that it isn't released before
// we try to use it.
RefPtr<ClearKeySessionManager> self(this);
function<void()> deferrer =
[self, aPromiseId, sessionId] ()
{
self->LoadSession(aPromiseId, sessionId.data(), sessionId.size());
};
if (MaybeDeferTillInitialized(deferrer)) {
return;
}
CK_LOGD("ClearKeySessionManager::LoadSession");
// If the SessionManager has been shutdown mHost will be null and we won't
// be able to resolve the promise.
if (!mHost) {
return;
}
if (!ClearKeyUtils::IsValidSessionId(aSessionId, aSessionIdLength)) {
mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
mCallback->ResolveLoadSessionPromise(aPromiseId, false);
return;
}
if (!mPersistence->IsPersistentSessionId(sessionId)) {
mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
if (ClearKeyPersistence::DeferLoadSessionIfNotReady(this,
aPromiseId,
aSessionId,
aSessionIdLength)) {
return;
}
function<void(const uint8_t*, uint32_t)> success =
[self, sessionId, aPromiseId] (const uint8_t* data, uint32_t size)
{
self->PersistentSessionDataLoaded(aPromiseId,
sessionId,
data,
size);
};
string sid(aSessionId, aSessionId + aSessionIdLength);
if (!ClearKeyPersistence::IsPersistentSessionId(sid)) {
mCallback->ResolveLoadSessionPromise(aPromiseId, false);
return;
}
function<void()> failure = [self, sessionId, aPromiseId] {
if (!self->mHost) {
return;
}
// As per the API described in ContentDecryptionModule_8
self->mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
};
ReadData(mHost, sessionId, move(success), move(failure));
// Callsback PersistentSessionDataLoaded with results...
ClearKeyPersistence::LoadSessionData(this, sid, aPromiseId);
}
void
ClearKeySessionManager::PersistentSessionDataLoaded(uint32_t aPromiseId,
ClearKeySessionManager::PersistentSessionDataLoaded(GMPErr aStatus,
uint32_t aPromiseId,
const string& aSessionId,
const uint8_t* aKeyData,
uint32_t aKeyDataSize)
{
CK_LOGD("ClearKeySessionManager::PersistentSessionDataLoaded");
// Check that the SessionManager has not been shut down before we try and
// resolve any promises.
if (!mHost) {
return;
}
if (Contains(mSessions, aSessionId) ||
if (GMP_FAILED(aStatus) ||
Contains(mSessions, aSessionId) ||
(aKeyDataSize % (2 * CENC_KEY_LEN)) != 0) {
// As per the instructions in ContentDecryptionModule_8
mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
mCallback->ResolveLoadSessionPromise(aPromiseId, false);
return;
}
ClearKeySession* session = new ClearKeySession(aSessionId,
SessionType::kPersistentLicense);
mCallback,
kGMPPersistentSession);
mSessions[aSessionId] = session;
uint32_t numKeys = aKeyDataSize / (2 * CENC_KEY_LEN);
vector<KeyInformation> keyInfos;
vector<GMPMediaKeyInfo> key_infos;
vector<KeyIdPair> keyPairs;
for (uint32_t i = 0; i < numKeys; i ++) {
const uint8_t* base = aKeyData + 2 * CENC_KEY_LEN * i;
@ -287,25 +187,16 @@ ClearKeySessionManager::PersistentSessionDataLoaded(uint32_t aPromiseId,
mDecryptionManager->ExpectKeyId(keyPair.mKeyId);
mDecryptionManager->InitKey(keyPair.mKeyId, keyPair.mKey);
mKeyIds.insert(keyPair.mKey);
keyPairs.push_back(keyPair);
KeyInformation keyInfo = KeyInformation();
keyInfo.key_id = &keyPairs.back().mKeyId[0];
keyInfo.key_id_size = keyPair.mKeyId.size();
keyInfo.status = KeyStatus::kUsable;
keyInfos.push_back(keyInfo);
key_infos.push_back(GMPMediaKeyInfo(&keyPairs[i].mKeyId[0],
keyPairs[i].mKeyId.size(),
kGMPUsable));
}
mCallback->BatchedKeyStatusChanged(&aSessionId[0], aSessionId.size(),
key_infos.data(), key_infos.size());
mHost->OnSessionKeysChange(&aSessionId[0],
aSessionId.size(),
true,
keyInfos.data(),
keyInfos.size());
mHost->OnResolveNewSessionPromise(aPromiseId,
aSessionId.c_str(),
aSessionId.size());
mCallback->ResolveLoadSessionPromise(aPromiseId, true);
}
void
@ -315,47 +206,13 @@ ClearKeySessionManager::UpdateSession(uint32_t aPromiseId,
const uint8_t* aResponse,
uint32_t aResponseSize)
{
// Copy the method arguments so we can capture them in the lambda
string sessionId(aSessionId, aSessionId + aSessionIdLength);
vector<uint8_t> response(aResponse, aResponse + aResponseSize);
// Hold a reference to the SessionManager so it isn't released before we
// callback.
RefPtr<ClearKeySessionManager> self(this);
function<void()> deferrer =
[self, aPromiseId, sessionId, response] ()
{
self->UpdateSession(aPromiseId,
sessionId.data(),
sessionId.size(),
response.data(),
response.size());
};
// If we haven't fully loaded, defer calling this method
if (MaybeDeferTillInitialized(deferrer)) {
return;
}
// Make sure the SessionManager has not been shutdown before we try and
// resolve any promises.
if (!mHost) {
return;
}
CK_LOGD("ClearKeySessionManager::UpdateSession");
CK_LOGD("Updating session: %s", sessionId.c_str());
string sessionId(aSessionId, aSessionId + aSessionIdLength);
auto itr = mSessions.find(sessionId);
if (itr == mSessions.end() || !(itr->second)) {
CK_LOGW("ClearKey CDM couldn't resolve session ID in UpdateSession.");
CK_LOGD("Unable to find session: %s", sessionId.c_str());
mHost->OnRejectPromise(aPromiseId,
Error::kInvalidAccessError,
0,
nullptr,
0);
mCallback->RejectPromise(aPromiseId, kGMPNotFoundError, nullptr, 0);
return;
}
ClearKeySession* session = itr->second;
@ -363,56 +220,32 @@ ClearKeySessionManager::UpdateSession(uint32_t aPromiseId,
// Verify the size of session response.
if (aResponseSize >= kMaxSessionResponseLength) {
CK_LOGW("Session response size is not within a reasonable size.");
CK_LOGD("Failed to parse response for session %s", sessionId.c_str());
mHost->OnRejectPromise(aPromiseId,
Error::kInvalidAccessError,
0,
nullptr,
0);
mCallback->RejectPromise(aPromiseId, kGMPTypeError, nullptr, 0);
return;
}
// Parse the response for any (key ID, key) pairs.
vector<KeyIdPair> keyPairs;
if (!ClearKeyUtils::ParseJWK(aResponse,
aResponseSize,
keyPairs,
session->Type())) {
if (!ClearKeyUtils::ParseJWK(aResponse, aResponseSize, keyPairs, session->Type())) {
CK_LOGW("ClearKey CDM failed to parse JSON Web Key.");
mHost->OnRejectPromise(aPromiseId,
Error::kInvalidAccessError,
0,
nullptr,
0);
mCallback->RejectPromise(aPromiseId, kGMPTypeError, nullptr, 0);
return;
}
vector<KeyInformation> keyInfos;
vector<GMPMediaKeyInfo> key_infos;
for (size_t i = 0; i < keyPairs.size(); i++) {
KeyIdPair& keyPair = keyPairs[i];
mDecryptionManager->InitKey(keyPair.mKeyId, keyPair.mKey);
mKeyIds.insert(keyPair.mKeyId);
KeyInformation keyInfo = KeyInformation();
keyInfo.key_id = &keyPair.mKeyId[0];
keyInfo.key_id_size = keyPair.mKeyId.size();
keyInfo.status = KeyStatus::kUsable;
keyInfos.push_back(keyInfo);
key_infos.push_back(GMPMediaKeyInfo(&keyPair.mKeyId[0],
keyPair.mKeyId.size(),
kGMPUsable));
}
mCallback->BatchedKeyStatusChanged(aSessionId, aSessionIdLength,
key_infos.data(), key_infos.size());
mHost->OnSessionKeysChange(aSessionId,
aSessionIdLength,
true,
keyInfos.data(),
keyInfos.size());
if (session->Type() != SessionType::kPersistentLicense) {
mHost->OnResolvePromise(aPromiseId);
if (session->Type() != kGMPPersistentSession) {
mCallback->ResolvePromise(aPromiseId);
return;
}
@ -420,30 +253,15 @@ ClearKeySessionManager::UpdateSession(uint32_t aPromiseId,
// and simply append each keyId followed by its key.
vector<uint8_t> keydata;
Serialize(session, keydata);
function<void()> resolve = [self, aPromiseId] ()
{
if (!self->mHost) {
return;
}
self->mHost->OnResolvePromise(aPromiseId);
};
function<void()> reject = [self, aPromiseId] ()
{
if (!self->mHost) {
return;
}
static const char* message = "Couldn't store cenc key init data";
self->mHost->OnRejectPromise(aPromiseId,
Error::kInvalidStateError,
0,
message,
strlen(message));
};
WriteData(mHost, sessionId, keydata, move(resolve), move(reject));
GMPTask* resolve = WrapTask(mCallback, &GMPDecryptorCallback::ResolvePromise, aPromiseId);
static const char* message = "Couldn't store cenc key init data";
GMPTask* reject = WrapTask(mCallback,
&GMPDecryptorCallback::RejectPromise,
aPromiseId,
kGMPInvalidStateError,
message,
strlen(message));
StoreData(sessionId, keydata, resolve, reject);
}
void
@ -469,39 +287,13 @@ ClearKeySessionManager::CloseSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength)
{
// Copy the sessionId into a string so we capture it properly.
string sessionId(aSessionId, aSessionId + aSessionIdLength);
// Hold a reference to the session manager, so it doesn't get deleted
// before we need to use it.
RefPtr<ClearKeySessionManager> self(this);
function<void()> deferrer =
[self, aPromiseId, sessionId] ()
{
self->CloseSession(aPromiseId, sessionId.data(), sessionId.size());
};
// If we haven't loaded, call this method later.
if (MaybeDeferTillInitialized(deferrer)) {
return;
}
CK_LOGD("ClearKeySessionManager::CloseSession");
// If DecryptingComplete has been called mHost will be null and we won't
// be able to resolve our promise.
if (!mHost) {
return;
}
string sessionId(aSessionId, aSessionId + aSessionIdLength);
auto itr = mSessions.find(sessionId);
if (itr == mSessions.end()) {
CK_LOGW("ClearKey CDM couldn't close non-existent session.");
mHost->OnRejectPromise(aPromiseId,
Error::kInvalidAccessError,
0,
nullptr,
0);
mCallback->RejectPromise(aPromiseId, kGMPNotFoundError, nullptr, 0);
return;
}
@ -509,9 +301,8 @@ ClearKeySessionManager::CloseSession(uint32_t aPromiseId,
assert(session);
ClearInMemorySessionData(session);
mHost->OnSessionClosed(aSessionId, aSessionIdLength);
mHost->OnResolvePromise(aPromiseId);
mCallback->SessionClosed(aSessionId, aSessionIdLength);
mCallback->ResolvePromise(aPromiseId);
}
void
@ -526,80 +317,40 @@ ClearKeySessionManager::RemoveSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength)
{
// Copy the sessionId into a string so it can be captured for the lambda.
string sessionId(aSessionId, aSessionId + aSessionIdLength);
// Hold a reference to the SessionManager, so it isn't released before we
// try and use it.
RefPtr<ClearKeySessionManager> self(this);
function<void()> deferrer =
[self, aPromiseId, sessionId] ()
{
self->RemoveSession(aPromiseId, sessionId.data(), sessionId.size());
};
// If we haven't fully loaded, defer calling this method.
if (MaybeDeferTillInitialized(deferrer)) {
return;
}
// Check that the SessionManager has not been shutdown before we try and
// resolve any promises.
if (!mHost) {
return;
}
CK_LOGD("ClearKeySessionManager::RemoveSession");
string sessionId(aSessionId, aSessionId + aSessionIdLength);
auto itr = mSessions.find(sessionId);
if (itr == mSessions.end()) {
CK_LOGW("ClearKey CDM couldn't remove non-existent session.");
mHost->OnRejectPromise(aPromiseId,
Error::kInvalidAccessError,
0,
nullptr,
0);
mCallback->RejectPromise(aPromiseId, kGMPNotFoundError, nullptr, 0);
return;
}
ClearKeySession* session = itr->second;
assert(session);
string sid = session->Id();
bool isPersistent = session->Type() == SessionType::kPersistentLicense;
bool isPersistent = session->Type() == kGMPPersistentSession;
ClearInMemorySessionData(session);
if (!isPersistent) {
mHost->OnResolvePromise(aPromiseId);
mCallback->ResolvePromise(aPromiseId);
return;
}
mPersistence->PersistentSessionRemoved(sid);
ClearKeyPersistence::PersistentSessionRemoved(sid);
// Overwrite the record storing the sessionId's key data with a zero
// length record to delete it.
vector<uint8_t> emptyKeydata;
function<void()> resolve = [self, aPromiseId, sessionId] ()
{
if (!self->mHost) {
return;
}
self->mHost->OnResolvePromise(aPromiseId);
};
function<void()> reject = [self, aPromiseId, sessionId] ()
{
if (!self->mHost) {
return;
}
static const char* message = "Could not remove session";
self->mHost->OnRejectPromise(aPromiseId,
Error::kInvalidAccessError,
0,
message,
strlen(message));
};
WriteData(mHost, sessionId, emptyKeydata, move(resolve), move(reject));
GMPTask* resolve = WrapTask(mCallback, &GMPDecryptorCallback::ResolvePromise, aPromiseId);
static const char* message = "Could not remove session";
GMPTask* reject = WrapTask(mCallback,
&GMPDecryptorCallback::RejectPromise,
aPromiseId,
kGMPInvalidAccessError,
message,
strlen(message));
StoreData(sessionId, emptyKeydata, resolve, reject);
}
void
@ -609,36 +360,48 @@ ClearKeySessionManager::SetServerCertificate(uint32_t aPromiseId,
{
// ClearKey CDM doesn't support this method by spec.
CK_LOGD("ClearKeySessionManager::SetServerCertificate");
mHost->OnRejectPromise(aPromiseId,
Error::kNotSupportedError,
0,
nullptr /* message */,
0 /* messageLen */);
mCallback->RejectPromise(aPromiseId, kGMPNotSupportedError,
nullptr /* message */, 0 /* messageLen */);
}
Status
ClearKeySessionManager::Decrypt(const InputBuffer& aBuffer,
DecryptedBlock* aDecryptedBlock)
void
ClearKeySessionManager::Decrypt(GMPBuffer* aBuffer,
GMPEncryptedBufferMetadata* aMetadata)
{
CK_LOGD("ClearKeySessionManager::Decrypt");
CK_LOGARRAY("Key: ", aBuffer.key_id, aBuffer.key_id_size);
if (!mThread) {
CK_LOGW("No decrypt thread");
mCallback->Decrypted(aBuffer, GMPGenericErr);
return;
}
Buffer* buffer = mHost->Allocate(aBuffer.data_size);
assert(buffer != nullptr);
assert(buffer->Data() != nullptr);
assert(buffer->Capacity() >= aBuffer.data_size);
mThread->Post(WrapTaskRefCounted(this,
&ClearKeySessionManager::DoDecrypt,
aBuffer, aMetadata));
}
memcpy(buffer->Data(), aBuffer.data, aBuffer.data_size);
void
ClearKeySessionManager::DoDecrypt(GMPBuffer* aBuffer,
GMPEncryptedBufferMetadata* aMetadata)
{
CK_LOGD("ClearKeySessionManager::DoDecrypt");
Status status = mDecryptionManager->Decrypt(buffer->Data(),
buffer->Size(),
CryptoMetaData(&aBuffer));
GMPErr rv = mDecryptionManager->Decrypt(aBuffer->Data(), aBuffer->Size(),
CryptoMetaData(aMetadata));
CK_LOGD("DeDecrypt finished with code %x\n", rv);
mCallback->Decrypted(aBuffer, rv);
}
aDecryptedBlock->SetDecryptedBuffer(buffer);
aDecryptedBlock->SetTimestamp(aBuffer.timestamp);
void
ClearKeySessionManager::Shutdown()
{
CK_LOGD("ClearKeySessionManager::Shutdown %p", this);
return status;
for (auto it = mSessions.begin(); it != mSessions.end(); it++) {
delete it->second;
}
mSessions.clear();
}
void
@ -646,23 +409,10 @@ ClearKeySessionManager::DecryptingComplete()
{
CK_LOGD("ClearKeySessionManager::DecryptingComplete %p", this);
for (auto it = mSessions.begin(); it != mSessions.end(); it++) {
delete it->second;
}
mSessions.clear();
GMPThread* thread = mThread;
thread->Join();
Shutdown();
mDecryptionManager = nullptr;
mHost = nullptr;
Release();
}
bool ClearKeySessionManager::MaybeDeferTillInitialized(function<void()> aMaybeDefer)
{
if (mPersistence->IsLoaded()) {
return false;
}
mDeferredInitialize.emplace(move(aMaybeDefer));
return true;
}

View File

@ -1,81 +1,80 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __ClearKeyDecryptor_h__
#define __ClearKeyDecryptor_h__
#include "ClearKeyDecryptionManager.h"
#include "ClearKeyPersistence.h"
#include "ClearKeySession.h"
#include "ClearKeyUtils.h"
// This include is required in order for content_decryption_module to work
// on Unix systems.
#include "stddef.h"
#include "content_decryption_module.h"
#include "RefCounted.h"
#include <functional>
#include <map>
#include <queue>
#include <set>
#include <string>
#include <vector>
class ClearKeySessionManager final : public RefCounted
#include "ClearKeyDecryptionManager.h"
#include "ClearKeySession.h"
#include "ClearKeyUtils.h"
#include "gmp-api/gmp-decryption.h"
#include "RefCounted.h"
class ClearKeySessionManager final : public GMPDecryptor
, public RefCounted
{
public:
explicit ClearKeySessionManager(cdm::Host_8* aHost);
ClearKeySessionManager();
void Init(bool aDistinctiveIdentifierAllowed,
bool aPersistentStateAllowed);
virtual void Init(GMPDecryptorCallback* aCallback,
bool aDistinctiveIdentifierAllowed,
bool aPersistentStateAllowed) override;
void CreateSession(uint32_t aPromiseId,
cdm::InitDataType aInitDataType,
const uint8_t* aInitData,
uint32_t aInitDataSize,
cdm::SessionType aSessionType);
virtual 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);
virtual 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);
virtual 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);
virtual void CloseSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength) override;
void RemoveSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength);
virtual void RemoveSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength) override;
void SetServerCertificate(uint32_t aPromiseId,
const uint8_t* aServerCert,
uint32_t aServerCertSize);
virtual void SetServerCertificate(uint32_t aPromiseId,
const uint8_t* aServerCert,
uint32_t aServerCertSize) override;
cdm::Status
Decrypt(const cdm::InputBuffer& aBuffer,
cdm::DecryptedBlock* aDecryptedBlock);
virtual void Decrypt(GMPBuffer* aBuffer,
GMPEncryptedBufferMetadata* aMetadata) override;
void DecryptingComplete();
virtual void DecryptingComplete() override;
void PersistentSessionDataLoaded(uint32_t aPromiseId,
void PersistentSessionDataLoaded(GMPErr aStatus,
uint32_t aPromiseId,
const std::string& aSessionId,
const uint8_t* aKeyData,
uint32_t aKeyDataSize);
@ -83,20 +82,19 @@ public:
private:
~ClearKeySessionManager();
void DoDecrypt(GMPBuffer* aBuffer, GMPEncryptedBufferMetadata* aMetadata);
void Shutdown();
void ClearInMemorySessionData(ClearKeySession* aSession);
bool MaybeDeferTillInitialized(std::function<void()> aMaybeDefer);
void Serialize(const ClearKeySession* aSession,
std::vector<uint8_t>& aOutKeyData);
void Serialize(const ClearKeySession* aSession, std::vector<uint8_t>& aOutKeyData);
RefPtr<ClearKeyDecryptionManager> mDecryptionManager;
RefPtr<ClearKeyPersistence> mPersistence;
cdm::Host_8* mHost = nullptr;
GMPDecryptorCallback* mCallback;
GMPThread* mThread;
std::set<KeyId> mKeyIds;
std::map<std::string, ClearKeySession*> mSessions;
std::queue<std::function<void()>> mDeferredInitialize;
};
#endif // __ClearKeyDecryptor_h__

View File

@ -15,212 +15,180 @@
*/
#include "ClearKeyStorage.h"
#include "ClearKeyUtils.h"
// This include is required in order for content_decryption_module to work
// on Unix systems.
#include "stddef.h"
#include "content_decryption_module.h"
#include "gmp-task-utils.h"
#include <assert.h>
#include "ArrayUtils.h"
#include <vector>
using namespace cdm;
using namespace std;
class WriteRecordClient : public FileIOClient
static GMPErr
RunOnMainThread(GMPTask* aTask)
{
return GetPlatform()->runonmainthread(aTask);
}
GMPErr
OpenRecord(const char* aName,
uint32_t aNameLength,
GMPRecord** aOutRecord,
GMPRecordClient* aClient)
{
return GetPlatform()->createrecord(aName, aNameLength, aOutRecord, aClient);
}
class WriteRecordClient : public GMPRecordClient {
public:
/*
* This function will take the memory ownership of the parameters and
* delete them when done.
*/
static void Write(Host_8* aHost,
string& aRecordName,
const vector<uint8_t>& aData,
function<void()>&& aOnSuccess,
function<void()>&& aOnFailure)
{
WriteRecordClient* client = new WriteRecordClient(aData,
move(aOnSuccess),
move(aOnFailure));
client->Do(aRecordName, aHost);
static void Write(const std::string& aRecordName,
const std::vector<uint8_t>& aData,
GMPTask* aOnSuccess,
GMPTask* aOnFailure) {
(new WriteRecordClient(aData, aOnSuccess, aOnFailure))->Do(aRecordName);
}
void OnOpenComplete(Status aStatus) override
{
// If we hit an error, fail.
if (aStatus != Status::kSuccess) {
Done(aStatus);
} else if (mFileIO) { // Otherwise, write our data to the file.
mFileIO->Write(&mData[0], mData.size());
virtual void OpenComplete(GMPErr aStatus) override {
if (GMP_FAILED(aStatus) ||
GMP_FAILED(mRecord->Write(&mData.front(), mData.size()))) {
Done(mOnFailure, mOnSuccess);
}
}
void OnReadComplete(Status aStatus,
const uint8_t* aData,
uint32_t aDataSize) override
{
// This function should never be called, we only ever write data with this
// client.
assert(false);
virtual void ReadComplete(GMPErr aStatus,
const uint8_t* aData,
uint32_t aDataSize) override {
assert(false); // Should not reach here.
}
void OnWriteComplete(Status aStatus) override
{
Done(aStatus);
virtual void WriteComplete(GMPErr aStatus) override {
if (GMP_FAILED(aStatus)) {
Done(mOnFailure, mOnSuccess);
} else {
Done(mOnSuccess, mOnFailure);
}
}
private:
explicit WriteRecordClient(const vector<uint8_t>& aData,
function<void()>&& aOnSuccess,
function<void()>&& aOnFailure)
: mFileIO(nullptr)
, mOnSuccess(move(aOnSuccess))
, mOnFailure(move(aOnFailure))
WriteRecordClient(const std::vector<uint8_t>& aData,
GMPTask* aOnSuccess,
GMPTask* aOnFailure)
: mRecord(nullptr)
, mOnSuccess(aOnSuccess)
, mOnFailure(aOnFailure)
, mData(aData) {}
void Do(const string& aName, Host_8* aHost)
{
// Initialize the FileIO.
mFileIO = aHost->CreateFileIO(this);
mFileIO->Open(aName.c_str(), aName.size());
void Do(const std::string& aName) {
auto err = OpenRecord(aName.c_str(), aName.size(), &mRecord, this);
if (GMP_FAILED(err) ||
GMP_FAILED(mRecord->Open())) {
Done(mOnFailure, mOnSuccess);
}
}
void Done(cdm::FileIOClient::Status aStatus)
{
void Done(GMPTask* aToRun, GMPTask* aToDestroy) {
// Note: Call Close() before running continuation, in case the
// continuation tries to open the same record; if we call Close()
// after running the continuation, the Close() call will arrive
// just after the Open() call succeeds, immediately closing the
// record we just opened.
if (mFileIO) {
mFileIO->Close();
if (mRecord) {
mRecord->Close();
}
if (IO_SUCCEEDED(aStatus)) {
mOnSuccess();
} else {
mOnFailure();
}
aToDestroy->Destroy();
RunOnMainThread(aToRun);
delete this;
}
FileIO* mFileIO = nullptr;
function<void()> mOnSuccess;
function<void()> mOnFailure;
const vector<uint8_t> mData;
GMPRecord* mRecord;
GMPTask* mOnSuccess;
GMPTask* mOnFailure;
const std::vector<uint8_t> mData;
};
void
WriteData(Host_8* aHost,
string& aRecordName,
const vector<uint8_t>& aData,
function<void()>&& aOnSuccess,
function<void()>&& aOnFailure)
StoreData(const std::string& aRecordName,
const std::vector<uint8_t>& aData,
GMPTask* aOnSuccess,
GMPTask* aOnFailure)
{
WriteRecordClient::Write(aHost,
aRecordName,
aData,
move(aOnSuccess),
move(aOnFailure));
WriteRecordClient::Write(aRecordName, aData, aOnSuccess, aOnFailure);
}
class ReadRecordClient : public FileIOClient
{
class ReadRecordClient : public GMPRecordClient {
public:
/*
* This function will take the memory ownership of the parameters and
* delete them when done.
*/
static void Read(Host_8* aHost,
string& aRecordName,
function<void(const uint8_t*, uint32_t)>&& aOnSuccess,
function<void()>&& aOnFailure)
{
(new ReadRecordClient(move(aOnSuccess), move(aOnFailure)))->
Do(aRecordName, aHost);
static void Read(const std::string& aRecordName,
ReadContinuation* aContinuation) {
assert(aContinuation);
(new ReadRecordClient(aContinuation))->Do(aRecordName);
}
void OnOpenComplete(Status aStatus) override
{
virtual void OpenComplete(GMPErr aStatus) override {
auto err = aStatus;
if (aStatus != Status::kSuccess) {
if (GMP_FAILED(err) ||
GMP_FAILED(err = mRecord->Read())) {
Done(err, nullptr, 0);
} else {
mFileIO->Read();
}
}
void OnReadComplete(Status aStatus,
const uint8_t* aData,
uint32_t aDataSize) override
{
virtual void ReadComplete(GMPErr aStatus,
const uint8_t* aData,
uint32_t aDataSize) override {
Done(aStatus, aData, aDataSize);
}
void OnWriteComplete(Status aStatus) override
{
// We should never reach here, this client only ever reads data.
assert(false);
virtual void WriteComplete(GMPErr aStatus) override {
assert(false); // Should not reach here.
}
private:
explicit ReadRecordClient(function<void(const uint8_t*, uint32_t)>&& aOnSuccess,
function<void()>&& aOnFailure)
: mFileIO(nullptr)
, mOnSuccess(move(aOnSuccess))
, mOnFailure(move(aOnFailure))
{}
explicit ReadRecordClient(ReadContinuation* aContinuation)
: mRecord(nullptr)
, mContinuation(aContinuation) {}
void Do(const string& aName, Host_8* aHost)
{
mFileIO = aHost->CreateFileIO(this);
mFileIO->Open(aName.c_str(), aName.size());
void Do(const std::string& aName) {
auto err = OpenRecord(aName.c_str(), aName.size(), &mRecord, this);
if (GMP_FAILED(err) ||
GMP_FAILED(err = mRecord->Open())) {
Done(err, nullptr, 0);
}
}
void Done(cdm::FileIOClient::Status aStatus,
const uint8_t* aData,
uint32_t aDataSize)
{
void Done(GMPErr err, const uint8_t* aData, uint32_t aDataSize) {
// Note: Call Close() before running continuation, in case the
// continuation tries to open the same record; if we call Close()
// after running the continuation, the Close() call will arrive
// just after the Open() call succeeds, immediately closing the
// record we just opened.
if (mFileIO) {
mFileIO->Close();
if (mRecord) {
mRecord->Close();
}
if (IO_SUCCEEDED(aStatus)) {
mOnSuccess(aData, aDataSize);
} else {
mOnFailure();
}
mContinuation->ReadComplete(err, aData, aDataSize);
delete mContinuation;
delete this;
}
FileIO* mFileIO = nullptr;
function<void(const uint8_t*, uint32_t)> mOnSuccess;
function<void()> mOnFailure;
GMPRecord* mRecord;
ReadContinuation* mContinuation;
};
void
ReadData(Host_8* mHost,
string& aRecordName,
function<void(const uint8_t*, uint32_t)>&& aOnSuccess,
function<void()>&& aOnFailure)
ReadData(const std::string& aRecordName,
ReadContinuation* aContinuation)
{
ReadRecordClient::Read(mHost,
aRecordName,
move(aOnSuccess),
move(aOnFailure));
ReadRecordClient::Read(aRecordName, aContinuation);
}
GMPErr
EnumRecordNames(RecvGMPRecordIteratorPtr aRecvIteratorFunc)
{
return GetPlatform()->getrecordenumerator(aRecvIteratorFunc, nullptr);
}

View File

@ -17,27 +17,32 @@
#ifndef __ClearKeyStorage_h__
#define __ClearKeyStorage_h__
#include <functional>
#include <stdint.h>
#include "gmp-api/gmp-errors.h"
#include "gmp-api/gmp-platform.h"
#include <string>
#include <vector>
#include <stdint.h>
#include "ClearKeySessionManager.h"
class GMPTask;
#define IO_SUCCEEDED(x) ((x) == cdm::FileIOClient::Status::kSuccess)
#define IO_FAILED(x) ((x) != cdm::FileIOClient::Status::kSuccess)
// Writes data to a file and fires the appropriate callback when complete.
void WriteData(cdm::Host_8* aHost,
std::string& aRecordName,
// Responsible for ensuring that both aOnSuccess and aOnFailure are destroyed.
void StoreData(const std::string& aRecordName,
const std::vector<uint8_t>& aData,
std::function<void()>&& aOnSuccess,
std::function<void()>&& aOnFailure);
GMPTask* aOnSuccess,
GMPTask* aOnFailure);
// Reads data from a file and fires the appropriate callback when complete.
void ReadData(cdm::Host_8* aHost,
std::string& aRecordName,
std::function<void(const uint8_t*, uint32_t)>&& aOnSuccess,
std::function<void()>&& aOnFailure);
class ReadContinuation {
public:
virtual void ReadComplete(GMPErr aStatus,
const uint8_t* aData,
uint32_t aLength) = 0;
virtual ~ReadContinuation() {}
};
// Deletes aContinuation after running it to report the result.
void ReadData(const std::string& aSessionId,
ReadContinuation* aContinuation);
GMPErr EnumRecordNames(RecvGMPRecordIteratorPtr aRecvIteratorFunc);
#endif // __ClearKeyStorage_h__

View File

@ -14,77 +14,33 @@
* limitations under the License.
*/
#include "ClearKeyUtils.h"
#include <algorithm>
#include <assert.h>
#include <stdlib.h>
#include <cctype>
#include <ctype.h>
#include <memory.h>
#include <sstream>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <vector>
#include "ArrayUtils.h"
#include "BigEndian.h"
#include "ClearKeyUtils.h"
#include "ClearKeyBase64.h"
// This include is required in order for content_decryption_module to work
// on Unix systems.
#include "stddef.h"
#include "content_decryption_module.h"
#include "ArrayUtils.h"
#include <assert.h>
#include <memory.h>
#include "BigEndian.h"
#include "openaes/oaes_lib.h"
#include "psshparser/PsshParser.h"
using namespace cdm;
using namespace std;
void
CK_Log(const char* aFmt, ...)
{
FILE* out = stdout;
if (getenv("CLEARKEY_LOG_FILE")) {
out = fopen(getenv("CLEARKEY_LOG_FILE"), "a");
}
va_list ap;
va_start(ap, aFmt);
const size_t len = 1024;
char buf[len];
vsnprintf(buf, len, aFmt, ap);
vprintf(aFmt, ap);
va_end(ap);
fprintf(out, "%s\n", buf);
fflush(out);
if (out != stdout) {
fclose(out);
}
}
static bool
PrintableAsString(const uint8_t* aBytes, uint32_t aLength)
{
return all_of(aBytes, aBytes + aLength, [] (uint8_t c) {
return isprint(c) == 1;
});
}
void
CK_LogArray(const char* prepend,
const uint8_t* aData,
const uint32_t aDataSize)
{
// If the data is valid ascii, use that. Otherwise print the hex
string data = PrintableAsString(aData, aDataSize) ?
string(aData, aData + aDataSize) :
ClearKeyUtils::ToHexString(aData, aDataSize);
CK_LOGD("%s%s", prepend, data.c_str());
printf("\n");
fflush(stdout);
}
static void
@ -159,9 +115,7 @@ EncodeBase64Web(vector<uint8_t> aBinary, string& aEncoded)
// Cast idx to size_t before using it as an array-index,
// to pacify clang 'Wchar-subscripts' warning:
size_t idx = static_cast<size_t>(out[i]);
// out of bounds index for 'sAlphabet'
assert(idx < MOZ_ARRAY_LENGTH(sAlphabet));
assert(idx < MOZ_ARRAY_LENGTH(sAlphabet)); // out of bounds index for 'sAlphabet'
out[i] = sAlphabet[idx];
}
@ -171,7 +125,7 @@ EncodeBase64Web(vector<uint8_t> aBinary, string& aEncoded)
/* static */ void
ClearKeyUtils::MakeKeyRequest(const vector<KeyId>& aKeyIDs,
string& aOutRequest,
SessionType aSessionType)
GMPSessionType aSessionType)
{
assert(aKeyIDs.size() && aOutRequest.empty());
@ -435,7 +389,7 @@ ParseKeys(ParserContext& aCtx, vector<KeyIdPair>& aOutKeys)
/* static */ bool
ClearKeyUtils::ParseJWK(const uint8_t* aKeyData, uint32_t aKeyDataSize,
vector<KeyIdPair>& aOutKeys,
SessionType aSessionType)
GMPSessionType aSessionType)
{
ParserContext ctx;
ctx.mIter = aKeyData;
@ -551,16 +505,15 @@ ClearKeyUtils::ParseKeyIdsInitData(const uint8_t* aInitData,
}
/* static */ const char*
ClearKeyUtils::SessionTypeToString(SessionType aSessionType)
ClearKeyUtils::SessionTypeToString(GMPSessionType aSessionType)
{
switch (aSessionType) {
case SessionType::kTemporary: return "temporary";
case SessionType::kPersistentLicense: return "persistent-license";
default: {
// We don't support any other license types.
assert(false);
return "invalid";
}
case kGMPTemporySession: return "temporary";
case kGMPPersistentSession: return "persistent-license";
default: {
assert(false); // Should not reach here.
return "invalid";
}
}
}
@ -580,15 +533,9 @@ ClearKeyUtils::IsValidSessionId(const char* aBuff, uint32_t aLength)
return true;
}
string
ClearKeyUtils::ToHexString(const uint8_t * aBytes, uint32_t aLength)
{
stringstream ss;
ss << std::showbase << std::uppercase << std::hex;
for (uint32_t i = 0; i < aLength; ++i) {
ss << std::hex << static_cast<uint32_t>(aBytes[i]);
ss << " ";
}
return ss.str();
GMPMutex* GMPCreateMutex() {
GMPMutex* mutex;
auto err = GetPlatform()->createmutex(&mutex);
assert(mutex);
return GMP_FAILED(err) ? nullptr : mutex;
}

View File

@ -21,29 +21,22 @@
#include <string>
#include <vector>
#include <assert.h>
// This include is required in order for content_decryption_module to work
// on Unix systems.
#include "stddef.h"
#include "content_decryption_module.h"
#include "gmp-api/gmp-decryption.h"
#if 0
void CK_Log(const char* aFmt, ...);
#define CK_LOGE(...) CK_Log(__VA_ARGS__)
#define CK_LOGD(...) CK_Log(__VA_ARGS__)
#define CK_LOGW(...) CK_Log(__VA_ARGS__)
#define CK_LOGARRAY(APREPEND, ADATA, ADATA_SIZE) CK_LogArray(APREPEND, \
ADATA, \
ADATA_SIZE)
#else
// Note: Enabling logging slows things down a LOT, especially when logging to
// a file.
#define CK_LOGE(...)
#define CK_LOGD(...)
#define CK_LOGW(...)
#define CK_LOGARRAY(APREPEND, ADATA, ADATA_SIZE)
#endif
struct GMPPlatformAPI;
extern GMPPlatformAPI* GetPlatform();
typedef std::vector<uint8_t> KeyId;
typedef std::vector<uint8_t> Key;
@ -55,10 +48,6 @@ static const uint32_t kMaxSessionResponseLength = 65536;
static const uint32_t kMaxWebmInitDataSize = 65536;
static const uint32_t kMaxKeyIdsLength = 512;
void CK_LogArray(const char* aPrepend,
const uint8_t* aData,
const uint32_t aDataSize);
struct KeyIdPair
{
KeyId mKeyId;
@ -77,16 +66,14 @@ public:
static void MakeKeyRequest(const std::vector<KeyId>& aKeyIds,
std::string& aOutRequest,
cdm::SessionType aSessionType);
GMPSessionType aSessionType);
static bool ParseJWK(const uint8_t* aKeyData, uint32_t aKeyDataSize,
std::vector<KeyIdPair>& aOutKeys,
cdm::SessionType aSessionType);
static const char* SessionTypeToString(cdm::SessionType aSessionType);
GMPSessionType aSessionType);
static const char* SessionTypeToString(GMPSessionType aSessionType);
static bool IsValidSessionId(const char* aBuff, uint32_t aLength);
static std::string ToHexString(const uint8_t * aBytes, uint32_t aLength);
};
template<class Container, class Element>
@ -96,6 +83,27 @@ Contains(const Container& aContainer, const Element& aElement)
return aContainer.find(aElement) != aContainer.end();
}
class AutoLock {
public:
explicit AutoLock(GMPMutex* aMutex)
: mMutex(aMutex)
{
assert(aMutex);
if (mMutex) {
mMutex->Acquire();
}
}
~AutoLock() {
if (mMutex) {
mMutex->Release();
}
}
private:
GMPMutex* mMutex;
};
GMPMutex* GMPCreateMutex();
template<typename T>
inline void
Assign(std::vector<T>& aVec, const T* aData, size_t aLength)

View File

@ -21,7 +21,41 @@
#include <assert.h>
#include "ClearKeyUtils.h"
#if defined(_MSC_VER)
#include <atomic>
typedef std::atomic<uint32_t> AtomicRefCount;
#else
class AtomicRefCount {
public:
explicit AtomicRefCount(uint32_t aValue)
: mCount(aValue)
, mMutex(GMPCreateMutex())
{
assert(mMutex);
}
~AtomicRefCount()
{
if (mMutex) {
mMutex->Destroy();
}
}
uint32_t operator--() {
AutoLock lock(mMutex);
return --mCount;
}
uint32_t operator++() {
AutoLock lock(mMutex);
return ++mCount;
}
operator uint32_t() {
AutoLock lock(mMutex);
return mCount;
}
private:
uint32_t mCount;
GMPMutex* mMutex;
};
#endif
// Note: Thread safe.
class RefCounted {
@ -47,41 +81,27 @@ protected:
{
assert(!mRefCount);
}
std::atomic<uint32_t> mRefCount;
AtomicRefCount mRefCount;
};
template<class T>
class RefPtr {
public:
RefPtr(const RefPtr& src) {
Set(src.mPtr);
explicit RefPtr(T* aPtr) : mPtr(nullptr) {
Assign(aPtr);
}
explicit RefPtr(T* aPtr) {
Set(aPtr);
}
RefPtr() { Set(nullptr); }
~RefPtr() {
Set(nullptr);
Assign(nullptr);
}
T* operator->() const { return mPtr; }
T** operator&() { return &mPtr; }
T* operator->() { return mPtr; }
operator T*() { return mPtr; }
T* Get() const { return mPtr; }
RefPtr& operator=(T* aVal) {
Set(aVal);
Assign(aVal);
return *this;
}
private:
T* Set(T* aPtr) {
if (mPtr == aPtr) {
return aPtr;
}
void Assign(T* aPtr) {
if (mPtr) {
mPtr->Release();
}
@ -89,10 +109,8 @@ private:
if (mPtr) {
aPtr->AddRef();
}
return mPtr;
}
T* mPtr = nullptr;
T* mPtr;
};
#endif // __RefCount_h__

View File

@ -14,162 +14,247 @@
* limitations under the License.
*/
#include <algorithm>
#include <cstdint>
#include <limits>
#include "AnnexB.h"
#include "BigEndian.h"
#include "ClearKeyDecryptionManager.h"
#include "ClearKeyUtils.h"
#include "gmp-task-utils.h"
#include "VideoDecoder.h"
using namespace wmf;
using namespace cdm;
VideoDecoder::VideoDecoder(Host_8 *aHost)
: mHost(aHost)
VideoDecoder::VideoDecoder(GMPVideoHost *aHostAPI)
: mHostAPI(aHostAPI)
, mCallback(nullptr)
, mWorkerThread(nullptr)
, mMutex(nullptr)
, mNumInputTasks(0)
, mSentExtraData(false)
, mIsFlushing(false)
, mHasShutdown(false)
{
// We drop the ref in DecodingComplete().
AddRef();
mDecoder = new WMFH264Decoder();
uint32_t cores = std::max(1u, std::thread::hardware_concurrency());
HRESULT hr = mDecoder->Init(cores);
}
VideoDecoder::~VideoDecoder()
{
if (mMutex) {
mMutex->Destroy();
}
}
Status
VideoDecoder::InitDecode(const VideoDecoderConfig& aConfig)
void
VideoDecoder::InitDecode(const GMPVideoCodec& aCodecSettings,
const uint8_t* aCodecSpecific,
uint32_t aCodecSpecificLength,
GMPVideoDecoderCallback* aCallback,
int32_t aCoreCount)
{
if (!mDecoder) {
mCallback = aCallback;
assert(mCallback);
mDecoder = new WMFH264Decoder();
HRESULT hr = mDecoder->Init(aCoreCount);
if (FAILED(hr)) {
CK_LOGD("VideoDecoder::InitDecode failed to init WMFH264Decoder");
return Status::kDecodeError;
mCallback->Error(GMPGenericErr);
return;
}
return Status::kSuccess;
auto err = GetPlatform()->createmutex(&mMutex);
if (GMP_FAILED(err)) {
CK_LOGD("VideoDecoder::InitDecode failed to create GMPMutex");
mCallback->Error(GMPGenericErr);
return;
}
// The first byte is mPacketizationMode, which is only relevant for
// WebRTC/OpenH264 usecase.
const uint8_t* avcc = aCodecSpecific + 1;
const uint8_t* avccEnd = aCodecSpecific + aCodecSpecificLength;
mExtraData.insert(mExtraData.end(), avcc, avccEnd);
AnnexB::ConvertConfig(mExtraData, mAnnexB);
}
Status
VideoDecoder::Decode(const InputBuffer& aInputBuffer, VideoFrame* aVideoFrame)
void
VideoDecoder::EnsureWorker()
{
// If the input buffer we have been passed has a null buffer, it means we
// should drain.
if (!aInputBuffer.data) {
// This will drain the decoder until there are no frames left to drain,
// whereupon it will return 'NeedsMoreData'.
CK_LOGD("Input buffer null: Draining");
return Drain(aVideoFrame);
if (!mWorkerThread) {
GetPlatform()->createthread(&mWorkerThread);
if (!mWorkerThread) {
mCallback->Error(GMPAllocErr);
return;
}
}
}
void
VideoDecoder::Decode(GMPVideoEncodedFrame* aInputFrame,
bool aMissingFrames,
const uint8_t* aCodecSpecificInfo,
uint32_t aCodecSpecificInfoLength,
int64_t aRenderTimeMs)
{
if (aInputFrame->BufferType() != GMP_BufferLength32) {
// Gecko should only send frames with 4 byte NAL sizes to GMPs.
mCallback->Error(GMPGenericErr);
return;
}
DecodeData* data = new DecodeData();
Assign(data->mBuffer, aInputBuffer.data, aInputBuffer.data_size);
data->mTimestamp = aInputBuffer.timestamp;
data->mCrypto = CryptoMetaData(&aInputBuffer);
EnsureWorker();
{
AutoLock lock(mMutex);
mNumInputTasks++;
}
// Note: we don't need the codec specific info on a per-frame basis.
// It's mostly useful for WebRTC use cases.
// Make a copy of the data, so we can release aInputFrame ASAP,
// to avoid too many shmem handles being held by the GMP process.
// If the GMP process holds on to too many shmem handles, the Gecko
// side can fail to allocate a shmem to send more input. This is
// particularly a problem in Gecko mochitests, which can open multiple
// actors at once which share the same pool of shmems.
DecodeData* data = new DecodeData();
Assign(data->mBuffer, aInputFrame->Buffer(), aInputFrame->Size());
data->mTimestamp = aInputFrame->TimeStamp();
data->mDuration = aInputFrame->Duration();
data->mIsKeyframe = (aInputFrame->FrameType() == kGMPKeyFrame);
const GMPEncryptedBufferMetadata* crypto = aInputFrame->GetDecryptionData();
if (crypto) {
data->mCrypto.Init(crypto);
}
aInputFrame->Destroy();
mWorkerThread->Post(WrapTaskRefCounted(this,
&VideoDecoder::DecodeTask,
data));
}
void
VideoDecoder::DecodeTask(DecodeData* aData)
{
CK_LOGD("VideoDecoder::DecodeTask");
AutoPtr<DecodeData> d(data);
AutoPtr<DecodeData> d(aData);
HRESULT hr;
if (!data || !mDecoder) {
CK_LOGE("Decode job not set up correctly!");
return Status::kDecodeError;
{
AutoLock lock(mMutex);
mNumInputTasks--;
assert(mNumInputTasks >= 0);
}
std::vector<uint8_t>& buffer = data->mBuffer;
if (mIsFlushing) {
CK_LOGD("VideoDecoder::DecodeTask rejecting frame: flushing.");
return;
}
if (data->mCrypto.IsValid()) {
Status rv =
ClearKeyDecryptionManager::Get()->Decrypt(buffer, data->mCrypto);
if (!aData || !mHostAPI || !mDecoder) {
CK_LOGE("Decode job not set up correctly!");
return;
}
if (STATUS_FAILED(rv)) {
CK_LOGARRAY("Failed to decrypt video using key ",
aInputBuffer.key_id,
aInputBuffer.key_id_size);
return rv;
std::vector<uint8_t>& buffer = aData->mBuffer;
if (aData->mCrypto.IsValid()) {
// Plugin host should have set up its decryptor/key sessions
// before trying to decode!
GMPErr rv =
ClearKeyDecryptionManager::Get()->Decrypt(buffer, aData->mCrypto);
if (GMP_FAILED(rv)) {
MaybeRunOnMainThread(WrapTask(mCallback, &GMPVideoDecoderCallback::Error, rv));
return;
}
}
AnnexB::ConvertFrameInPlace(buffer);
if (aData->mIsKeyframe) {
// We must send the SPS and PPS to Windows Media Foundation's decoder.
// Note: We do this *after* decryption, otherwise the subsample info
// would be incorrect.
buffer.insert(buffer.begin(), mAnnexB.begin(), mAnnexB.end());
}
hr = mDecoder->Input(buffer.data(),
buffer.size(),
data->mTimestamp);
aData->mTimestamp,
aData->mDuration);
CK_LOGD("VideoDecoder::DecodeTask() Input ret hr=0x%x\n", hr);
if (FAILED(hr)) {
assert(hr != MF_E_TRANSFORM_NEED_MORE_INPUT);
CK_LOGE("VideoDecoder::DecodeTask() decode failed ret=0x%x%s\n",
hr,
((hr == MF_E_NOTACCEPTING) ? " (MF_E_NOTACCEPTING)" : ""));
CK_LOGD("Decode failed. The decoder is not accepting input");
return Status::kDecodeError;
hr,
((hr == MF_E_NOTACCEPTING) ? " (MF_E_NOTACCEPTING)" : ""));
return;
}
return OutputFrame(aVideoFrame);
}
Status VideoDecoder::OutputFrame(VideoFrame* aVideoFrame) {
HRESULT hr = S_OK;
// Read all the output from the decoder. Ideally, this would be a while loop
// where we read the output and check the result as the condition. However,
// this produces a memory leak connected to assigning a new CComPtr to the
// address of the old one, which avoids the CComPtr cleaning up.
while (true) {
while (hr == S_OK) {
CComPtr<IMFSample> output;
hr = mDecoder->Output(&output);
if (hr != S_OK) {
break;
}
CK_LOGD("VideoDecoder::DecodeTask() output ret=0x%x\n", hr);
if (hr == S_OK) {
MaybeRunOnMainThread(
WrapTaskRefCounted(this,
&VideoDecoder::ReturnOutput,
CComPtr<IMFSample>(output),
mDecoder->GetFrameWidth(),
mDecoder->GetFrameHeight(),
mDecoder->GetStride()));
}
if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
AutoLock lock(mMutex);
if (mNumInputTasks == 0) {
// We have run all input tasks. We *must* notify Gecko so that it will
// send us more data.
MaybeRunOnMainThread(
WrapTask(mCallback,
&GMPVideoDecoderCallback::InputDataExhausted));
}
}
if (FAILED(hr)) {
CK_LOGE("VideoDecoder::DecodeTask() output failed hr=0x%x\n", hr);
}
}
}
mOutputQueue.push(output);
CK_LOGD("Queue size: %u", mOutputQueue.size());
void
VideoDecoder::ReturnOutput(IMFSample* aSample,
int32_t aWidth,
int32_t aHeight,
int32_t aStride)
{
CK_LOGD("[%p] VideoDecoder::ReturnOutput()\n", this);
assert(aSample);
HRESULT hr;
GMPVideoFrame* f = nullptr;
auto err = mHostAPI->CreateFrame(kGMPI420VideoFrame, &f);
if (GMP_FAILED(err) || !f) {
CK_LOGE("Failed to create i420 frame!\n");
return;
}
if (HasShutdown()) {
// Note: GMPVideoHost::CreateFrame() can process messages before returning,
// including a message that calls VideoDecoder::DecodingComplete(), i.e.
// we can shutdown during the call!
CK_LOGD("Shutdown while waiting on GMPVideoHost::CreateFrame()!\n");
f->Destroy();
return;
}
// If we don't have any inputs, we need more data.
if (mOutputQueue.empty()) {
CK_LOGD("Decode failed. Not enought data; Requesting more input");
return Status::kNeedMoreData;
}
auto vf = static_cast<GMPVideoi420Frame*>(f);
// We will get a MF_E_TRANSFORM_NEED_MORE_INPUT every time, as we always
// consume everything in the buffer.
if (hr != MF_E_TRANSFORM_NEED_MORE_INPUT && FAILED(hr)) {
CK_LOGD("Decode failed output ret=0x%x\n", hr);
return Status::kDecodeError;
}
hr = SampleToVideoFrame(aSample, aWidth, aHeight, aStride, vf);
ENSURE(SUCCEEDED(hr), /*void*/);
CComPtr<IMFSample> result = mOutputQueue.front();
mOutputQueue.pop();
// The Chromium CDM API doesn't have support for negative strides, though
// they are theoretically possible in real world data.
if (mDecoder->GetStride() <= 0) {
return Status::kDecodeError;
}
hr = SampleToVideoFrame(result,
mDecoder->GetFrameWidth(),
mDecoder->GetFrameHeight(),
mDecoder->GetStride(),
aVideoFrame);
if (FAILED(hr)) {
return Status::kDecodeError;
}
CK_LOGD("Decode succeeded.");
return Status::kSuccess;
mCallback->Decoded(vf);
}
HRESULT
@ -177,19 +262,14 @@ VideoDecoder::SampleToVideoFrame(IMFSample* aSample,
int32_t aWidth,
int32_t aHeight,
int32_t aStride,
VideoFrame* aVideoFrame)
GMPVideoi420Frame* aVideoFrame)
{
CK_LOGD("[%p] VideoDecoder::SampleToVideoFrame()\n", this);
assert(aSample);
ENSURE(aSample != nullptr, E_POINTER);
ENSURE(aVideoFrame != nullptr, E_POINTER);
HRESULT hr;
CComPtr<IMFMediaBuffer> mediaBuffer;
aVideoFrame->SetFormat(kI420);
// Must convert to contiguous mediaBuffer to use IMD2DBuffer interface.
hr = aSample->ConvertToContiguousBuffer(&mediaBuffer);
ENSURE(SUCCEEDED(hr), hr);
@ -205,60 +285,46 @@ VideoDecoder::SampleToVideoFrame(IMFSample* aSample,
hr = twoDBuffer->Lock2D(&data, &stride);
ENSURE(SUCCEEDED(hr), hr);
} else {
hr = mediaBuffer->Lock(&data, nullptr, nullptr);
hr = mediaBuffer->Lock(&data, NULL, NULL);
ENSURE(SUCCEEDED(hr), hr);
stride = aStride;
}
// The U and V planes are stored 16-row-aligned, so we need to add padding
// The V and U planes are stored 16-row-aligned, so we need to add padding
// to the row heights to ensure the Y'CbCr planes are referenced properly.
// YV12, planar format: [YYYY....][UUUU....][VVVV....]
// i.e., Y, then U, then V.
// YV12, planar format: [YYYY....][VVVV....][UUUU....]
// i.e., Y, then V, then U.
uint32_t padding = 0;
if (aHeight % 16 != 0) {
padding = 16 - (aHeight % 16);
}
uint32_t ySize = stride * (aHeight + padding);
uint32_t uSize = stride * (aHeight + padding) / 4;
uint32_t halfStride = (stride + 1) / 2;
uint32_t halfHeight = (aHeight + 1) / 2;
int32_t y_size = stride * (aHeight + padding);
int32_t v_size = stride * (aHeight + padding) / 4;
int32_t halfStride = (stride + 1) / 2;
int32_t halfHeight = (aHeight + 1) / 2;
aVideoFrame->SetStride(VideoFrame::kYPlane, stride);
aVideoFrame->SetStride(VideoFrame::kUPlane, halfStride);
aVideoFrame->SetStride(VideoFrame::kVPlane, halfStride);
auto err = aVideoFrame->CreateEmptyFrame(stride, aHeight, stride, halfStride, halfStride);
ENSURE(GMP_SUCCEEDED(err), E_FAIL);
aVideoFrame->SetSize(Size(aWidth, aHeight));
err = aVideoFrame->SetWidth(aWidth);
ENSURE(GMP_SUCCEEDED(err), E_FAIL);
err = aVideoFrame->SetHeight(aHeight);
ENSURE(GMP_SUCCEEDED(err), E_FAIL);
uint64_t bufferSize = ySize + 2 * uSize;
uint8_t* outBuffer = aVideoFrame->Buffer(kGMPYPlane);
ENSURE(outBuffer != nullptr, E_FAIL);
assert(aVideoFrame->AllocatedSize(kGMPYPlane) >= stride*aHeight);
memcpy(outBuffer, data, stride*aHeight);
// If the buffer is bigger than the max for a 32 bit, fail to avoid buffer
// overflows.
if (bufferSize > UINT32_MAX) {
return Status::kDecodeError;
}
outBuffer = aVideoFrame->Buffer(kGMPUPlane);
ENSURE(outBuffer != nullptr, E_FAIL);
assert(aVideoFrame->AllocatedSize(kGMPUPlane) >= halfStride*halfHeight);
memcpy(outBuffer, data+y_size, halfStride*halfHeight);
// Get the buffer from the host.
Buffer* buffer = mHost->Allocate(bufferSize);
aVideoFrame->SetFrameBuffer(buffer);
// Make sure the buffer is non-null (allocate guarantees it will be of
// sufficient size).
if (!buffer) {
return E_OUTOFMEMORY;
}
uint8_t* outBuffer = buffer->Data();
aVideoFrame->SetPlaneOffset(VideoFrame::kYPlane, 0);
// Offset is the size of the copied y data.
aVideoFrame->SetPlaneOffset(VideoFrame::kUPlane, ySize);
// Offset is the size of the copied y data + the size of the copied u data.
aVideoFrame->SetPlaneOffset(VideoFrame::kVPlane, ySize + uSize);
// Copy the data.
memcpy(outBuffer, data, ySize + uSize * 2);
outBuffer = aVideoFrame->Buffer(kGMPVPlane);
ENSURE(outBuffer != nullptr, E_FAIL);
assert(aVideoFrame->AllocatedSize(kGMPVPlane) >= halfStride*halfHeight);
memcpy(outBuffer, data + y_size + v_size, halfStride*halfHeight);
if (twoDBuffer) {
twoDBuffer->Unlock2D();
@ -269,46 +335,84 @@ VideoDecoder::SampleToVideoFrame(IMFSample* aSample,
LONGLONG hns = 0;
hr = aSample->GetSampleTime(&hns);
ENSURE(SUCCEEDED(hr), hr);
aVideoFrame->SetTimestamp(HNsToUsecs(hns));
hr = aSample->GetSampleDuration(&hns);
ENSURE(SUCCEEDED(hr), hr);
aVideoFrame->SetDuration(HNsToUsecs(hns));
return S_OK;
}
void
VideoDecoder::ResetCompleteTask()
{
mIsFlushing = false;
if (mCallback) {
MaybeRunOnMainThread(WrapTask(mCallback,
&GMPVideoDecoderCallback::ResetComplete));
}
}
void
VideoDecoder::Reset()
{
CK_LOGD("VideoDecoder::Reset");
mIsFlushing = true;
if (mDecoder) {
mDecoder->Reset();
}
// Remove all the frames from the output queue.
while (!mOutputQueue.empty()) {
mOutputQueue.pop();
}
// Schedule ResetComplete callback to run after existing frames have been
// flushed out of the task queue.
EnsureWorker();
mWorkerThread->Post(WrapTaskRefCounted(this,
&VideoDecoder::ResetCompleteTask));
}
Status
VideoDecoder::Drain(VideoFrame* aVideoFrame)
void
VideoDecoder::DrainTask()
{
CK_LOGD("VideoDecoder::Drain()");
if (!mDecoder) {
CK_LOGD("Drain failed! Decoder was not initialized");
return Status::kDecodeError;
}
mDecoder->Drain();
// Return any pending output.
return OutputFrame(aVideoFrame);
HRESULT hr = S_OK;
while (hr == S_OK) {
CComPtr<IMFSample> output;
hr = mDecoder->Output(&output);
CK_LOGD("VideoDecoder::DrainTask() output ret=0x%x\n", hr);
if (hr == S_OK) {
MaybeRunOnMainThread(
WrapTaskRefCounted(this,
&VideoDecoder::ReturnOutput,
CComPtr<IMFSample>(output),
mDecoder->GetFrameWidth(),
mDecoder->GetFrameHeight(),
mDecoder->GetStride()));
}
}
MaybeRunOnMainThread(WrapTask(mCallback, &GMPVideoDecoderCallback::DrainComplete));
}
void
VideoDecoder::Drain()
{
if (!mDecoder) {
if (mCallback) {
mCallback->DrainComplete();
}
return;
}
EnsureWorker();
mWorkerThread->Post(WrapTaskRefCounted(this,
&VideoDecoder::DrainTask));
}
void
VideoDecoder::DecodingComplete()
{
if (mWorkerThread) {
mWorkerThread->Join();
}
mHasShutdown = true;
// Release the reference we added in the constructor. There may be
@ -316,3 +420,36 @@ VideoDecoder::DecodingComplete()
// us alive a little longer.
Release();
}
void
VideoDecoder::MaybeRunOnMainThread(GMPTask* aTask)
{
class MaybeRunTask : public GMPTask
{
public:
MaybeRunTask(VideoDecoder* aDecoder, GMPTask* aTask)
: mDecoder(aDecoder), mTask(aTask)
{ }
virtual void Run(void) {
if (mDecoder->HasShutdown()) {
CK_LOGD("Trying to dispatch to main thread after VideoDecoder has shut down");
return;
}
mTask->Run();
}
virtual void Destroy()
{
mTask->Destroy();
delete this;
}
private:
RefPtr<VideoDecoder> mDecoder;
GMPTask* mTask;
};
GetPlatform()->runonmainthread(new MaybeRunTask(this, aTask));
}

View File

@ -18,28 +18,37 @@
#define __VideoDecoder_h__
#include <atomic>
#include <queue>
#include <thread>
// This include is required in order for content_decryption_module to work
// on Unix systems.
#include "stddef.h"
#include "content_decryption_module.h"
#include "gmp-task-utils.h"
#include "gmp-video-decode.h"
#include "gmp-video-host.h"
#include "WMFH264Decoder.h"
class VideoDecoder : public RefCounted
#include "mfobjects.h"
class VideoDecoder : public GMPVideoDecoder
, public RefCounted
{
public:
explicit VideoDecoder(cdm::Host_8 *aHost);
explicit VideoDecoder(GMPVideoHost *aHostAPI);
cdm::Status InitDecode(const cdm::VideoDecoderConfig& aConfig);
virtual void InitDecode(const GMPVideoCodec& aCodecSettings,
const uint8_t* aCodecSpecific,
uint32_t aCodecSpecificLength,
GMPVideoDecoderCallback* aCallback,
int32_t aCoreCount) override;
cdm::Status Decode(const cdm::InputBuffer& aEncryptedBuffer,
cdm::VideoFrame* aVideoFrame);
virtual void Decode(GMPVideoEncodedFrame* aInputFrame,
bool aMissingFrames,
const uint8_t* aCodecSpecific,
uint32_t aCodecSpecificLength,
int64_t aRenderTimeMs = -1);
void Reset();
virtual void Reset() override;
void DecodingComplete();
virtual void Drain() override;
virtual void DecodingComplete() override;
bool HasShutdown() { return mHasShutdown; }
@ -47,26 +56,53 @@ private:
virtual ~VideoDecoder();
cdm::Status Drain(cdm::VideoFrame* aVideoFrame);
void EnsureWorker();
void DrainTask();
struct DecodeData {
DecodeData()
: mTimestamp(0)
, mDuration(0)
, mIsKeyframe(false)
{}
std::vector<uint8_t> mBuffer;
uint64_t mTimestamp = 0;
uint64_t mTimestamp;
uint64_t mDuration;
bool mIsKeyframe;
CryptoMetaData mCrypto;
};
cdm::Status OutputFrame(cdm::VideoFrame* aVideoFrame);
void DecodeTask(DecodeData* aData);
void ResetCompleteTask();
void ReturnOutput(IMFSample* aSample,
int32_t aWidth,
int32_t aHeight,
int32_t aStride);
HRESULT SampleToVideoFrame(IMFSample* aSample,
int32_t aWidth,
int32_t aHeight,
int32_t aStride,
cdm::VideoFrame* aVideoFrame);
GMPVideoi420Frame* aVideoFrame);
cdm::Host_8* mHost;
void MaybeRunOnMainThread(GMPTask* aTask);
GMPVideoHost *mHostAPI; // host-owned, invalid at DecodingComplete
GMPVideoDecoderCallback* mCallback; // host-owned, invalid at DecodingComplete
GMPThread* mWorkerThread;
GMPMutex* mMutex;
wmf::AutoPtr<wmf::WMFH264Decoder> mDecoder;
std::queue<wmf::CComPtr<IMFSample>> mOutputQueue;
std::vector<uint8_t> mExtraData;
std::vector<uint8_t> mAnnexB;
int32_t mNumInputTasks;
bool mSentExtraData;
std::atomic<bool> mIsFlushing;
bool mHasShutdown;
};

View File

@ -196,6 +196,7 @@ HRESULT
WMFH264Decoder::CreateInputSample(const uint8_t* aData,
uint32_t aDataSize,
Microseconds aTimestamp,
Microseconds aDuration,
IMFSample** aOutSample)
{
HRESULT hr;
@ -230,6 +231,8 @@ WMFH264Decoder::CreateInputSample(const uint8_t* aData,
hr = sample->SetSampleTime(UsecsToHNs(aTimestamp));
ENSURE(SUCCEEDED(hr), hr);
sample->SetSampleDuration(UsecsToHNs(aDuration));
*aOutSample = sample.Detach();
return S_OK;
@ -298,11 +301,12 @@ WMFH264Decoder::GetOutputSample(IMFSample** aOutSample)
HRESULT
WMFH264Decoder::Input(const uint8_t* aData,
uint32_t aDataSize,
Microseconds aTimestamp)
Microseconds aTimestamp,
Microseconds aDuration)
{
HRESULT hr;
CComPtr<IMFSample> input = nullptr;
hr = CreateInputSample(aData, aDataSize, aTimestamp, &input);
hr = CreateInputSample(aData, aDataSize, aTimestamp, aDuration, &input);
ENSURE(SUCCEEDED(hr) && input!=nullptr, hr);
hr = mDecoder->ProcessInput(0, input, 0);

View File

@ -30,7 +30,8 @@ public:
HRESULT Input(const uint8_t* aData,
uint32_t aDataSize,
Microseconds aTimestamp);
Microseconds aTimestamp,
Microseconds aDuration);
HRESULT Output(IMFSample** aOutput);
@ -52,6 +53,7 @@ private:
HRESULT CreateInputSample(const uint8_t* aData,
uint32_t aDataSize,
Microseconds aTimestamp,
Microseconds aDuration,
IMFSample** aOutSample);
HRESULT CreateOutputSample(IMFSample** aOutSample);

View File

@ -119,8 +119,8 @@ typedef int64_t Microseconds;
#define ENSURE(condition, ret) \
{ if (!(condition)) { LOG("##condition## FAILED %S:%d\n", __FILE__, __LINE__); return ret; } }
#define STATUS_SUCCEEDED(x) ((x) == Status::kSuccess)
#define STATUS_FAILED(x) ((x) != Status::kSuccess)
#define GMP_SUCCEEDED(x) ((x) == GMPNoErr)
#define GMP_FAILED(x) ((x) != GMPNoErr)
#define MFPLAT_FUNC(_func, _dllname) \
extern decltype(::_func)* _func;

View File

@ -18,47 +18,68 @@
#include <stdio.h>
#include <string.h>
#include "ClearKeyCDM.h"
#include "ClearKeyAsyncShutdown.h"
#include "ClearKeySessionManager.h"
// This include is required in order for content_decryption_module to work
// on Unix systems.
#include "stddef.h"
#include "content_decryption_module.h"
#include "gmp-api/gmp-async-shutdown.h"
#include "gmp-api/gmp-decryption.h"
#include "gmp-api/gmp-platform.h"
#ifdef ENABLE_WMF
#if defined(ENABLE_WMF)
#include "WMFUtils.h"
#endif // ENABLE_WMF
#include "VideoDecoder.h"
#endif
#if defined(WIN32)
#define GMP_EXPORT __declspec(dllexport)
#else
#define GMP_EXPORT __attribute__((visibility("default")))
#endif
static GMPPlatformAPI* sPlatform = nullptr;
GMPPlatformAPI*
GetPlatform()
{
return sPlatform;
}
extern "C" {
CDM_EXPORT
void INITIALIZE_CDM_MODULE() {
GMP_EXPORT GMPErr
GMPInit(GMPPlatformAPI* aPlatformAPI)
{
sPlatform = aPlatformAPI;
return GMPNoErr;
}
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)
GMP_EXPORT GMPErr
GMPGetAPI(const char* aApiName, void* aHostAPI, void** aPluginAPI)
{
CK_LOGD("ClearKey GMPGetAPI |%s|", aApiName);
assert(!*aPluginAPI);
CK_LOGE("ClearKey CreateCDMInstance");
#ifdef ENABLE_WMF
if (!wmf::EnsureLibs()) {
CK_LOGE("Required libraries were not found");
return nullptr;
if (!strcmp(aApiName, GMP_API_DECRYPTOR)) {
*aPluginAPI = new ClearKeySessionManager();
}
#if defined(ENABLE_WMF)
else if (!strcmp(aApiName, GMP_API_VIDEO_DECODER) &&
wmf::EnsureLibs()) {
*aPluginAPI = new VideoDecoder(static_cast<GMPVideoHost*>(aHostAPI));
}
#endif
else if (!strcmp(aApiName, GMP_API_ASYNC_SHUTDOWN)) {
*aPluginAPI = new ClearKeyAsyncShutdown(static_cast<GMPAsyncShutdownHost*> (aHostAPI));
} else {
CK_LOGE("GMPGetAPI couldn't resolve API name |%s|\n", aApiName);
}
cdm::Host_8* host = static_cast<cdm::Host_8*>(
get_cdm_host_func(cdm_interface_version, user_data));
ClearKeyCDM* clearKey = new ClearKeyCDM(host);
CK_LOGE("Created ClearKeyCDM instance!");
return clearKey;
return *aPluginAPI ? GMPNoErr : GMPNotImplementedErr;
}
GMP_EXPORT GMPErr
GMPShutdown(void)
{
CK_LOGD("ClearKey GMPShutdown");
return GMPNoErr;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,47 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Original author: ekr@rtfm.com
#ifndef gmp_task_utils_h_
#define gmp_task_utils_h_
#include "gmp-api/gmp-platform.h"
class gmp_task_args_base : public GMPTask {
public:
virtual void Destroy() { delete this; }
virtual void Run() = 0;
};
// The generated file contains four major function templates
// (in variants for arbitrary numbers of arguments up to 10,
// which is why it is machine generated). The four templates
// are:
//
// WrapTask(o, m, ...) -- wraps a member function m of an object ptr o
// WrapTaskRet(o, m, ..., r) -- wraps a member function m of an object ptr o
// the function returns something that can
// be assigned to *r
// WrapTaskNM(f, ...) -- wraps a function f
// WrapTaskNMRet(f, ..., r) -- wraps a function f that returns something
// that can be assigned to *r
//
// All of these template functions return a GMPTask* which can be passed
// to DispatchXX().
#include "gmp-task-utils-generated.h"
#endif // gmp_task_utils_h_

View File

@ -11,8 +11,8 @@ FINAL_TARGET = 'dist/bin/gmp-clearkey/0.1'
FINAL_TARGET_PP_FILES += ['manifest.json.in']
UNIFIED_SOURCES += [
'ClearKeyAsyncShutdown.cpp',
'ClearKeyBase64.cpp',
'ClearKeyCDM.cpp',
'ClearKeyDecryptionManager.cpp',
'ClearKeyPersistence.cpp',
'ClearKeySession.cpp',
@ -28,6 +28,7 @@ SOURCES += [
if CONFIG['OS_ARCH'] == 'WINNT':
UNIFIED_SOURCES += [
'AnnexB.cpp',
'VideoDecoder.cpp',
'WMFH264Decoder.cpp',
]
@ -42,13 +43,15 @@ if CONFIG['OS_ARCH'] == 'WINNT':
DEFINES['ENABLE_WMF'] = True
DEFINES['CDM_IMPLEMENTATION'] = True
TEST_DIRS += [
'gtest',
]
LOCAL_INCLUDES += [
'/dom/media/gmp',
]
DISABLE_STL_WRAPPING = True
DEFINES['MOZ_NO_MOZALLOC'] = True