blob: bc7da98fea2d9d855c8c145952d208075fd6966f [file] [log] [blame]
// Copyright 2019 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/vaapi/vaapi_video_decoder_delegate.h"
#include "base/bind.h"
#include "base/containers/contains.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "base/time/default_tick_clock.h"
#include "build/chromeos_buildflags.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/cdm_context.h"
#include "media/gpu/decode_surface_handler.h"
#include "media/gpu/vaapi/va_surface.h"
#include "media/gpu/vaapi/vaapi_wrapper.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chromeos/components/cdm_factory_daemon/chromeos_cdm_factory.h"
namespace {
// During playback of protected content, we need to request the keys at an
// interval no greater than this. This allows updating of key usage data.
constexpr base::TimeDelta kKeyRetrievalMaxPeriod = base::Minutes(1);
// This increments the lower 64 bit counter of an 128 bit IV.
void ctr128_inc64(uint8_t* counter) {
uint32_t n = 16;
do {
if (++counter[--n] != 0)
return;
} while (n > 8);
}
} // namespace
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
namespace media {
VaapiVideoDecoderDelegate::VaapiVideoDecoderDelegate(
DecodeSurfaceHandler<VASurface>* const vaapi_dec,
scoped_refptr<VaapiWrapper> vaapi_wrapper,
ProtectedSessionUpdateCB on_protected_session_update_cb,
CdmContext* cdm_context,
EncryptionScheme encryption_scheme)
: vaapi_dec_(vaapi_dec),
vaapi_wrapper_(std::move(vaapi_wrapper)),
on_protected_session_update_cb_(
std::move(on_protected_session_update_cb)),
encryption_scheme_(encryption_scheme),
protected_session_state_(ProtectedSessionState::kNotCreated),
performing_recovery_(false) {
DCHECK(vaapi_wrapper_);
DCHECK(vaapi_dec_);
DETACH_FROM_SEQUENCE(sequence_checker_);
#if BUILDFLAG(IS_CHROMEOS_ASH)
if (cdm_context)
chromeos_cdm_context_ = cdm_context->GetChromeOsCdmContext();
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
transcryption_ = cdm_context && VaapiWrapper::GetImplementationType() ==
VAImplementation::kMesaGallium;
}
VaapiVideoDecoderDelegate::~VaapiVideoDecoderDelegate() {
// TODO(mcasas): consider enabling the checker, https://crbug.com/789160
// DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Also destroy the protected session on destruction of the accelerator
// delegate. That way if a new delegate is created, when it tries to create a
// new protected session it won't overwrite the existing one.
vaapi_wrapper_->DestroyProtectedSession();
}
void VaapiVideoDecoderDelegate::set_vaapi_wrapper(
scoped_refptr<VaapiWrapper> vaapi_wrapper) {
DETACH_FROM_SEQUENCE(sequence_checker_);
vaapi_wrapper_ = std::move(vaapi_wrapper);
protected_session_state_ = ProtectedSessionState::kNotCreated;
hw_identifier_.clear();
hw_key_data_map_.clear();
}
void VaapiVideoDecoderDelegate::OnVAContextDestructionSoon() {}
bool VaapiVideoDecoderDelegate::HasInitiatedProtectedRecovery() {
if (protected_session_state_ != ProtectedSessionState::kNeedsRecovery)
return false;
performing_recovery_ = true;
protected_session_state_ = ProtectedSessionState::kNotCreated;
return true;
}
bool VaapiVideoDecoderDelegate::SetDecryptConfig(
std::unique_ptr<DecryptConfig> decrypt_config) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// It is possible to switch between clear and encrypted (and vice versa), but
// we should not be changing encryption schemes across encrypted portions.
if (!decrypt_config)
return true;
// TODO(jkardatzke): Handle changing encryption modes midstream, the latest
// OEMCrypto spec allows this, although we won't hit it in reality for now.
// Check to make sure they are compatible.
if (!transcryption_ &&
decrypt_config->encryption_scheme() != encryption_scheme_) {
LOG(ERROR) << "Cannot change encryption modes midstream";
return false;
}
decrypt_config_ = std::move(decrypt_config);
return true;
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
VaapiVideoDecoderDelegate::ProtectedSessionState
VaapiVideoDecoderDelegate::SetupDecryptDecode(
bool full_sample,
size_t size,
VAEncryptionParameters* crypto_params,
std::vector<VAEncryptionSegmentInfo>* segments,
const std::vector<SubsampleEntry>& subsamples) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(crypto_params);
DCHECK(segments);
if (protected_session_state_ == ProtectedSessionState::kInProcess ||
protected_session_state_ == ProtectedSessionState::kFailed) {
return protected_session_state_;
}
if (protected_session_state_ == ProtectedSessionState::kNotCreated) {
if (!chromeos_cdm_context_) {
LOG(ERROR) << "Cannot create protected session w/out ChromeOsCdmContext";
protected_session_state_ = ProtectedSessionState::kFailed;
return protected_session_state_;
}
// We need to start the creation of this, first part requires getting the
// hw config data from the daemon.
chromeos::ChromeOsCdmFactory::GetHwConfigData(BindToCurrentLoop(
base::BindOnce(&VaapiVideoDecoderDelegate::OnGetHwConfigData,
weak_factory_.GetWeakPtr())));
protected_session_state_ = ProtectedSessionState::kInProcess;
return protected_session_state_;
}
DCHECK_EQ(protected_session_state_, ProtectedSessionState::kCreated);
const bool ctr = (encryption_scheme_ == EncryptionScheme::kCenc);
if (ctr) {
crypto_params->encryption_type = full_sample
? VA_ENCRYPTION_TYPE_FULLSAMPLE_CTR
: VA_ENCRYPTION_TYPE_SUBSAMPLE_CTR;
} else {
crypto_params->encryption_type = full_sample
? VA_ENCRYPTION_TYPE_FULLSAMPLE_CBC
: VA_ENCRYPTION_TYPE_SUBSAMPLE_CBC;
}
// For multi-slice we may already have segment information in here, so
// calculate the current offset.
size_t offset = 0;
for (const auto& segment : *segments)
offset += segment.segment_length;
if (subsamples.empty() ||
(subsamples.size() == 1 && subsamples[0].cypher_bytes == 0)) {
// We still need to specify the crypto params to the driver for some reason
// and indicate the entire content is clear.
VAEncryptionSegmentInfo segment_info = {};
segment_info.segment_start_offset = offset;
segment_info.segment_length = segment_info.init_byte_length = size;
if (decrypt_config_) {
// We need to specify the IV even if the segment is clear.
memcpy(segment_info.aes_cbc_iv_or_ctr, decrypt_config_->iv().data(),
DecryptConfig::kDecryptionKeySize);
}
segments->emplace_back(std::move(segment_info));
crypto_params->num_segments++;
crypto_params->segment_info = &segments->front();
return protected_session_state_;
}
DCHECK(decrypt_config_);
// We also need to make sure we have the key data for the active
// DecryptConfig now that the protected session exists.
if (!base::Contains(hw_key_data_map_, decrypt_config_->key_id())) {
DVLOG(1) << "Looking up the key data for: " << decrypt_config_->key_id();
chromeos_cdm_context_->GetHwKeyData(
decrypt_config_.get(), hw_identifier_,
BindToCurrentLoop(base::BindOnce(
&VaapiVideoDecoderDelegate::OnGetHwKeyData,
weak_factory_.GetWeakPtr(), decrypt_config_->key_id())));
last_key_retrieval_time_ =
base::DefaultTickClock::GetInstance()->NowTicks();
// Don't change our state here because we are created, but we just return
// kInProcess for now to trigger a wait/retry state.
return ProtectedSessionState::kInProcess;
}
// We may also need to request the key in order to update key usage times in
// OEMCrypto. We do care about the return value, because it will indicate key
// validity for us.
if (base::DefaultTickClock::GetInstance()->NowTicks() -
last_key_retrieval_time_ >
kKeyRetrievalMaxPeriod) {
chromeos_cdm_context_->GetHwKeyData(
decrypt_config_.get(), hw_identifier_,
BindToCurrentLoop(base::BindOnce(
&VaapiVideoDecoderDelegate::OnGetHwKeyData,
weak_factory_.GetWeakPtr(), decrypt_config_->key_id())));
last_key_retrieval_time_ =
base::DefaultTickClock::GetInstance()->NowTicks();
}
crypto_params->num_segments += subsamples.size();
if (decrypt_config_->HasPattern()) {
crypto_params->blocks_stripe_encrypted =
decrypt_config_->encryption_pattern()->crypt_byte_block();
crypto_params->blocks_stripe_clear =
decrypt_config_->encryption_pattern()->skip_byte_block();
}
size_t total_cypher_size = 0;
std::vector<uint8_t> iv(DecryptConfig::kDecryptionKeySize);
iv.assign(decrypt_config_->iv().begin(), decrypt_config_->iv().end());
for (const auto& entry : subsamples) {
VAEncryptionSegmentInfo segment_info = {};
segment_info.segment_start_offset = offset;
segment_info.segment_length = entry.clear_bytes + entry.cypher_bytes;
memcpy(segment_info.aes_cbc_iv_or_ctr, iv.data(),
DecryptConfig::kDecryptionKeySize);
if (ctr) {
size_t partial_block_size =
(DecryptConfig::kDecryptionKeySize -
(total_cypher_size % DecryptConfig::kDecryptionKeySize)) %
DecryptConfig::kDecryptionKeySize;
segment_info.partial_aes_block_size = partial_block_size;
if (entry.cypher_bytes > partial_block_size) {
// If we are finishing a block, increment the counter.
if (partial_block_size)
ctr128_inc64(iv.data());
// Increment the counter for every complete block we are adding.
for (size_t block = 0;
block < (entry.cypher_bytes - partial_block_size) /
DecryptConfig::kDecryptionKeySize;
++block)
ctr128_inc64(iv.data());
}
total_cypher_size += entry.cypher_bytes;
}
segment_info.init_byte_length = entry.clear_bytes;
offset += entry.clear_bytes + entry.cypher_bytes;
segments->emplace_back(std::move(segment_info));
}
memcpy(crypto_params->wrapped_decrypt_blob,
hw_key_data_map_[decrypt_config_->key_id()].data(),
DecryptConfig::kDecryptionKeySize);
crypto_params->key_blob_size = DecryptConfig::kDecryptionKeySize;
crypto_params->segment_info = &segments->front();
return protected_session_state_;
}
#endif // if BUILDFLAG(IS_CHROMEOS_ASH)
bool VaapiVideoDecoderDelegate::NeedsProtectedSessionRecovery() {
if (!IsEncryptedSession() || !vaapi_wrapper_->IsProtectedSessionDead() ||
performing_recovery_) {
return false;
}
LOG(WARNING) << "Protected session loss detected, initiating recovery";
protected_session_state_ = ProtectedSessionState::kNeedsRecovery;
hw_key_data_map_.clear();
hw_identifier_.clear();
vaapi_wrapper_->DestroyProtectedSession();
return true;
}
void VaapiVideoDecoderDelegate::ProtectedDecodedSucceeded() {
performing_recovery_ = false;
}
std::string VaapiVideoDecoderDelegate::GetDecryptKeyId() const {
DCHECK(decrypt_config_);
return decrypt_config_->key_id();
}
void VaapiVideoDecoderDelegate::OnGetHwConfigData(
bool success,
const std::vector<uint8_t>& config_data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!success) {
protected_session_state_ = ProtectedSessionState::kFailed;
on_protected_session_update_cb_.Run(false);
return;
}
hw_identifier_.clear();
if (!vaapi_wrapper_->CreateProtectedSession(encryption_scheme_, config_data,
&hw_identifier_)) {
LOG(ERROR) << "Failed to setup protected session";
protected_session_state_ = ProtectedSessionState::kFailed;
on_protected_session_update_cb_.Run(false);
return;
}
protected_session_state_ = ProtectedSessionState::kCreated;
on_protected_session_update_cb_.Run(true);
}
void VaapiVideoDecoderDelegate::OnGetHwKeyData(
const std::string& key_id,
Decryptor::Status status,
const std::vector<uint8_t>& key_data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// There's a special case here where we are updating usage times/checking on
// key validity, and in that case the key is already in the map.
if (base::Contains(hw_key_data_map_, key_id)) {
if (status == Decryptor::Status::kSuccess)
return;
// This key is no longer valid, decryption will fail, so stop playback
// now. This key should have been renewed by the CDM instead.
LOG(ERROR) << "CDM has lost key information, stopping playback";
protected_session_state_ = ProtectedSessionState::kFailed;
on_protected_session_update_cb_.Run(false);
return;
}
if (status != Decryptor::Status::kSuccess) {
// If it's a failure, then indicate so, otherwise if it's waiting for a key,
// then we don't do anything since we will get called again when there's a
// message about key availability changing.
if (status == Decryptor::Status::kNoKey) {
DVLOG(1) << "HW did not have key information, keep waiting for it";
return;
}
LOG(ERROR) << "Failure getting the key data, fail overall";
protected_session_state_ = ProtectedSessionState::kFailed;
on_protected_session_update_cb_.Run(false);
return;
}
if (key_data.size() != DecryptConfig::kDecryptionKeySize) {
LOG(ERROR) << "Invalid key size returned of: " << key_data.size();
protected_session_state_ = ProtectedSessionState::kFailed;
on_protected_session_update_cb_.Run(false);
return;
}
hw_key_data_map_[key_id] = key_data;
on_protected_session_update_cb_.Run(true);
}
} // namespace media