diff options
Diffstat (limited to 'chromium/media/gpu/vaapi/vaapi_video_decoder_delegate.cc')
-rw-r--r-- | chromium/media/gpu/vaapi/vaapi_video_decoder_delegate.cc | 356 |
1 files changed, 354 insertions, 2 deletions
diff --git a/chromium/media/gpu/vaapi/vaapi_video_decoder_delegate.cc b/chromium/media/gpu/vaapi/vaapi_video_decoder_delegate.cc index d69f3250e05..b2accbbc497 100644 --- a/chromium/media/gpu/vaapi/vaapi_video_decoder_delegate.cc +++ b/chromium/media/gpu/vaapi/vaapi_video_decoder_delegate.cc @@ -4,32 +4,384 @@ #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::TimeDelta::FromMinutes(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) - : vaapi_dec_(vaapi_dec), vaapi_wrapper_(std::move(vaapi_wrapper)) { + 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), + scaled_surface_id_(VA_INVALID_ID), + 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) + memset(&src_region_, 0, sizeof(src_region_)); + memset(&dst_region_, 0, sizeof(dst_region_)); } 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 (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); + + if (encryption_scheme_ == EncryptionScheme::kCenc) { + 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()) { + if (subsamples.size() != 1) { + LOG(ERROR) << "Need single subsample for encryption pattern"; + protected_session_state_ = ProtectedSessionState::kFailed; + return protected_session_state_; + } + crypto_params->blocks_stripe_encrypted = + decrypt_config_->encryption_pattern()->crypt_byte_block(); + crypto_params->blocks_stripe_clear = + decrypt_config_->encryption_pattern()->skip_byte_block(); + VAEncryptionSegmentInfo segment_info = {}; + segment_info.segment_start_offset = offset; + segment_info.init_byte_length = subsamples[0].clear_bytes; + segment_info.segment_length = + subsamples[0].clear_bytes + subsamples[0].cypher_bytes; + memcpy(segment_info.aes_cbc_iv_or_ctr, decrypt_config_->iv().data(), + DecryptConfig::kDecryptionKeySize); + segments->emplace_back(std::move(segment_info)); + } else { + 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; + size_t partial_block_size = + (DecryptConfig::kDecryptionKeySize - + (total_cypher_size % DecryptConfig::kDecryptionKeySize)) % + DecryptConfig::kDecryptionKeySize; + segment_info.partial_aes_block_size = partial_block_size; + memcpy(segment_info.aes_cbc_iv_or_ctr, iv.data(), + DecryptConfig::kDecryptionKeySize); + 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; +} + +bool VaapiVideoDecoderDelegate::FillDecodeScalingIfNeeded( + const gfx::Rect& decode_visible_rect, + VASurfaceID decode_surface_id, + scoped_refptr<VASurface> output_surface, + VAProcPipelineParameterBuffer* proc_buffer) { + if (!vaapi_dec_->IsScalingDecode()) + return false; + + // Submit the buffer for the inline decode scaling. + memset(proc_buffer, 0, sizeof(*proc_buffer)); + src_region_.x = base::checked_cast<int16_t>(decode_visible_rect.x()); + src_region_.y = base::checked_cast<int16_t>(decode_visible_rect.y()); + src_region_.width = base::checked_cast<uint16_t>(decode_visible_rect.width()); + src_region_.height = + base::checked_cast<uint16_t>(decode_visible_rect.height()); + + gfx::Rect scaled_visible_rect = vaapi_dec_->GetOutputVisibleRect( + decode_visible_rect, output_surface->size()); + dst_region_.x = base::checked_cast<int16_t>(scaled_visible_rect.x()); + dst_region_.y = base::checked_cast<int16_t>(scaled_visible_rect.y()); + dst_region_.width = base::checked_cast<uint16_t>(scaled_visible_rect.width()); + dst_region_.height = + base::checked_cast<uint16_t>(scaled_visible_rect.height()); + + proc_buffer->surface_region = &src_region_; + proc_buffer->output_region = &dst_region_; + + scaled_surface_id_ = output_surface->id(); + proc_buffer->additional_outputs = &scaled_surface_id_; + proc_buffer->num_additional_outputs = 1; + proc_buffer->surface = decode_surface_id; + return true; +} + +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 |