diff options
Diffstat (limited to 'media/gmp-clearkey')
-rw-r--r-- | media/gmp-clearkey/0.1/ClearKeyCDM.cpp | 191 | ||||
-rw-r--r-- | media/gmp-clearkey/0.1/ClearKeyCDM.h | 103 | ||||
-rw-r--r-- | media/gmp-clearkey/0.1/VideoDecoder.cpp | 325 | ||||
-rw-r--r-- | media/gmp-clearkey/0.1/VideoDecoder.h | 72 | ||||
-rw-r--r-- | media/gmp-clearkey/0.1/WMFH264Decoder.cpp | 331 | ||||
-rw-r--r-- | media/gmp-clearkey/0.1/WMFH264Decoder.h | 70 | ||||
-rw-r--r-- | media/gmp-clearkey/0.1/WMFSymbols.h | 20 | ||||
-rw-r--r-- | media/gmp-clearkey/0.1/WMFUtils.cpp | 223 | ||||
-rw-r--r-- | media/gmp-clearkey/0.1/WMFUtils.h | 248 | ||||
-rw-r--r-- | media/gmp-clearkey/0.1/gmp-clearkey.cpp | 173 | ||||
-rw-r--r-- | media/gmp-clearkey/0.1/manifest.json.in | 13 | ||||
-rw-r--r-- | media/gmp-clearkey/0.1/moz.build | 62 |
12 files changed, 1831 insertions, 0 deletions
diff --git a/media/gmp-clearkey/0.1/ClearKeyCDM.cpp b/media/gmp-clearkey/0.1/ClearKeyCDM.cpp new file mode 100644 index 0000000000..10ec6d3fe2 --- /dev/null +++ b/media/gmp-clearkey/0.1/ClearKeyCDM.cpp @@ -0,0 +1,191 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "ClearKeyCDM.h" + +#include "ClearKeyUtils.h" + +using namespace cdm; + +ClearKeyCDM::ClearKeyCDM(Host_10* aHost) { + mHost = aHost; + mSessionManager = new ClearKeySessionManager(mHost); +} + +void ClearKeyCDM::Initialize(bool aAllowDistinctiveIdentifier, + bool aAllowPersistentState, + bool aUseHardwareSecureCodecs) { + mSessionManager->Init(aAllowDistinctiveIdentifier, aAllowPersistentState); + // We call mHost->OnInitialized() in the session manager once it has + // initialized. +} + +void ClearKeyCDM::GetStatusForPolicy(uint32_t aPromiseId, + const Policy& aPolicy) { + // Pretend the device is HDCP 2.1 compliant. + const cdm::HdcpVersion kDeviceHdcpVersion = cdm::kHdcpVersion2_1; + if (aPolicy.min_hdcp_version <= kDeviceHdcpVersion) { + mHost->OnResolveKeyStatusPromise(aPromiseId, KeyStatus::kUsable); + } else { + mHost->OnResolveKeyStatusPromise(aPromiseId, KeyStatus::kOutputRestricted); + } +} +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_2& aEncryptedBuffer, + DecryptedBlock* aDecryptedBuffer) { + if (mIsProtectionQueryEnabled) { + // Piggyback this check onto Decrypt calls. If Clearkey implements a timer + // based approach for firing events, we could instead trigger the check + // using that mechanism. + mSessionManager->QueryOutputProtectionStatusIfNeeded(); + } + return mSessionManager->Decrypt(aEncryptedBuffer, aDecryptedBuffer); +} + +Status ClearKeyCDM::InitializeAudioDecoder( + const AudioDecoderConfig_2& 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_2& aVideoDecoderConfig) { +#ifdef ENABLE_WMF + mVideoDecoder = new VideoDecoder(mHost); + return mVideoDecoder->InitDecode(aVideoDecoderConfig); +#else + return Status::kDecodeError; +#endif +} + +void ClearKeyCDM::DeinitializeDecoder(StreamType aDecoderType) { +#ifdef ENABLE_WMF + if (aDecoderType == StreamType::kStreamTypeVideo) { + mVideoDecoder->DecodingComplete(); + mVideoDecoder = nullptr; + } +#endif +} + +void ClearKeyCDM::ResetDecoder(StreamType aDecoderType) { +#ifdef ENABLE_WMF + if (aDecoderType == StreamType::kStreamTypeVideo) { + mVideoDecoder->Reset(); + } +#endif +} + +Status ClearKeyCDM::DecryptAndDecodeFrame(const InputBuffer_2& aEncryptedBuffer, + VideoFrame* aVideoFrame) { +#ifdef ENABLE_WMF + if (mIsProtectionQueryEnabled) { + // Piggyback this check onto Decrypt + Decode. If Clearkey implements a + // timer based approach for firing events, we could instead trigger the + // check using that mechanism. + mSessionManager->QueryOutputProtectionStatusIfNeeded(); + } + return mVideoDecoder->Decode(aEncryptedBuffer, aVideoFrame); +#else + return Status::kDecodeError; +#endif +} + +Status ClearKeyCDM::DecryptAndDecodeSamples( + const InputBuffer_2& 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) { + // The higher level GMP machinery should not forward us query information + // unless we've requested it (even if mutiple CDMs exist at once and some + // others are reqeusting this info). If this assert fires we're violating + // that. + MOZ_ASSERT(mIsProtectionQueryEnabled, + "Should only receive a protection status " + "mIsProtectionQueryEnabled is true"); + // The session manager handles the guts of this for ClearKey. + mSessionManager->OnQueryOutputProtectionStatus(aResult, aLinkMask, + aOutputProtectionMask); +} + +void ClearKeyCDM::OnStorageId(uint32_t aVersion, const uint8_t* aStorageId, + uint32_t aStorageIdSize) { + // This function should never be called and is not supported. + assert(false); +} + +void ClearKeyCDM::Destroy() { + mSessionManager->DecryptingComplete(); +#ifdef ENABLE_WMF + // If we have called 'DeinitializeDecoder' mVideoDecoder will be null. + if (mVideoDecoder) { + mVideoDecoder->DecodingComplete(); + } +#endif + delete this; +} + +void ClearKeyCDM::EnableProtectionQuery() { + MOZ_ASSERT(!mIsProtectionQueryEnabled, + "Should not be called more than once per CDM"); + mIsProtectionQueryEnabled = true; +} diff --git a/media/gmp-clearkey/0.1/ClearKeyCDM.h b/media/gmp-clearkey/0.1/ClearKeyCDM.h new file mode 100644 index 0000000000..6977b0e787 --- /dev/null +++ b/media/gmp-clearkey/0.1/ClearKeyCDM.h @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef ClearKeyCDM_h_ +#define ClearKeyCDM_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 "ClearKeySessionManager.h" + +#ifdef ENABLE_WMF +# include "VideoDecoder.h" +# include "WMFUtils.h" +#endif + +class ClearKeyCDM : public cdm::ContentDecryptionModule_10 { + private: + RefPtr<ClearKeySessionManager> mSessionManager; +#ifdef ENABLE_WMF + RefPtr<VideoDecoder> mVideoDecoder; +#endif + bool mIsProtectionQueryEnabled = false; + + protected: + cdm::Host_10* mHost; + + public: + explicit ClearKeyCDM(cdm::Host_10* aHost); + + void Initialize(bool aAllowDistinctiveIdentifier, bool aAllowPersistentState, + bool aUseHardwareSecureCodecs) override; + + void GetStatusForPolicy(uint32_t aPromiseId, + const cdm::Policy& aPolicy) 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_2& aEncryptedBuffer, + cdm::DecryptedBlock* aDecryptedBuffer) override; + + cdm::Status InitializeAudioDecoder( + const cdm::AudioDecoderConfig_2& aAudioDecoderConfig) override; + + cdm::Status InitializeVideoDecoder( + const cdm::VideoDecoderConfig_2& aVideoDecoderConfig) override; + + void DeinitializeDecoder(cdm::StreamType aDecoderType) override; + + void ResetDecoder(cdm::StreamType aDecoderType) override; + + cdm::Status DecryptAndDecodeFrame(const cdm::InputBuffer_2& aEncryptedBuffer, + cdm::VideoFrame* aVideoFrame) override; + + cdm::Status DecryptAndDecodeSamples( + const cdm::InputBuffer_2& 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 OnStorageId(uint32_t aVersion, const uint8_t* aStorageId, + uint32_t aStorageIdSize) override; + + void Destroy() override; + + void EnableProtectionQuery(); +}; + +#endif diff --git a/media/gmp-clearkey/0.1/VideoDecoder.cpp b/media/gmp-clearkey/0.1/VideoDecoder.cpp new file mode 100644 index 0000000000..6417defdaf --- /dev/null +++ b/media/gmp-clearkey/0.1/VideoDecoder.cpp @@ -0,0 +1,325 @@ +/* + * Copyright 2013, 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 <algorithm> +#include <cstdint> +#include <limits> + +#include "BigEndian.h" +#include "ClearKeyUtils.h" +#include "ClearKeyDecryptionManager.h" +#include "VideoDecoder.h" +#include "content_decryption_module.h" +#include "mozilla/CheckedInt.h" + +using namespace wmf; + +VideoDecoder::VideoDecoder(cdm::Host_10* aHost) + : mHost(aHost), mHasShutdown(false) { + CK_LOGD("VideoDecoder created"); + + // 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); + if (FAILED(hr)) { + CK_LOGE("Failed to initialize mDecoder!"); + } +} + +VideoDecoder::~VideoDecoder() { CK_LOGD("VideoDecoder destroyed"); } + +cdm::Status VideoDecoder::InitDecode(const cdm::VideoDecoderConfig_2& aConfig) { + CK_LOGD("VideoDecoder::InitDecode"); + + if (!mDecoder) { + CK_LOGD("VideoDecoder::InitDecode failed to init WMFH264Decoder"); + + return cdm::Status::kDecodeError; + } + + return cdm::Status::kSuccess; +} + +cdm::Status VideoDecoder::Decode(const cdm::InputBuffer_2& aInputBuffer, + cdm::VideoFrame* aVideoFrame) { + CK_LOGD("VideoDecoder::Decode"); + // 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("VideoDecoder::Decode Input buffer null: Draining"); + return Drain(aVideoFrame); + } + + DecodeData* data = new DecodeData(); + Assign(data->mBuffer, aInputBuffer.data, aInputBuffer.data_size); + data->mTimestamp = aInputBuffer.timestamp; + data->mCrypto = CryptoMetaData(&aInputBuffer); + + AutoPtr<DecodeData> d(data); + HRESULT hr; + + if (!data || !mDecoder) { + CK_LOGE("Decode job not set up correctly!"); + return cdm::Status::kDecodeError; + } + + std::vector<uint8_t>& buffer = data->mBuffer; + + if (data->mCrypto.IsValid()) { + cdm::Status rv = + ClearKeyDecryptionManager::Get()->Decrypt(buffer, data->mCrypto); + + if (STATUS_FAILED(rv)) { + CK_LOGARRAY("Failed to decrypt video using key ", aInputBuffer.key_id, + aInputBuffer.key_id_size); + return rv; + } + } + + hr = mDecoder->Input(buffer.data(), buffer.size(), data->mTimestamp); + + CK_LOGD("VideoDecoder::Decode() Input ret hr=0x%x", hr); + + if (FAILED(hr)) { + assert(hr != MF_E_TRANSFORM_NEED_MORE_INPUT); + + CK_LOGE("VideoDecoder::Decode() decode failed ret=0x%x%s", hr, + ((hr == MF_E_NOTACCEPTING) ? " (MF_E_NOTACCEPTING)" : "")); + CK_LOGD("Decode failed. The decoder is not accepting input"); + return cdm::Status::kDecodeError; + } + + return OutputFrame(aVideoFrame); +} + +cdm::Status VideoDecoder::OutputFrame(cdm::VideoFrame* aVideoFrame) { + CK_LOGD("VideoDecoder::OutputFrame"); + + 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) { + CComPtr<IMFSample> output; + hr = mDecoder->Output(&output); + + if (hr != S_OK) { + break; + } + + CK_LOGD("VideoDecoder::OutputFrame Decoder output ret=0x%x", hr); + + mOutputQueue.push(output); + CK_LOGD("VideoDecoder::OutputFrame: Queue size: %u", mOutputQueue.size()); + } + + // 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 cdm::Status::kNeedMoreData; + } + + // 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", hr); + return cdm::Status::kDecodeError; + } + + 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) { + CK_LOGD("VideoDecoder::OutputFrame Failed! (negative stride)"); + return cdm::Status::kDecodeError; + } + + const IntRect& picture = mDecoder->GetPictureRegion(); + hr = SampleToVideoFrame(result, picture.width, picture.height, + mDecoder->GetStride(), mDecoder->GetFrameHeight(), + aVideoFrame); + if (FAILED(hr)) { + CK_LOGD("VideoDecoder::OutputFrame Failed!"); + return cdm::Status::kDecodeError; + } + + CK_LOGD("VideoDecoder::OutputFrame Succeeded."); + return cdm::Status::kSuccess; +} + +HRESULT +VideoDecoder::SampleToVideoFrame(IMFSample* aSample, int32_t aPictureWidth, + int32_t aPictureHeight, int32_t aStride, + int32_t aFrameHeight, + cdm::VideoFrame* aVideoFrame) { + CK_LOGD("[%p] VideoDecoder::SampleToVideoFrame()", this); + + ENSURE(aSample != nullptr, E_POINTER); + ENSURE(aVideoFrame != nullptr, E_POINTER); + + HRESULT hr; + CComPtr<IMFMediaBuffer> mediaBuffer; + + aVideoFrame->SetFormat(cdm::kI420); + + // Must convert to contiguous mediaBuffer to use IMD2DBuffer interface. + hr = aSample->ConvertToContiguousBuffer(&mediaBuffer); + ENSURE(SUCCEEDED(hr), hr); + + // Try and use the IMF2DBuffer interface if available, otherwise fallback + // to the IMFMediaBuffer interface. Apparently IMF2DBuffer is more efficient, + // but only some systems (Windows 8?) support it. + BYTE* data = nullptr; + LONG stride = 0; + CComPtr<IMF2DBuffer> twoDBuffer; + hr = mediaBuffer->QueryInterface(static_cast<IMF2DBuffer**>(&twoDBuffer)); + if (SUCCEEDED(hr)) { + hr = twoDBuffer->Lock2D(&data, &stride); + ENSURE(SUCCEEDED(hr), hr); + } else { + hr = mediaBuffer->Lock(&data, nullptr, nullptr); + ENSURE(SUCCEEDED(hr), hr); + stride = aStride; + } + + // WMF stores the U and V planes 16-row-aligned, so we need to add padding + // to the row heights to ensure the source offsets of the Y'CbCr planes are + // referenced properly. + // YV12, planar format: [YYYY....][UUUU....][VVVV....] + // i.e., Y, then U, then V. + uint32_t padding = 0; + if (aFrameHeight % 16 != 0) { + padding = 16 - (aFrameHeight % 16); + } + uint32_t srcYSize = stride * (aFrameHeight + padding); + uint32_t srcUVSize = stride * (aFrameHeight + padding) / 4; + uint32_t halfStride = (stride + 1) / 2; + + aVideoFrame->SetStride(cdm::VideoPlane::kYPlane, stride); + aVideoFrame->SetStride(cdm::VideoPlane::kUPlane, halfStride); + aVideoFrame->SetStride(cdm::VideoPlane::kVPlane, halfStride); + + aVideoFrame->SetSize(cdm::Size{aPictureWidth, aPictureHeight}); + + // Note: We allocate the minimal sized buffer required to send the + // frame back over to the parent process. This is so that we request the + // same sized frame as the buffer allocator expects. + using mozilla::CheckedUint32; + CheckedUint32 bufferSize = CheckedUint32(stride) * aPictureHeight + + ((CheckedUint32(stride) * aPictureHeight) / 4) * 2; + + // If the buffer is bigger than the max for a 32 bit, fail to avoid buffer + // overflows. + if (!bufferSize.isValid()) { + CK_LOGD("VideoDecoder::SampleToFrame Buffersize bigger than UINT32_MAX"); + return E_FAIL; + } + + // Get the buffer from the host. + cdm::Buffer* buffer = mHost->Allocate(bufferSize.value()); + aVideoFrame->SetFrameBuffer(buffer); + + // Make sure the buffer is non-null (allocate guarantees it will be of + // sufficient size). + if (!buffer) { + CK_LOGD("VideoDecoder::SampleToFrame Out of memory"); + return E_OUTOFMEMORY; + } + + uint8_t* outBuffer = buffer->Data(); + + aVideoFrame->SetPlaneOffset(cdm::VideoPlane::kYPlane, 0); + + // Offset of U plane is the size of the Y plane, excluding the padding that + // WMF adds. + uint32_t dstUOffset = stride * aPictureHeight; + aVideoFrame->SetPlaneOffset(cdm::VideoPlane::kUPlane, dstUOffset); + + // Offset of the V plane is the size of the Y plane + the size of the U plane, + // excluding any padding WMF adds. + uint32_t dstVOffset = stride * aPictureHeight + (stride * aPictureHeight) / 4; + aVideoFrame->SetPlaneOffset(cdm::VideoPlane::kVPlane, dstVOffset); + + // Copy the pixel data, excluding WMF's padding. + memcpy(outBuffer, data, stride * aPictureHeight); + memcpy(outBuffer + dstUOffset, data + srcYSize, + (stride * aPictureHeight) / 4); + memcpy(outBuffer + dstVOffset, data + srcYSize + srcUVSize, + (stride * aPictureHeight) / 4); + + if (twoDBuffer) { + twoDBuffer->Unlock2D(); + } else { + mediaBuffer->Unlock(); + } + + LONGLONG hns = 0; + hr = aSample->GetSampleTime(&hns); + ENSURE(SUCCEEDED(hr), hr); + + aVideoFrame->SetTimestamp(HNsToUsecs(hns)); + + return S_OK; +} + +void VideoDecoder::Reset() { + CK_LOGD("VideoDecoder::Reset"); + + if (mDecoder) { + mDecoder->Reset(); + } + + // Remove all the frames from the output queue. + while (!mOutputQueue.empty()) { + mOutputQueue.pop(); + } +} + +cdm::Status VideoDecoder::Drain(cdm::VideoFrame* aVideoFrame) { + CK_LOGD("VideoDecoder::Drain()"); + + if (!mDecoder) { + CK_LOGD("Drain failed! Decoder was not initialized"); + return cdm::Status::kDecodeError; + } + + mDecoder->Drain(); + + // Return any pending output. + return OutputFrame(aVideoFrame); +} + +void VideoDecoder::DecodingComplete() { + CK_LOGD("VideoDecoder::DecodingComplete()"); + + mHasShutdown = true; + + // Release the reference we added in the constructor. There may be + // WrapRefCounted tasks that also hold references to us, and keep + // us alive a little longer. + Release(); +} diff --git a/media/gmp-clearkey/0.1/VideoDecoder.h b/media/gmp-clearkey/0.1/VideoDecoder.h new file mode 100644 index 0000000000..6a1544ce65 --- /dev/null +++ b/media/gmp-clearkey/0.1/VideoDecoder.h @@ -0,0 +1,72 @@ +/* + * Copyright 2013, 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 __VideoDecoder_h__ +#define __VideoDecoder_h__ + +// This include is required in order for content_decryption_module to work +// on Unix systems. +#include <stddef.h> + +#include <atomic> +#include <queue> +#include <thread> + +#include "content_decryption_module.h" +#include "WMFH264Decoder.h" + +class VideoDecoder : public RefCounted { + public: + explicit VideoDecoder(cdm::Host_10* aHost); + + cdm::Status InitDecode(const cdm::VideoDecoderConfig_2& aConfig); + + cdm::Status Decode(const cdm::InputBuffer_2& aEncryptedBuffer, + cdm::VideoFrame* aVideoFrame); + + void Reset(); + + void DecodingComplete(); + + bool HasShutdown() { return mHasShutdown; } + + private: + virtual ~VideoDecoder(); + + cdm::Status Drain(cdm::VideoFrame* aVideoFrame); + + struct DecodeData { + std::vector<uint8_t> mBuffer; + uint64_t mTimestamp = 0; + CryptoMetaData mCrypto; + }; + + cdm::Status OutputFrame(cdm::VideoFrame* aVideoFrame); + + HRESULT SampleToVideoFrame(IMFSample* aSample, int32_t aPictureWidth, + int32_t aPictureHeight, int32_t aStride, + int32_t aFrameHeight, + cdm::VideoFrame* aVideoFrame); + + cdm::Host_10* mHost; + wmf::AutoPtr<wmf::WMFH264Decoder> mDecoder; + + std::queue<wmf::CComPtr<IMFSample>> mOutputQueue; + + bool mHasShutdown; +}; + +#endif // __VideoDecoder_h__ diff --git a/media/gmp-clearkey/0.1/WMFH264Decoder.cpp b/media/gmp-clearkey/0.1/WMFH264Decoder.cpp new file mode 100644 index 0000000000..8054b38c64 --- /dev/null +++ b/media/gmp-clearkey/0.1/WMFH264Decoder.cpp @@ -0,0 +1,331 @@ +/* + * Copyright 2013, 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 "WMFH264Decoder.h" + +#include <codecapi.h> + +#include <algorithm> + +namespace wmf { + +WMFH264Decoder::WMFH264Decoder() : mDecoder(nullptr) { + memset(&mInputStreamInfo, 0, sizeof(MFT_INPUT_STREAM_INFO)); + memset(&mOutputStreamInfo, 0, sizeof(MFT_OUTPUT_STREAM_INFO)); +} + +WMFH264Decoder::~WMFH264Decoder() {} + +HRESULT +WMFH264Decoder::Init(int32_t aCoreCount) { + HRESULT hr; + + hr = CreateMFT(__uuidof(CMSH264DecoderMFT), WMFDecoderDllName(), mDecoder); + if (FAILED(hr)) { + // Windows 7 Enterprise Server N (which is what Mozilla's mochitests run + // on) need a different CLSID to instantiate the H.264 decoder. + hr = CreateMFT(CLSID_CMSH264DecMFT, WMFDecoderDllName(), mDecoder); + } + ENSURE(SUCCEEDED(hr), hr); + + CComPtr<IMFAttributes> attr; + hr = mDecoder->GetAttributes(&attr); + ENSURE(SUCCEEDED(hr), hr); + hr = attr->SetUINT32(CODECAPI_AVDecNumWorkerThreads, + GetNumThreads(aCoreCount)); + ENSURE(SUCCEEDED(hr), hr); + + hr = SetDecoderInputType(); + ENSURE(SUCCEEDED(hr), hr); + + hr = SetDecoderOutputType(); + ENSURE(SUCCEEDED(hr), hr); + + hr = SendMFTMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0); + ENSURE(SUCCEEDED(hr), hr); + + hr = SendMFTMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0); + ENSURE(SUCCEEDED(hr), hr); + + hr = mDecoder->GetInputStreamInfo(0, &mInputStreamInfo); + ENSURE(SUCCEEDED(hr), hr); + + hr = mDecoder->GetOutputStreamInfo(0, &mOutputStreamInfo); + ENSURE(SUCCEEDED(hr), hr); + + return S_OK; +} + +HRESULT +WMFH264Decoder::ConfigureVideoFrameGeometry(IMFMediaType* aMediaType) { + ENSURE(aMediaType != nullptr, E_POINTER); + HRESULT hr; + + IntRect pictureRegion; + hr = wmf::GetPictureRegion(aMediaType, pictureRegion); + ENSURE(SUCCEEDED(hr), hr); + + UINT32 width = 0, height = 0; + hr = MFGetAttributeSize(aMediaType, MF_MT_FRAME_SIZE, &width, &height); + ENSURE(SUCCEEDED(hr), hr); + ENSURE(width <= mozilla::MAX_VIDEO_WIDTH, E_FAIL); + ENSURE(height <= mozilla::MAX_VIDEO_HEIGHT, E_FAIL); + + UINT32 stride = 0; + hr = GetDefaultStride(aMediaType, &stride); + ENSURE(SUCCEEDED(hr), hr); + ENSURE(stride <= mozilla::MAX_VIDEO_WIDTH, E_FAIL); + + // Success! Save state. + mStride = stride; + mVideoWidth = width; + mVideoHeight = height; + mPictureRegion = pictureRegion; + + LOG("WMFH264Decoder frame geometry frame=(%u,%u) stride=%u picture=(%d, %d, " + "%d, %d)\n", + width, height, mStride, mPictureRegion.x, mPictureRegion.y, + mPictureRegion.width, mPictureRegion.height); + + return S_OK; +} + +int32_t WMFH264Decoder::GetFrameHeight() const { return mVideoHeight; } + +const IntRect& WMFH264Decoder::GetPictureRegion() const { + return mPictureRegion; +} + +int32_t WMFH264Decoder::GetStride() const { return mStride; } + +HRESULT +WMFH264Decoder::SetDecoderInputType() { + HRESULT hr; + + CComPtr<IMFMediaType> type; + hr = MFCreateMediaType(&type); + ENSURE(SUCCEEDED(hr), hr); + + hr = type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); + ENSURE(SUCCEEDED(hr), hr); + + hr = type->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_H264); + ENSURE(SUCCEEDED(hr), hr); + + hr = type->SetUINT32(MF_MT_INTERLACE_MODE, + MFVideoInterlace_MixedInterlaceOrProgressive); + ENSURE(SUCCEEDED(hr), hr); + + hr = mDecoder->SetInputType(0, type, 0); + ENSURE(SUCCEEDED(hr), hr); + + return S_OK; +} + +HRESULT +WMFH264Decoder::SetDecoderOutputType() { + HRESULT hr; + + CComPtr<IMFMediaType> type; + + UINT32 typeIndex = 0; + while (static_cast<void>(type = nullptr), + SUCCEEDED(mDecoder->GetOutputAvailableType(0, typeIndex++, &type))) { + GUID subtype; + hr = type->GetGUID(MF_MT_SUBTYPE, &subtype); + if (FAILED(hr)) { + continue; + } + if (subtype == MFVideoFormat_I420 || subtype == MFVideoFormat_IYUV) { + // On Windows 7 Enterprise N the MFT reports it reports IYUV instead + // of I420. Other Windows' report I420. The formats are the same, so + // support both. + hr = mDecoder->SetOutputType(0, type, 0); + ENSURE(SUCCEEDED(hr), hr); + + hr = ConfigureVideoFrameGeometry(type); + ENSURE(SUCCEEDED(hr), hr); + + return S_OK; + } + } + + return E_FAIL; +} + +HRESULT +WMFH264Decoder::SendMFTMessage(MFT_MESSAGE_TYPE aMsg, UINT32 aData) { + ENSURE(mDecoder != nullptr, E_POINTER); + HRESULT hr = mDecoder->ProcessMessage(aMsg, aData); + ENSURE(SUCCEEDED(hr), hr); + return S_OK; +} + +HRESULT +WMFH264Decoder::CreateInputSample(const uint8_t* aData, uint32_t aDataSize, + Microseconds aTimestamp, + IMFSample** aOutSample) { + HRESULT hr; + CComPtr<IMFSample> sample; + hr = MFCreateSample(&sample); + ENSURE(SUCCEEDED(hr), hr); + + CComPtr<IMFMediaBuffer> buffer; + int32_t bufferSize = + std::max<uint32_t>(uint32_t(mInputStreamInfo.cbSize), aDataSize); + UINT32 alignment = + (mInputStreamInfo.cbAlignment > 1) ? mInputStreamInfo.cbAlignment - 1 : 0; + hr = MFCreateAlignedMemoryBuffer(bufferSize, alignment, &buffer); + ENSURE(SUCCEEDED(hr), hr); + + DWORD maxLength = 0; + DWORD currentLength = 0; + BYTE* dst = nullptr; + hr = buffer->Lock(&dst, &maxLength, ¤tLength); + ENSURE(SUCCEEDED(hr), hr); + + // Copy data into sample's buffer. + memcpy(dst, aData, aDataSize); + + hr = buffer->Unlock(); + ENSURE(SUCCEEDED(hr), hr); + + hr = buffer->SetCurrentLength(aDataSize); + ENSURE(SUCCEEDED(hr), hr); + + hr = sample->AddBuffer(buffer); + ENSURE(SUCCEEDED(hr), hr); + + hr = sample->SetSampleTime(UsecsToHNs(aTimestamp)); + ENSURE(SUCCEEDED(hr), hr); + + *aOutSample = sample.Detach(); + + return S_OK; +} + +HRESULT +WMFH264Decoder::CreateOutputSample(IMFSample** aOutSample) { + HRESULT hr; + CComPtr<IMFSample> sample; + hr = MFCreateSample(&sample); + ENSURE(SUCCEEDED(hr), hr); + + CComPtr<IMFMediaBuffer> buffer; + int32_t bufferSize = mOutputStreamInfo.cbSize; + UINT32 alignment = (mOutputStreamInfo.cbAlignment > 1) + ? mOutputStreamInfo.cbAlignment - 1 + : 0; + hr = MFCreateAlignedMemoryBuffer(bufferSize, alignment, &buffer); + ENSURE(SUCCEEDED(hr), hr); + + hr = sample->AddBuffer(buffer); + ENSURE(SUCCEEDED(hr), hr); + + *aOutSample = sample.Detach(); + + return S_OK; +} + +HRESULT +WMFH264Decoder::GetOutputSample(IMFSample** aOutSample) { + HRESULT hr; + // We allocate samples for MFT output. + MFT_OUTPUT_DATA_BUFFER output = {0}; + + CComPtr<IMFSample> sample; + hr = CreateOutputSample(&sample); + ENSURE(SUCCEEDED(hr), hr); + + output.pSample = sample; + + DWORD status = 0; + hr = mDecoder->ProcessOutput(0, 1, &output, &status); + // LOG(L"WMFH264Decoder::GetOutputSample() ProcessOutput returned 0x%x\n", + // hr); + CComPtr<IMFCollection> events = output.pEvents; // Ensure this is released. + + if (hr == MF_E_TRANSFORM_STREAM_CHANGE) { + // Type change. Probably geometric apperature change. + hr = SetDecoderOutputType(); + ENSURE(SUCCEEDED(hr), hr); + + return GetOutputSample(aOutSample); + } else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { + return MF_E_TRANSFORM_NEED_MORE_INPUT; + } + // Treat other errors as fatal. + ENSURE(SUCCEEDED(hr), hr); + + assert(sample); + + // output.pSample + *aOutSample = sample.Detach(); // AddRefs + return S_OK; +} + +HRESULT +WMFH264Decoder::Input(const uint8_t* aData, uint32_t aDataSize, + Microseconds aTimestamp) { + HRESULT hr; + CComPtr<IMFSample> input = nullptr; + hr = CreateInputSample(aData, aDataSize, aTimestamp, &input); + ENSURE(SUCCEEDED(hr) && input != nullptr, hr); + + hr = mDecoder->ProcessInput(0, input, 0); + if (hr == MF_E_NOTACCEPTING) { + // MFT *already* has enough data to produce a sample. Retrieve it. + LOG("ProcessInput returned MF_E_NOTACCEPTING\n"); + return MF_E_NOTACCEPTING; + } + ENSURE(SUCCEEDED(hr), hr); + + return S_OK; +} + +HRESULT +WMFH264Decoder::Output(IMFSample** aOutput) { + HRESULT hr; + CComPtr<IMFSample> outputSample; + hr = GetOutputSample(&outputSample); + if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { + return MF_E_TRANSFORM_NEED_MORE_INPUT; + } + // Treat other errors as fatal. + ENSURE(SUCCEEDED(hr) && outputSample, hr); + + *aOutput = outputSample.Detach(); + + return S_OK; +} + +HRESULT +WMFH264Decoder::Reset() { + HRESULT hr = SendMFTMessage(MFT_MESSAGE_COMMAND_FLUSH, 0); + ENSURE(SUCCEEDED(hr), hr); + + return S_OK; +} + +HRESULT +WMFH264Decoder::Drain() { + HRESULT hr = SendMFTMessage(MFT_MESSAGE_COMMAND_DRAIN, 0); + ENSURE(SUCCEEDED(hr), hr); + + return S_OK; +} + +} // namespace wmf diff --git a/media/gmp-clearkey/0.1/WMFH264Decoder.h b/media/gmp-clearkey/0.1/WMFH264Decoder.h new file mode 100644 index 0000000000..3f74735e23 --- /dev/null +++ b/media/gmp-clearkey/0.1/WMFH264Decoder.h @@ -0,0 +1,70 @@ +/* + * Copyright 2013, 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. + */ + +#if !defined(WMFH264Decoder_h_) +# define WMFH264Decoder_h_ + +# include "WMFUtils.h" + +namespace wmf { + +class WMFH264Decoder { + public: + WMFH264Decoder(); + ~WMFH264Decoder(); + + HRESULT Init(int32_t aCoreCount); + + HRESULT Input(const uint8_t* aData, uint32_t aDataSize, + Microseconds aTimestamp); + + HRESULT Output(IMFSample** aOutput); + + HRESULT Reset(); + + int32_t GetFrameHeight() const; + const IntRect& GetPictureRegion() const; + int32_t GetStride() const; + + HRESULT Drain(); + + private: + HRESULT SetDecoderInputType(); + HRESULT SetDecoderOutputType(); + HRESULT SendMFTMessage(MFT_MESSAGE_TYPE aMsg, UINT32 aData); + + HRESULT CreateInputSample(const uint8_t* aData, uint32_t aDataSize, + Microseconds aTimestamp, IMFSample** aOutSample); + + HRESULT CreateOutputSample(IMFSample** aOutSample); + + HRESULT GetOutputSample(IMFSample** aOutSample); + HRESULT ConfigureVideoFrameGeometry(IMFMediaType* aMediaType); + + MFT_INPUT_STREAM_INFO mInputStreamInfo; + MFT_OUTPUT_STREAM_INFO mOutputStreamInfo; + + CComPtr<IMFTransform> mDecoder; + + int32_t mVideoWidth; + int32_t mVideoHeight; + IntRect mPictureRegion; + int32_t mStride; +}; + +} // namespace wmf + +#endif diff --git a/media/gmp-clearkey/0.1/WMFSymbols.h b/media/gmp-clearkey/0.1/WMFSymbols.h new file mode 100644 index 0000000000..60a0a6854b --- /dev/null +++ b/media/gmp-clearkey/0.1/WMFSymbols.h @@ -0,0 +1,20 @@ +/* + * 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. + */ + +MFPLAT_FUNC(MFCreateSample, "mfplat.dll"); +MFPLAT_FUNC(MFCreateAlignedMemoryBuffer, "mfplat.dll"); +MFPLAT_FUNC(MFGetStrideForBitmapInfoHeader, "evr.dll"); +MFPLAT_FUNC(MFCreateMediaType, "mfplat.dll"); diff --git a/media/gmp-clearkey/0.1/WMFUtils.cpp b/media/gmp-clearkey/0.1/WMFUtils.cpp new file mode 100644 index 0000000000..5aa628f619 --- /dev/null +++ b/media/gmp-clearkey/0.1/WMFUtils.cpp @@ -0,0 +1,223 @@ +/* + * Copyright 2013, 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 "WMFUtils.h" + +#define INITGUID +#include <guiddef.h> +#include <stdio.h> +#include <versionhelpers.h> + +#include <algorithm> + +#include "ClearKeyUtils.h" + +#ifndef __MINGW32__ +# pragma comment(lib, "mfuuid.lib") +# pragma comment(lib, "wmcodecdspuuid") +#endif + +void LOG(const char* format, ...) { +#ifdef WMF_DECODER_LOG + va_list args; + va_start(args, format); + vprintf(format, args); +#endif +} + +DEFINE_GUID(CLSID_CMSH264DecMFT, 0x62CE7E72, 0x4C71, 0x4d20, 0xB1, 0x5D, 0x45, + 0x28, 0x31, 0xA8, 0x7D, 0x9D); + +namespace wmf { + +#define MFPLAT_FUNC(_func, _dllname) decltype(::_func)* _func; +#include "WMFSymbols.h" +#undef MFPLAT_FUNC + +static bool LinkMfplat() { + static bool sInitDone = false; + static bool sInitOk = false; + if (!sInitDone) { + sInitDone = true; + HMODULE handle; + +#define MFPLAT_FUNC(_func, _dllname) \ + handle = GetModuleHandleA(_dllname); \ + if (!(_func = (decltype(_func))(GetProcAddress(handle, #_func)))) { \ + return false; \ + } + +#include "WMFSymbols.h" +#undef MFPLAT_FUNC + sInitOk = true; + } + return sInitOk; +} + +bool EnsureLibs() { + static bool sInitDone = false; + static bool sInitOk = false; + if (!sInitDone) { + sInitOk = LinkMfplat() && !!GetModuleHandleA(WMFDecoderDllName()); + sInitDone = true; + } + return sInitOk; +} + +int32_t MFOffsetToInt32(const MFOffset& aOffset) { + return int32_t(aOffset.value + (aOffset.fract / 65536.0f)); +} + +// Gets the sub-region of the video frame that should be displayed. +// See: +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb530115(v=vs.85).aspx +HRESULT +GetPictureRegion(IMFMediaType* aMediaType, IntRect& aOutPictureRegion) { + // Determine if "pan and scan" is enabled for this media. If it is, we + // only display a region of the video frame, not the entire frame. + BOOL panScan = + MFGetAttributeUINT32(aMediaType, MF_MT_PAN_SCAN_ENABLED, FALSE); + + // If pan and scan mode is enabled. Try to get the display region. + HRESULT hr = E_FAIL; + MFVideoArea videoArea; + memset(&videoArea, 0, sizeof(MFVideoArea)); + if (panScan) { + hr = aMediaType->GetBlob(MF_MT_PAN_SCAN_APERTURE, (UINT8*)&videoArea, + sizeof(MFVideoArea), NULL); + } + + // If we're not in pan-and-scan mode, or the pan-and-scan region is not set, + // check for a minimimum display aperture. + if (!panScan || hr == MF_E_ATTRIBUTENOTFOUND) { + hr = aMediaType->GetBlob(MF_MT_MINIMUM_DISPLAY_APERTURE, (UINT8*)&videoArea, + sizeof(MFVideoArea), NULL); + } + + if (hr == MF_E_ATTRIBUTENOTFOUND) { + // Minimum display aperture is not set, for "backward compatibility with + // some components", check for a geometric aperture. + hr = aMediaType->GetBlob(MF_MT_GEOMETRIC_APERTURE, (UINT8*)&videoArea, + sizeof(MFVideoArea), NULL); + } + + if (SUCCEEDED(hr)) { + // The media specified a picture region, return it. + IntRect picture = IntRect(MFOffsetToInt32(videoArea.OffsetX), + MFOffsetToInt32(videoArea.OffsetY), + videoArea.Area.cx, videoArea.Area.cy); + ENSURE(picture.width <= mozilla::MAX_VIDEO_WIDTH, E_FAIL); + ENSURE(picture.height <= mozilla::MAX_VIDEO_HEIGHT, E_FAIL); + aOutPictureRegion = picture; + return S_OK; + } + + // No picture region defined, fall back to using the entire video area. + UINT32 width = 0, height = 0; + hr = MFGetAttributeSize(aMediaType, MF_MT_FRAME_SIZE, &width, &height); + ENSURE(SUCCEEDED(hr), hr); + ENSURE(width <= mozilla::MAX_VIDEO_WIDTH, E_FAIL); + ENSURE(height <= mozilla::MAX_VIDEO_HEIGHT, E_FAIL); + aOutPictureRegion = IntRect(0, 0, width, height); + return S_OK; +} + +HRESULT +GetDefaultStride(IMFMediaType* aType, uint32_t* aOutStride) { + // Try to get the default stride from the media type. + UINT32 stride = 0; + HRESULT hr = aType->GetUINT32(MF_MT_DEFAULT_STRIDE, &stride); + if (SUCCEEDED(hr)) { + ENSURE(stride <= mozilla::MAX_VIDEO_WIDTH, E_FAIL); + *aOutStride = stride; + return S_OK; + } + + // Stride attribute not set, calculate it. + GUID subtype = GUID_NULL; + uint32_t width = 0; + uint32_t height = 0; + + hr = aType->GetGUID(MF_MT_SUBTYPE, &subtype); + ENSURE(SUCCEEDED(hr), hr); + + hr = MFGetAttributeSize(aType, MF_MT_FRAME_SIZE, &width, &height); + ENSURE(SUCCEEDED(hr), hr); + ENSURE(width <= mozilla::MAX_VIDEO_WIDTH, E_FAIL); + ENSURE(height <= mozilla::MAX_VIDEO_HEIGHT, E_FAIL); + + LONG lstride = 0; + hr = MFGetStrideForBitmapInfoHeader(subtype.Data1, width, &lstride); + ENSURE(SUCCEEDED(hr), hr); + ENSURE(lstride <= mozilla::MAX_VIDEO_WIDTH, E_FAIL); + ENSURE(lstride >= 0, E_FAIL); + *aOutStride = lstride; + + return hr; +} + +void dump(const uint8_t* data, uint32_t len, const char* filename) { + FILE* f = 0; + fopen_s(&f, filename, "wb"); + fwrite(data, len, 1, f); + fclose(f); +} + +HRESULT +CreateMFT(const CLSID& clsid, const char* aDllName, + CComPtr<IMFTransform>& aOutMFT) { + HMODULE module = ::GetModuleHandleA(aDllName); + if (!module) { + LOG("Failed to get %S\n", aDllName); + return E_FAIL; + } + + typedef HRESULT(WINAPI * DllGetClassObjectFnPtr)( + const CLSID& clsid, const IID& iid, void** object); + + DllGetClassObjectFnPtr GetClassObjPtr = + reinterpret_cast<DllGetClassObjectFnPtr>( + GetProcAddress(module, "DllGetClassObject")); + if (!GetClassObjPtr) { + LOG("Failed to get DllGetClassObject\n"); + return E_FAIL; + } + + CComPtr<IClassFactory> classFactory; + HRESULT hr = GetClassObjPtr( + clsid, __uuidof(IClassFactory), + reinterpret_cast<void**>(static_cast<IClassFactory**>(&classFactory))); + if (FAILED(hr)) { + LOG("Failed to get H264 IClassFactory\n"); + return E_FAIL; + } + + hr = classFactory->CreateInstance( + NULL, __uuidof(IMFTransform), + reinterpret_cast<void**>(static_cast<IMFTransform**>(&aOutMFT))); + if (FAILED(hr)) { + LOG("Failed to get create MFT\n"); + return E_FAIL; + } + + return S_OK; +} + +int32_t GetNumThreads(int32_t aCoreCount) { + return aCoreCount > 4 ? -1 : (std::max)(aCoreCount - 1, 1); +} + +} // namespace wmf diff --git a/media/gmp-clearkey/0.1/WMFUtils.h b/media/gmp-clearkey/0.1/WMFUtils.h new file mode 100644 index 0000000000..0c2819e25c --- /dev/null +++ b/media/gmp-clearkey/0.1/WMFUtils.h @@ -0,0 +1,248 @@ +/* + * Copyright 2013, 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 __WMFUtils_h__ +#define __WMFUtils_h__ + +#include <assert.h> +#include <mfapi.h> +#include <mferror.h> +#include <mfobjects.h> +#include <mftransform.h> +#include <wmcodecdsp.h> + +#include <cstdint> +#include <string> + +#include "VideoLimits.h" +#include "content_decryption_module.h" +#include "gmp-platform.h" +#include "mozilla/Attributes.h" + +void LOG(const char* format, ...); + +#ifdef LOG_SAMPLE_DECODE +# define SAMPLE_LOG LOG +#else +# define SAMPLE_LOG(...) +#endif + +#ifndef CLSID_CMSAACDecMFT +# define WMF_MUST_DEFINE_AAC_MFT_CLSID +extern "C" const CLSID CLSID_CMSAACDecMFT; +#endif + +extern "C" const CLSID CLSID_CMSH264DecMFT; + +namespace wmf { + +// Reimplementation of CComPtr to reduce dependence on system +// shared libraries. +template <class T> +class CComPtr { + public: + CComPtr(CComPtr&& aOther) : mPtr(aOther.Detach()) {} + CComPtr& operator=(CComPtr&& other) { mPtr = other.Detach(); } + + CComPtr(const CComPtr& aOther) : mPtr(nullptr) { Set(aOther.Get()); } + CComPtr() : mPtr(nullptr) {} + MOZ_IMPLICIT CComPtr(T* const& aPtr) : mPtr(nullptr) { Set(aPtr); } + MOZ_IMPLICIT CComPtr(const std::nullptr_t& aNullPtr) : mPtr(aNullPtr) {} + T** operator&() { return &mPtr; } + T* operator->() { return mPtr; } + operator T*() { return mPtr; } + T* operator=(T* const& aPtr) { return Set(aPtr); } + T* operator=(const std::nullptr_t& aPtr) { return mPtr = aPtr; } + + T* Get() const { return mPtr; } + + T* Detach() { + T* tmp = mPtr; + mPtr = nullptr; + return tmp; + } + + ~CComPtr() { + if (mPtr) { + mPtr->Release(); + } + mPtr = nullptr; + } + + private: + T* Set(T* aPtr) { + if (mPtr == aPtr) { + return aPtr; + } + if (mPtr) { + mPtr->Release(); + } + mPtr = aPtr; + if (mPtr) { + mPtr->AddRef(); + } + return mPtr; + } + + T* mPtr; +}; + +class IntRect { + public: + IntRect(int32_t _x, int32_t _y, int32_t _w, int32_t _h) + : x(_x), y(_y), width(_w), height(_h) {} + IntRect() : x(0), y(0), width(0), height(0) {} + int32_t x; + int32_t y; + int32_t width; + int32_t height; +}; + +typedef int64_t Microseconds; + +#ifdef ENSURE +# undef ENSURE +#endif + +#define ENSURE(condition, ret) \ + { \ + if (!(condition)) { \ + LOG("##condition## FAILED %S:%d\n", __FILE__, __LINE__); \ + return ret; \ + } \ + } + +inline bool STATUS_SUCCEEDED(cdm::Status status) { + return status == cdm::Status::kSuccess; +} +inline bool STATUS_FAILED(cdm::Status status) { + return !STATUS_SUCCEEDED(status); +} + +#define MFPLAT_FUNC(_func, _dllname) extern decltype(::_func)* _func; +#include "WMFSymbols.h" +#undef MFPLAT_FUNC + +bool EnsureLibs(); + +HRESULT +GetPictureRegion(IMFMediaType* aMediaType, IntRect& aOutPictureRegion); + +HRESULT +GetDefaultStride(IMFMediaType* aType, uint32_t* aOutStride); + +// Converts from microseconds to hundreds of nanoseconds. +// We use microseconds for our timestamps, whereas WMF uses +// hundreds of nanoseconds. +inline int64_t UsecsToHNs(int64_t aUsecs) { return aUsecs * 10; } + +// Converts from hundreds of nanoseconds to microseconds. +// We use microseconds for our timestamps, whereas WMF uses +// hundreds of nanoseconds. +inline int64_t HNsToUsecs(int64_t hNanoSecs) { return hNanoSecs / 10; } + +inline std::string narrow(std::wstring& wide) { + std::string ns(wide.begin(), wide.end()); + return ns; +} + +inline std::wstring widen(std::string& narrow) { + std::wstring ws(narrow.begin(), narrow.end()); + return ws; +} + +#define ARRAY_LENGTH(array_) (sizeof(array_) / sizeof(array_[0])) + +template <class Type> +class AutoPtr { + public: + AutoPtr() : mPtr(nullptr) {} + + AutoPtr(AutoPtr<Type>& aPtr) : mPtr(aPtr.Forget()) {} + + explicit AutoPtr(Type* aPtr) : mPtr(aPtr) {} + + ~AutoPtr() { + if (mPtr) { + delete mPtr; + } + } + + Type* Forget() { + Type* rv = mPtr; + mPtr = nullptr; + return rv; + } + + AutoPtr<Type>& operator=(Type* aOther) { + Assign(aOther); + return *this; + } + + AutoPtr<Type>& operator=(AutoPtr<Type>& aOther) { + Assign(aOther.Forget()); + return *this; + } + + Type* Get() const { return mPtr; } + + Type* operator->() const { + assert(mPtr); + return Get(); + } + + operator Type*() const { return Get(); } + + Type** Receive() { return &mPtr; } + + private: + void Assign(Type* aPtr) { + if (mPtr) { + delete mPtr; + } + mPtr = aPtr; + } + + Type* mPtr; +}; + +// Video frame microseconds are (currently) in 90kHz units, as used by RTP. +// Use this to convert to microseconds... +inline Microseconds RTPTimeToMicroseconds(int64_t rtptime) { + return (rtptime * 1000000) / 90000; +} + +inline uint32_t MicrosecondsToRTPTime(Microseconds us) { + return uint32_t(0xffffffff & (us * 90000) / 1000000); +} + +void dump(const uint8_t* data, uint32_t len, const char* filename); + +HRESULT +CreateMFT(const CLSID& clsid, const char* aDllName, + CComPtr<IMFTransform>& aOutMFT); + +// Returns the name of the DLL that is needed to decode H.264 on +// the given windows version we're running on. +inline const char* WMFDecoderDllName() { return "msmpeg2vdec.dll"; } + +// Returns the maximum number of threads we want WMF to use for decoding +// given the number of logical processors available. +int32_t GetNumThreads(int32_t aCoreCount); + +} // namespace wmf + +#endif // __WMFUtils_h__ diff --git a/media/gmp-clearkey/0.1/gmp-clearkey.cpp b/media/gmp-clearkey/0.1/gmp-clearkey.cpp new file mode 100644 index 0000000000..70c27602f7 --- /dev/null +++ b/media/gmp-clearkey/0.1/gmp-clearkey.cpp @@ -0,0 +1,173 @@ +/* + * 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 <assert.h> +// This include is required in order for content_decryption_module to work +// on Unix systems. +#include <stddef.h> +#include <stdio.h> +#include <string.h> + +#include <string> +#include <vector> + +#include "content_decryption_module.h" +#include "content_decryption_module_ext.h" +#include "nss.h" + +#include "ClearKeyCDM.h" +#include "ClearKeySessionManager.h" +#include "mozilla/dom/KeySystemNames.h" + +#ifndef XP_WIN +# include <sys/types.h> +# include <sys/stat.h> +# include <unistd.h> +#endif + +#ifdef ENABLE_WMF +# include "WMFUtils.h" +#endif // ENABLE_WMF + +extern "C" { + +CDM_API +void INITIALIZE_CDM_MODULE() {} + +static bool sCanReadHostVerificationFiles = false; + +CDM_API +void* CreateCdmInstance(int cdm_interface_version, const char* key_system, + uint32_t key_system_size, + GetCdmHostFunc get_cdm_host_func, void* user_data) { + CK_LOGE("ClearKey CreateCDMInstance"); + + if (cdm_interface_version != cdm::ContentDecryptionModule_10::kVersion) { + CK_LOGE( + "ClearKey CreateCDMInstance failed due to requesting unsupported " + "version %d.", + cdm_interface_version); + return nullptr; + } +#ifdef ENABLE_WMF + if (!wmf::EnsureLibs()) { + CK_LOGE("Required libraries were not found"); + return nullptr; + } +#endif + + if (NSS_NoDB_Init(nullptr) == SECFailure) { + CK_LOGE("Unable to initialize NSS"); + return nullptr; + } + +#ifdef MOZILLA_OFFICIAL + // Test that we're able to read the host files. + if (!sCanReadHostVerificationFiles) { + return nullptr; + } +#endif + + cdm::Host_10* host = static_cast<cdm::Host_10*>( + get_cdm_host_func(cdm_interface_version, user_data)); + ClearKeyCDM* clearKey = new ClearKeyCDM(host); + + CK_LOGE("Created ClearKeyCDM instance!"); + + if (strncmp(key_system, mozilla::kClearKeyWithProtectionQueryKeySystemName, + key_system_size) == 0) { + CK_LOGE("Enabling protection query on ClearKeyCDM instance!"); + clearKey->EnableProtectionQuery(); + } + + return clearKey; +} + +const size_t TEST_READ_SIZE = 16 * 1024; + +bool CanReadSome(cdm::PlatformFile aFile) { + std::vector<uint8_t> data; + data.resize(TEST_READ_SIZE); +#ifdef XP_WIN + DWORD bytesRead = 0; + return ReadFile(aFile, &data.front(), TEST_READ_SIZE, &bytesRead, nullptr) && + bytesRead > 0; +#else + return read(aFile, &data.front(), TEST_READ_SIZE) > 0; +#endif +} + +void ClosePlatformFile(cdm::PlatformFile aFile) { +#ifdef XP_WIN + CloseHandle(aFile); +#else + close(aFile); +#endif +} + +static uint32_t NumExpectedHostFiles(const cdm::HostFile* aHostFiles, + uint32_t aNumFiles) { +#if !defined(XP_WIN) + // We expect 4 binaries: clearkey, libxul, plugin-container, and Firefox. + return 4; +#else + // Windows running x64 or x86 natively should also have 4 as above. + // For Windows on ARM64, we run an x86 plugin-contianer process under + // emulation, and so we expect one additional binary; the x86 + // xul.dll used by plugin-container.exe. + bool i686underAArch64 = false; + // Assume that we're running under x86 emulation on an aarch64 host if + // one of the paths ends with the x86 plugin-container path we'd expect. + const std::wstring plugincontainer = L"i686\\plugin-container.exe"; + for (uint32_t i = 0; i < aNumFiles; i++) { + const cdm::HostFile& hostFile = aHostFiles[i]; + if (hostFile.file != cdm::kInvalidPlatformFile) { + std::wstring path = hostFile.file_path; + auto offset = path.find(plugincontainer); + if (offset != std::string::npos && + offset == path.size() - plugincontainer.size()) { + i686underAArch64 = true; + break; + } + } + } + return i686underAArch64 ? 5 : 4; +#endif +} + +CDM_API +bool VerifyCdmHost_0(const cdm::HostFile* aHostFiles, uint32_t aNumFiles) { + // Check that we've received the expected number of host files. + bool rv = (aNumFiles == NumExpectedHostFiles(aHostFiles, aNumFiles)); + // Verify that each binary is readable inside the sandbox, + // and close the handle. + for (uint32_t i = 0; i < aNumFiles; i++) { + const cdm::HostFile& hostFile = aHostFiles[i]; + if (hostFile.file != cdm::kInvalidPlatformFile) { + if (!CanReadSome(hostFile.file)) { + rv = false; + } + ClosePlatformFile(hostFile.file); + } + if (hostFile.sig_file != cdm::kInvalidPlatformFile) { + ClosePlatformFile(hostFile.sig_file); + } + } + sCanReadHostVerificationFiles = rv; + return rv; +} + +} // extern "C". diff --git a/media/gmp-clearkey/0.1/manifest.json.in b/media/gmp-clearkey/0.1/manifest.json.in new file mode 100644 index 0000000000..dd36af8556 --- /dev/null +++ b/media/gmp-clearkey/0.1/manifest.json.in @@ -0,0 +1,13 @@ +{
+ "name": "clearkey",
+ "description": "ClearKey Gecko Media Plugin",
+ "version": "1",
+ "x-cdm-module-versions": "4",
+ "x-cdm-interface-versions": "10",
+ "x-cdm-host-versions": "10",
+#ifdef ENABLE_WMF
+ "x-cdm-codecs": "avc1"
+#else
+ "x-cdm-codecs": ""
+#endif
+}
\ No newline at end of file diff --git a/media/gmp-clearkey/0.1/moz.build b/media/gmp-clearkey/0.1/moz.build new file mode 100644 index 0000000000..ad393c1c25 --- /dev/null +++ b/media/gmp-clearkey/0.1/moz.build @@ -0,0 +1,62 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files("**"): + BUG_COMPONENT = ("Core", "Audio/Video: GMP") + +GeckoSharedLibrary("clearkey", linkage=None) + +FINAL_TARGET = "dist/bin/gmp-clearkey/0.1" + +FINAL_TARGET_PP_FILES += ["manifest.json.in"] + +USE_LIBS += ["gecko-clearkey"] + +UNIFIED_SOURCES += [ + "ClearKeyCDM.cpp", + "gmp-clearkey.cpp", +] + +if CONFIG["OS_ARCH"] == "WINNT": + UNIFIED_SOURCES += [ + "VideoDecoder.cpp", + "WMFH264Decoder.cpp", + ] + + SOURCES += [ + "WMFUtils.cpp", + ] + + OS_LIBS += [ + "mfuuid", + "uuid", + ] + + DEFINES["ENABLE_WMF"] = True + +DEFINES["CDM_IMPLEMENTATION"] = True + +DisableStlWrapping() +DEFINES["MOZ_NO_MOZALLOC"] = True + +# Suppress warnings in third-party code. +CFLAGS += [ + "-Wno-pointer-to-int-cast", + "-Wno-sign-compare", +] + +if CONFIG["CC_TYPE"] in ("clang", "gcc"): + CFLAGS += [ + "-include", + "stdio.h", # for sprintf() prototype + "-include", + "unistd.h", # for getpid() prototype + ] +elif CONFIG["CC_TYPE"] == "clang-cl": + CFLAGS += [ + "-FI", + "stdio.h", # for sprintf() prototype + ] |