blob: 204927007066d86a803951c7607c4bb6b2b33335 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/gpu/windows/d3d11_decryptor.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "media/base/decoder_buffer.h"
namespace media {
namespace {
// "A buffer is defined as a single subresource."
// https://msdn.microsoft.com/en-us/library/windows/desktop/ff476901(v=vs.85).aspx
const UINT kSubresourceIndex = 0;
const UINT kWaitIfGPUBusy = 0;
// This value is somewhat arbitrary but is a multiple of 16 and 4K and is
// equal to D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION. Since the buffers are cast
// to ID3D11Texture2D, setting it as its size should make sense.
const UINT kBufferSize = 16384;
// Creates ID3D11Buffer using the values. Return true on success.
bool CreateBuffer(ID3D11Device* device,
D3D11_USAGE usage,
UINT bind_flags,
UINT cpu_access,
ID3D11Buffer** out) {
D3D11_BUFFER_DESC buf_desc = {};
buf_desc.ByteWidth = kBufferSize;
buf_desc.BindFlags = bind_flags;
buf_desc.Usage = usage;
buf_desc.CPUAccessFlags = cpu_access;
HRESULT hresult = device->CreateBuffer(&buf_desc, nullptr, out);
return SUCCEEDED(hresult);
}
// Copies |input| into |output|, the output buffer should be a staging buffer
// that is CPU writable.
bool CopyDataToBuffer(base::span<const uint8_t> input,
ID3D11DeviceContext* device_context,
ID3D11Buffer* output) {
D3D11_BUFFER_DESC output_buffer_desc = {};
output->GetDesc(&output_buffer_desc);
if (output_buffer_desc.ByteWidth < input.size()) {
DVLOG(1) << input.size() << " does not fit in "
<< output_buffer_desc.ByteWidth;
return false;
}
D3D11_MAPPED_SUBRESOURCE map_resource = {};
HRESULT hresult =
device_context->Map(output, kSubresourceIndex, D3D11_MAP_WRITE,
kWaitIfGPUBusy, &map_resource);
if (FAILED(hresult)) {
DVLOG(3) << "Failed to map buffer for writing.";
return false;
}
memcpy(map_resource.pData, input.data(), input.size_bytes());
device_context->Unmap(output, kSubresourceIndex);
return true;
}
// Copies |input| into |output|. The input buffer is should be a staging buffer
// that is CPU readable.
bool CopyDataOutFromBuffer(ID3D11Buffer* input,
size_t input_size,
ID3D11DeviceContext* device_context,
std::vector<uint8_t>* output) {
D3D11_MAPPED_SUBRESOURCE map_resource = {};
HRESULT hresult = device_context->Map(
input, kSubresourceIndex, D3D11_MAP_READ, kWaitIfGPUBusy, &map_resource);
if (FAILED(hresult)) {
DVLOG(3) << "Failed to map buffer for reading.";
return false;
}
output->resize(input_size);
memcpy(output->data(), map_resource.pData, input_size);
device_context->Unmap(input, kSubresourceIndex);
return true;
}
D3D11_AES_CTR_IV StringIvToD3D11Iv(const std::string& iv) {
D3D11_AES_CTR_IV d3d11_iv = {};
DCHECK_LE(iv.size(), 16u);
memcpy(&d3d11_iv, iv.data(), iv.size());
return d3d11_iv;
}
// Returns true if the entire sample is encrypted.
bool IsWholeSampleEncrypted(const DecryptConfig& decrypt_config,
size_t sample_size) {
const auto& subsamples = decrypt_config.subsamples();
if (subsamples.size() != 1)
return false;
return subsamples.front().clear_bytes == 0 &&
subsamples.front().cypher_bytes == sample_size;
}
// Checks whether |device1| is the same component as |device2|.
// Note that comparing COM pointers require using their IUnknowns.
// https://docs.microsoft.com/en-us/windows/desktop/api/unknwn/nf-unknwn-iunknown-queryinterface(q_)
bool SameDevices(Microsoft::WRL::ComPtr<ID3D11Device> device1,
Microsoft::WRL::ComPtr<ID3D11Device> device2) {
// For the case where both are nullptrs, they aren't devices, so returning
// false here.
if (!device1 || !device2)
return false;
Microsoft::WRL::ComPtr<IUnknown> device1_iunknown;
Microsoft::WRL::ComPtr<IUnknown> device2_iunknown;
HRESULT hr = device1.CopyTo(device1_iunknown.ReleaseAndGetAddressOf());
if (FAILED(hr))
return false;
hr = device2.CopyTo(device2_iunknown.ReleaseAndGetAddressOf());
if (FAILED(hr))
return false;
return device1_iunknown == device2_iunknown;
}
// Returns a value that is bigger than or equal to |num| that is a
// multiple of 16.
// E.g. num = 15 returns 16, 17 returns 32.
UINT To16Multiple(size_t num) {
return ((num + 15) >> 4) << 4;
}
} // namespace
D3D11Decryptor::D3D11Decryptor(CdmProxyContext* cdm_proxy_context)
: cdm_proxy_context_(cdm_proxy_context), weak_factory_(this) {
DCHECK(cdm_proxy_context_);
}
D3D11Decryptor::~D3D11Decryptor() {}
void D3D11Decryptor::RegisterNewKeyCB(StreamType stream_type,
const NewKeyCB& new_key_cb) {
// TODO(xhwang): Use RegisterNewKeyCB() on CdmContext, and remove
// RegisterNewKeyCB from Decryptor interface.
NOTREACHED();
}
void D3D11Decryptor::Decrypt(StreamType stream_type,
scoped_refptr<DecoderBuffer> encrypted,
const DecryptCB& decrypt_cb) {
if (encrypted->end_of_stream()) {
decrypt_cb.Run(kSuccess, encrypted);
return;
}
const auto* decrypt_config = encrypted->decrypt_config();
if (!decrypt_config) {
// Not encrypted, nothing to do.
decrypt_cb.Run(kSuccess, encrypted);
return;
}
if (decrypt_config->HasPattern()) {
DVLOG(3) << "Cannot handle pattern decryption.";
decrypt_cb.Run(kError, nullptr);
return;
}
auto context = cdm_proxy_context_->GetD3D11DecryptContext(
CdmProxy::KeyType::kDecryptOnly, decrypt_config->key_id());
if (!context) {
decrypt_cb.Run(kNoKey, nullptr);
return;
}
// Because DecryptionBlt() implementation checks whether the device, buffers,
// and the crypto session are from the same device, the buffers have to be
// recreated.
if (!InitializeDecryptionBuffer(*context)) {
decrypt_cb.Run(kError, nullptr);
return;
}
std::vector<uint8_t> output;
if (IsWholeSampleEncrypted(*encrypted->decrypt_config(),
encrypted->data_size())) {
if (!CtrDecrypt(base::make_span(encrypted->data(), encrypted->data_size()),
encrypted->decrypt_config()->iv(), *context, &output)) {
decrypt_cb.Run(kError, nullptr);
return;
}
} else {
if (!SubsampleCtrDecrypt(encrypted, *context, &output)) {
decrypt_cb.Run(kError, nullptr);
return;
}
}
auto decoder_buffer = DecoderBuffer::CopyFrom(output.data(), output.size());
decoder_buffer->set_timestamp(encrypted->timestamp());
decoder_buffer->set_duration(encrypted->duration());
decoder_buffer->set_is_key_frame(encrypted->is_key_frame());
decoder_buffer->CopySideDataFrom(encrypted->side_data(),
encrypted->side_data_size());
decrypt_cb.Run(kSuccess, decoder_buffer);
}
void D3D11Decryptor::CancelDecrypt(StreamType stream_type) {
// Decrypt() calls the DecryptCB synchronously so there's nothing to cancel.
}
void D3D11Decryptor::InitializeAudioDecoder(const AudioDecoderConfig& config,
const DecoderInitCB& init_cb) {
// D3D11Decryptor does not support audio decoding.
init_cb.Run(false);
}
void D3D11Decryptor::InitializeVideoDecoder(const VideoDecoderConfig& config,
const DecoderInitCB& init_cb) {
// D3D11Decryptor does not support video decoding.
init_cb.Run(false);
}
void D3D11Decryptor::DecryptAndDecodeAudio(
scoped_refptr<DecoderBuffer> encrypted,
const AudioDecodeCB& audio_decode_cb) {
NOTREACHED() << "D3D11Decryptor does not support audio decoding";
}
void D3D11Decryptor::DecryptAndDecodeVideo(
scoped_refptr<DecoderBuffer> encrypted,
const VideoDecodeCB& video_decode_cb) {
NOTREACHED() << "D3D11Decryptor does not support video decoding";
}
void D3D11Decryptor::ResetDecoder(StreamType stream_type) {
NOTREACHED() << "D3D11Decryptor does not support audio/video decoding";
}
void D3D11Decryptor::DeinitializeDecoder(StreamType stream_type) {
// D3D11Decryptor does not support audio/video decoding, but since this can be
// called any time after InitializeAudioDecoder/InitializeVideoDecoder,
// nothing to be done here.
}
bool D3D11Decryptor::InitializeDecryptionBuffer(
const CdmProxyContext::D3D11DecryptContext& decrypt_context) {
ComPtr<ID3D11Device> crypto_session_device;
decrypt_context.crypto_session->GetDevice(
crypto_session_device.ReleaseAndGetAddressOf());
// If they are the same devices, then there is no reason to reinitialize the
// buffers.
if (SameDevices(crypto_session_device, device_))
return true;
device_ = crypto_session_device;
device_->GetImmediateContext(device_context_.ReleaseAndGetAddressOf());
HRESULT hresult =
device_context_.CopyTo(video_context_.ReleaseAndGetAddressOf());
if (FAILED(hresult)) {
DVLOG(2) << "Failed to get video context.";
return false;
}
// The buffer is staging so that the data can be accessed by the CPU and HW.
if (!CreateBuffer(device_.Get(), D3D11_USAGE_STAGING, 0, // no binding.
D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE,
encrypted_sample_buffer_.ReleaseAndGetAddressOf())) {
DVLOG(2) << "Failed to create buffer for encrypted sample.";
return false;
}
// Note that the cpu access flag is 0 because this buffer is used to write the
// decrypted buffer in HW.
if (!CreateBuffer(device_.Get(), D3D11_USAGE_DEFAULT,
D3D11_BIND_RENDER_TARGET,
0, // no cpu access.
decrypted_sample_buffer_.ReleaseAndGetAddressOf())) {
DVLOG(2) << "Failed to create buffer for decrypted sample.";
return false;
}
if (!CreateBuffer(device_.Get(), D3D11_USAGE_STAGING, 0, // no binding.
D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE,
cpu_accessible_buffer_.ReleaseAndGetAddressOf())) {
DVLOG(2) << "Failed to create cpu accessible buffer.";
return false;
}
return true;
}
bool D3D11Decryptor::CtrDecrypt(
base::span<const uint8_t> input,
const std::string& iv,
const CdmProxyContext::D3D11DecryptContext& context,
std::vector<uint8_t>* output) {
output->clear();
if (input.empty())
return true;
if (!CopyDataToBuffer(input, device_context_.Get(),
encrypted_sample_buffer_.Get())) {
return false;
}
D3D11_AES_CTR_IV aes_ctr_iv = StringIvToD3D11Iv(iv);
// The size of the encrypted bytes must be a multiple of 16. See more at
// https://crbug.com/849466.
D3D11_ENCRYPTED_BLOCK_INFO block_info = {};
block_info.NumEncryptedBytesAtBeginning = To16Multiple(input.size());
DCHECK_LE(block_info.NumEncryptedBytesAtBeginning, kBufferSize);
block_info.NumBytesInSkipPattern =
kBufferSize - block_info.NumEncryptedBytesAtBeginning;
// ID3D11Buffers should be used but since the interface takes ID3D11Texture2D,
// it is reinterpret cast. See more at https://crbug.com/849466.
video_context_->DecryptionBlt(
context.crypto_session,
reinterpret_cast<ID3D11Texture2D*>(encrypted_sample_buffer_.Get()),
reinterpret_cast<ID3D11Texture2D*>(decrypted_sample_buffer_.Get()),
&block_info, context.key_blob_size, context.key_blob, sizeof(aes_ctr_iv),
&aes_ctr_iv);
// Because DecryptionBlt() doesn't have a return value, this is a hack to
// check for decryption operation status. If it has been modified, then there
// was an error. See more at https://crbug.com/849466.
HRESULT result = static_cast<HRESULT>(block_info.NumBytesInEncryptPattern);
if (FAILED(result)) {
DVLOG(3) << "Decryption error :"
<< logging::SystemErrorCodeToString(result);
return false;
}
device_context_->CopyResource(cpu_accessible_buffer_.Get(),
decrypted_sample_buffer_.Get());
return CopyDataOutFromBuffer(cpu_accessible_buffer_.Get(), input.size(),
device_context_.Get(), output);
}
// TODO(crbug.com/845631): This is the same as DecryptCencBuffer(), so it should
// be deduped.
bool D3D11Decryptor::SubsampleCtrDecrypt(
scoped_refptr<DecoderBuffer> encrypted,
const CdmProxyContext::D3D11DecryptContext& context,
std::vector<uint8_t>* output) {
const auto& subsamples = encrypted->decrypt_config()->subsamples();
std::vector<uint8_t> encrypted_data;
const uint8_t* data = encrypted->data();
for (const auto& subsample : subsamples) {
data += subsample.clear_bytes;
encrypted_data.insert(encrypted_data.end(), data,
data + subsample.cypher_bytes);
data += subsample.cypher_bytes;
}
std::vector<uint8_t> decrypted_data;
if (!CtrDecrypt(encrypted_data, encrypted->decrypt_config()->iv(), context,
&decrypted_data)) {
return false;
}
data = encrypted->data();
const uint8_t* decrypted_data_ptr = decrypted_data.data();
for (const auto& subsample : subsamples) {
output->insert(output->end(), data, data + subsample.clear_bytes);
data += subsample.clear_bytes;
output->insert(output->end(), decrypted_data_ptr,
decrypted_data_ptr + subsample.cypher_bytes);
decrypted_data_ptr += subsample.cypher_bytes;
data += subsample.cypher_bytes;
}
return true;
}
} // namespace media