Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "WMFClearKeyDecryptor.h"
#include <mfapi.h>
#include <mferror.h>
#include <vector>
#include "WMFClearKeyCDM.h"
#include "WMFDecryptedBlock.h"
#include "content_decryption_module.h"
namespace mozilla {
using Microsoft::WRL::ComPtr;
WMFClearKeyDecryptor::~WMFClearKeyDecryptor() { ENTRY_LOG(); }
HRESULT WMFClearKeyDecryptor::RuntimeClassInitialize(
SessionManagerWrapper* aSessionManager) {
ENTRY_LOG();
MOZ_ASSERT(aSessionManager);
mSessionManager = aSessionManager;
return S_OK;
}
STDMETHODIMP WMFClearKeyDecryptor::GetStreamLimits(DWORD* aInputMin,
DWORD* aInputMax,
DWORD* aOutputMin,
DWORD* aOutputMax) {
*aInputMin = *aInputMax = *aOutputMin = *aOutputMax = 1;
return S_OK;
}
STDMETHODIMP WMFClearKeyDecryptor::GetStreamCount(DWORD* aInputStreams,
DWORD* aOutputStreams) {
*aInputStreams = *aOutputStreams = 1;
return S_OK;
}
STDMETHODIMP WMFClearKeyDecryptor::GetStreamIDs(DWORD aInputSize,
DWORD* aInputIDs,
DWORD aOutputSize,
DWORD* aOutputIDs) {
return E_NOTIMPL;
}
STDMETHODIMP WMFClearKeyDecryptor::GetInputStreamInfo(
DWORD aInputStreamID, MFT_INPUT_STREAM_INFO* aStreamInfo) {
if (aInputStreamID != 0) {
return MF_E_INVALIDSTREAMNUMBER;
}
aStreamInfo->hnsMaxLatency = 0;
aStreamInfo->dwFlags = 0;
aStreamInfo->cbSize = 0;
aStreamInfo->cbMaxLookahead = 0;
aStreamInfo->cbAlignment = 0;
return S_OK;
}
STDMETHODIMP WMFClearKeyDecryptor::GetOutputStreamInfo(
DWORD aOutputStreamID, MFT_OUTPUT_STREAM_INFO* aStreamInfo) {
if (aOutputStreamID != 0) {
return MF_E_INVALIDSTREAMNUMBER;
}
aStreamInfo->dwFlags = MFT_OUTPUT_STREAM_PROVIDES_SAMPLES;
aStreamInfo->cbSize = 0;
aStreamInfo->cbAlignment = 0;
return S_OK;
}
STDMETHODIMP WMFClearKeyDecryptor::GetAttributes(IMFAttributes** aAttributes) {
return E_NOTIMPL;
}
STDMETHODIMP WMFClearKeyDecryptor::GetInputStreamAttributes(
DWORD aInputStreamID, IMFAttributes** aAttributes) {
return E_NOTIMPL;
}
STDMETHODIMP WMFClearKeyDecryptor::GetOutputStreamAttributes(
DWORD aOutputStreamID, IMFAttributes** aAttributes) {
return E_NOTIMPL;
}
STDMETHODIMP WMFClearKeyDecryptor::DeleteInputStream(DWORD aStreamID) {
return E_NOTIMPL;
}
STDMETHODIMP WMFClearKeyDecryptor::AddInputStreams(DWORD aStreams,
DWORD* aStreamIDs) {
return E_NOTIMPL;
}
STDMETHODIMP WMFClearKeyDecryptor::GetInputAvailableType(DWORD aInputStreamID,
DWORD aTypeIndex,
IMFMediaType** aType) {
std::lock_guard<std::mutex> lock(mMutex);
if (aInputStreamID != 0) {
return MF_E_INVALIDSTREAMNUMBER;
}
if (aTypeIndex != 0 || !mInputType) {
return MF_E_NO_MORE_TYPES;
}
return mInputType.CopyTo(aType);
}
STDMETHODIMP WMFClearKeyDecryptor::GetOutputAvailableType(
DWORD aOutputStreamID, DWORD aTypeIndex, IMFMediaType** aType) {
std::lock_guard<std::mutex> lock(mMutex);
if (aOutputStreamID != 0) {
return MF_E_INVALIDSTREAMNUMBER;
}
if (aTypeIndex != 0 || !mOutputType) {
return MF_E_NO_MORE_TYPES;
}
return mOutputType.CopyTo(aType);
}
STDMETHODIMP WMFClearKeyDecryptor::SetInputType(DWORD aInputStreamID,
IMFMediaType* aType,
DWORD aFlags) {
if (aInputStreamID != 0) {
return MF_E_INVALIDSTREAMNUMBER;
}
std::lock_guard<std::mutex> lock(mMutex);
if (aFlags & MFT_SET_TYPE_TEST_ONLY) {
return S_OK;
}
// Unwrap the protected media type to get the real underlying media type.
ComPtr<IMFMediaType> unwrapped;
if (aType && SUCCEEDED(MFUnwrapMediaType(aType, &unwrapped))) {
mInputType = unwrapped;
} else {
mInputType = aType;
}
if (!mOutputType && mInputType) {
// Mirror input type as output type.
RETURN_IF_FAILED(MFCreateMediaType(&mOutputType));
RETURN_IF_FAILED(mInputType->CopyAllItems(mOutputType.Get()));
}
return S_OK;
}
STDMETHODIMP WMFClearKeyDecryptor::SetOutputType(DWORD aOutputStreamID,
IMFMediaType* aType,
DWORD aFlags) {
if (aOutputStreamID != 0) {
return MF_E_INVALIDSTREAMNUMBER;
}
std::lock_guard<std::mutex> lock(mMutex);
if (aFlags & MFT_SET_TYPE_TEST_ONLY) {
return S_OK;
}
mOutputType = aType;
return S_OK;
}
STDMETHODIMP WMFClearKeyDecryptor::GetInputCurrentType(DWORD aInputStreamID,
IMFMediaType** aType) {
std::lock_guard<std::mutex> lock(mMutex);
if (aInputStreamID != 0) {
return MF_E_INVALIDSTREAMNUMBER;
}
if (!mInputType) {
return MF_E_TRANSFORM_TYPE_NOT_SET;
}
return mInputType.CopyTo(aType);
}
STDMETHODIMP WMFClearKeyDecryptor::GetOutputCurrentType(DWORD aOutputStreamID,
IMFMediaType** aType) {
std::lock_guard<std::mutex> lock(mMutex);
if (aOutputStreamID != 0) {
return MF_E_INVALIDSTREAMNUMBER;
}
if (!mOutputType) {
return MF_E_TRANSFORM_TYPE_NOT_SET;
}
return mOutputType.CopyTo(aType);
}
STDMETHODIMP WMFClearKeyDecryptor::GetInputStatus(DWORD aInputStreamID,
DWORD* aFlags) {
std::lock_guard<std::mutex> lock(mMutex);
if (aInputStreamID != 0) {
return MF_E_INVALIDSTREAMNUMBER;
}
*aFlags = mInputSample ? 0 : MFT_INPUT_STATUS_ACCEPT_DATA;
return S_OK;
}
STDMETHODIMP WMFClearKeyDecryptor::GetOutputStatus(DWORD* aFlags) {
std::lock_guard<std::mutex> lock(mMutex);
*aFlags = mInputSample ? MFT_OUTPUT_STATUS_SAMPLE_READY : 0;
return S_OK;
}
STDMETHODIMP WMFClearKeyDecryptor::SetOutputBounds(LONGLONG aLowerBound,
LONGLONG aUpperBound) {
return E_NOTIMPL;
}
STDMETHODIMP WMFClearKeyDecryptor::ProcessEvent(DWORD aInputStreamID,
IMFMediaEvent* aEvent) {
return S_OK;
}
STDMETHODIMP WMFClearKeyDecryptor::ProcessMessage(MFT_MESSAGE_TYPE aMessage,
ULONG_PTR aParam) {
return S_OK;
}
STDMETHODIMP WMFClearKeyDecryptor::ProcessInput(DWORD aInputStreamID,
IMFSample* aSample,
DWORD aFlags) {
ENTRY_LOG();
if (aInputStreamID != 0) {
return MF_E_INVALIDSTREAMNUMBER;
}
if (!aSample) {
return E_INVALIDARG;
}
std::lock_guard<std::mutex> lock(mMutex);
if (mInputSample) {
return MF_E_NOTACCEPTING;
}
mInputSample = aSample;
return S_OK;
}
HRESULT WMFClearKeyDecryptor::DecryptSample(IMFSample* aEncryptedSample,
IMFSample** aDecryptedSample) {
ENTRY_LOG();
// Extract IV (stored as the SampleID blob, 16 bytes).
BYTE* ivData = nullptr;
UINT32 ivSize = 0;
HRESULT hr = aEncryptedSample->GetAllocatedBlob(
MFSampleExtension_Encryption_SampleID, &ivData, &ivSize);
const bool isEncrypted = SUCCEEDED(hr) && ivSize > 0;
LOG("isEncrypted=%d, ivSize=%u", isEncrypted, ivSize);
// Copy all buffer data from the sample.
DWORD totalLen = 0;
RETURN_IF_FAILED(aEncryptedSample->GetTotalLength(&totalLen));
std::vector<uint8_t> sampleData(totalLen);
{
ComPtr<IMFMediaBuffer> contiguousBuffer;
RETURN_IF_FAILED(
aEncryptedSample->ConvertToContiguousBuffer(&contiguousBuffer));
BYTE* bufferData = nullptr;
DWORD currentLen = 0;
RETURN_IF_FAILED(contiguousBuffer->Lock(&bufferData, nullptr, &currentLen));
if (currentLen > 0) {
memcpy(sampleData.data(), bufferData, currentLen);
}
contiguousBuffer->Unlock();
}
WMFDecryptedBlock decryptedBlock;
if (isEncrypted) {
// Extract key ID (stored as a GUID).
GUID keyIdGuid = GUID_NULL;
RETURN_IF_FAILED(
aEncryptedSample->GetGUID(MFSampleExtension_Content_KeyID, &keyIdGuid));
uint8_t keyId[16];
GuidToKeyId(keyIdGuid, keyId);
// Extract subsample mapping if present.
BYTE* subsampleData = nullptr;
UINT32 subsampleDataSize = 0;
std::vector<cdm::SubsampleEntry> subsamples;
if (SUCCEEDED(aEncryptedSample->GetAllocatedBlob(
MFSampleExtension_Encryption_SubSample_Mapping, &subsampleData,
&subsampleDataSize)) &&
subsampleDataSize >= 8) {
// Each entry is {DWORD clearBytes, DWORD cipherBytes} = 8 bytes.
const DWORD numEntries = subsampleDataSize / 8;
subsamples.resize(numEntries);
for (DWORD i = 0; i < numEntries; i++) {
DWORD clearBytes = 0, cipherBytes = 0;
memcpy(&clearBytes, subsampleData + i * 8, sizeof(DWORD));
memcpy(&cipherBytes, subsampleData + i * 8 + sizeof(DWORD),
sizeof(DWORD));
subsamples[i].clear_bytes = clearBytes;
subsamples[i].cipher_bytes = cipherBytes;
}
CoTaskMemFree(subsampleData);
} else {
if (subsampleData) {
CoTaskMemFree(subsampleData);
}
// No subsample info: treat the whole buffer as cipher bytes.
cdm::SubsampleEntry entry;
entry.clear_bytes = 0;
entry.cipher_bytes = totalLen;
subsamples.push_back(entry);
}
LONGLONG sampleTime = 0;
aEncryptedSample->GetSampleTime(&sampleTime);
cdm::InputBuffer_2 inputBuffer = {};
inputBuffer.data = sampleData.data();
inputBuffer.data_size = static_cast<uint32_t>(sampleData.size());
inputBuffer.encryption_scheme = cdm::EncryptionScheme::kCenc;
inputBuffer.key_id = keyId;
inputBuffer.key_id_size = 16;
inputBuffer.iv = ivData;
inputBuffer.iv_size = ivSize;
inputBuffer.subsamples = subsamples.data();
inputBuffer.num_subsamples = static_cast<uint32_t>(subsamples.size());
inputBuffer.timestamp = sampleTime;
HRESULT decryptHr = mSessionManager->Decrypt(inputBuffer, &decryptedBlock);
LOG("Decrypt hr=%lx", static_cast<long>(decryptHr));
CoTaskMemFree(ivData);
RETURN_IF_FAILED(decryptHr);
} else {
if (ivData) {
CoTaskMemFree(ivData);
}
LOG("Clear sample passthrough, size=%lu", totalLen);
WMFDecryptedBuffer* buffer =
new WMFDecryptedBuffer(static_cast<uint32_t>(sampleData.size()));
memcpy(buffer->Data(), sampleData.data(), sampleData.size());
decryptedBlock.SetDecryptedBuffer(buffer);
}
cdm::Buffer* decryptedBuffer = decryptedBlock.DecryptedBuffer();
if (!decryptedBuffer || !decryptedBuffer->Data()) {
LOG("Decrypt produced null or empty buffer");
return E_FAIL;
}
// Wrap decrypted data in a new IMFSample.
ComPtr<IMFSample> outputSample;
RETURN_IF_FAILED(MFCreateSample(&outputSample));
ComPtr<IMFMediaBuffer> outputBuffer;
RETURN_IF_FAILED(MFCreateMemoryBuffer(
static_cast<DWORD>(decryptedBuffer->Size()), &outputBuffer));
BYTE* outputData = nullptr;
RETURN_IF_FAILED(outputBuffer->Lock(&outputData, nullptr, nullptr));
memcpy(outputData, decryptedBuffer->Data(), decryptedBuffer->Size());
outputBuffer->Unlock();
RETURN_IF_FAILED(outputBuffer->SetCurrentLength(
static_cast<DWORD>(decryptedBuffer->Size())));
RETURN_IF_FAILED(outputSample->AddBuffer(outputBuffer.Get()));
LONGLONG sampleTime = 0;
LONGLONG sampleDuration = 0;
if (SUCCEEDED(aEncryptedSample->GetSampleTime(&sampleTime))) {
outputSample->SetSampleTime(sampleTime);
}
if (SUCCEEDED(aEncryptedSample->GetSampleDuration(&sampleDuration))) {
outputSample->SetSampleDuration(sampleDuration);
}
// Propagate the keyframe flag so the downstream H264 decoder knows which
// samples are IDR frames.
UINT32 isCleanPoint = 0;
if (SUCCEEDED(aEncryptedSample->GetUINT32(MFSampleExtension_CleanPoint,
&isCleanPoint)) &&
isCleanPoint) {
outputSample->SetUINT32(MFSampleExtension_CleanPoint, 1);
}
*aDecryptedSample = outputSample.Detach();
return S_OK;
}
STDMETHODIMP WMFClearKeyDecryptor::ProcessOutput(
DWORD aFlags, DWORD aOutputBufferCount,
MFT_OUTPUT_DATA_BUFFER* aOutputSamples, DWORD* aStatus) {
ENTRY_LOG();
*aStatus = 0;
if (aOutputBufferCount != 1) {
LOG("Invalid output buffer count: %lu", aOutputBufferCount);
return E_INVALIDARG;
}
ComPtr<IMFSample> encryptedSample;
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mInputSample) {
LOG("No input sample available");
return MF_E_TRANSFORM_NEED_MORE_INPUT;
}
encryptedSample = std::move(mInputSample);
}
// The MFT allocates output samples (MFT_OUTPUT_STREAM_PROVIDES_SAMPLES).
MOZ_ASSERT(!aOutputSamples[0].pSample);
ComPtr<IMFSample> decryptedSample;
RETURN_IF_FAILED(DecryptSample(encryptedSample.Get(), &decryptedSample));
aOutputSamples[0].pSample = decryptedSample.Detach();
return S_OK;
}
} // namespace mozilla