// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/gpu/chromeos/decoder_buffer_transcryptor.h"
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/functional/callback.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "chromeos/components/cdm_factory_daemon/chromeos_cdm_context.h"
#include "media/gpu/chromeos/video_decoder_pipeline.h"
#include "media/parsers/vp9_parser.h"
namespace media {
DecoderBufferTranscryptor::TranscryptTask::TranscryptTask(
scoped_refptr<DecoderBuffer> buffer,
VideoDecoder::DecodeCB decode_done_cb)
: buffer(std::move(buffer)), decode_done_cb(std::move(decode_done_cb)) {}
DecoderBufferTranscryptor::TranscryptTask::~TranscryptTask() = default;
DecoderBufferTranscryptor::TranscryptTask::TranscryptTask(TranscryptTask&&) =
default;
DecoderBufferTranscryptor::DecoderBufferTranscryptor(
CdmContext* cdm_context,
VideoDecoderMixin& decoder,
bool needs_vp9_superframe_splitting,
OnBufferTranscryptedCB transcrypt_callback,
WaitingCB waiting_callback)
: decoder_(decoder),
transcrypt_callback_(std::move(transcrypt_callback)),
waiting_callback_(std::move(waiting_callback)),
needs_vp9_superframe_splitting_(needs_vp9_superframe_splitting) {
weak_this_ = weak_this_factory_.GetWeakPtr();
DCHECK(cdm_context);
cdm_event_cb_registration_ = cdm_context->RegisterEventCB(base::BindRepeating(
&DecoderBufferTranscryptor::OnCdmContextEvent, weak_this_));
cdm_context_ref_ = cdm_context->GetChromeOsCdmContext()->GetCdmContextRef();
DCHECK(cdm_context->GetDecryptor());
}
DecoderBufferTranscryptor::~DecoderBufferTranscryptor() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
Reset(DecoderStatus::Codes::kAborted);
}
void DecoderBufferTranscryptor::SecureBuffersMayBeAvailable() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Re-post this so we don't need to worry about re-entrancy issues.
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&DecoderBufferTranscryptor::DecryptPendingBuffer,
weak_this_));
}
void DecoderBufferTranscryptor::EnqueueBuffer(
scoped_refptr<DecoderBuffer> buffer,
VideoDecoder::DecodeCB decode_cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
transcrypt_task_queue_.emplace_back(std::move(buffer), std::move(decode_cb));
DecryptPendingBuffer();
}
void DecoderBufferTranscryptor::Reset(DecoderStatus status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (current_transcrypt_task_) {
std::move(current_transcrypt_task_->decode_done_cb).Run(status);
current_transcrypt_task_ = std::nullopt;
}
while (!transcrypt_task_queue_.empty()) {
std::move(transcrypt_task_queue_.front().decode_done_cb).Run(status);
transcrypt_task_queue_.pop_front();
}
}
void DecoderBufferTranscryptor::OnCdmContextEvent(CdmContext::Event event) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (event != CdmContext::Event::kHasAdditionalUsableKey)
return;
if (transcrypt_pending_)
key_added_while_decrypting_ = true;
else
DecryptPendingBuffer();
}
void DecoderBufferTranscryptor::DecryptPendingBuffer() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (transcrypt_pending_)
return;
if (!current_transcrypt_task_) {
if (transcrypt_task_queue_.empty())
return;
current_transcrypt_task_ = std::move(transcrypt_task_queue_.front());
transcrypt_task_queue_.pop_front();
}
DecoderBuffer* curr_buffer = current_transcrypt_task_->buffer.get();
if (curr_buffer->end_of_stream()) {
OnBufferTranscrypted(Decryptor::kSuccess, current_transcrypt_task_->buffer);
return;
}
// Check if we need to split VP9 superframes.
if (needs_vp9_superframe_splitting_ &&
Vp9Parser::IsSuperframe(curr_buffer->data(),
base::checked_cast<off_t>(curr_buffer->size()),
curr_buffer->decrypt_config())) {
base::circular_deque<Vp9Parser::FrameInfo> frames =
Vp9Parser::ExtractFrames(curr_buffer->data(),
base::checked_cast<off_t>(curr_buffer->size()),
curr_buffer->decrypt_config());
if (frames.empty()) {
LOG(ERROR) << "Failure in Vp9 superframe splitting";
OnBufferTranscrypted(Decryptor::kError, nullptr);
return;
}
// Save the original DecoderBuffer for the rest of our operations that has
// the whole superframe to keep its data valid until we are done and also to
// use for metadata reference.
scoped_refptr<DecoderBuffer> superframe =
std::move(current_transcrypt_task_->buffer);
// Put the first frame in place of the |current_transcrypt_task_|'s buffer,
// then add the rest to the queue.
//
// TODO(crbug.com/40284755): Use `base::span` in `Vp9Parser::FrameInfo`.
current_transcrypt_task_->buffer = DecoderBuffer::CopyFrom(UNSAFE_TODO(
base::span(frames.front().ptr.get(),
base::checked_cast<size_t>(frames.front().size))));
curr_buffer = current_transcrypt_task_->buffer.get();
// We only copy this limited set of fields to match what we do in the
// corresponding Decryptor implementation in:
// chromeos::ContentDecryptionModuleAdapter::OnDecrypt
current_transcrypt_task_->buffer->set_timestamp(superframe->timestamp());
current_transcrypt_task_->buffer->set_duration(superframe->duration());
current_transcrypt_task_->buffer->set_is_key_frame(
superframe->is_key_frame());
current_transcrypt_task_->buffer->set_side_data(superframe->side_data());
if (frames.front().decrypt_config) {
current_transcrypt_task_->buffer->set_decrypt_config(
std::move(frames.front().decrypt_config));
}
frames.pop_front();
// The last one in the queue should have the decode done callback and the
// rest should be DoNothing.
VideoDecoder::DecodeCB next_decode_done_cb;
if (!frames.empty()) {
next_decode_done_cb = std::move(current_transcrypt_task_->decode_done_cb);
current_transcrypt_task_->decode_done_cb = base::DoNothing();
}
while (!frames.empty()) {
// The |frames| are in decode order, so we take from the back of |frames|
// and append to the front of |transcrypt_task_queue_|.
scoped_refptr<DecoderBuffer> buffer = DecoderBuffer::CopyFrom(UNSAFE_TODO(
base::span(frames.back().ptr.get(),
base::checked_cast<size_t>(frames.back().size))));
buffer->set_timestamp(superframe->timestamp());
buffer->set_duration(superframe->duration());
buffer->set_is_key_frame(superframe->is_key_frame());
buffer->set_side_data(superframe->side_data());
if (frames.back().decrypt_config) {
buffer->set_decrypt_config(std::move(frames.back().decrypt_config));
}
frames.pop_back();
transcrypt_task_queue_.emplace_front(std::move(buffer),
std::move(next_decode_done_cb));
next_decode_done_cb = base::DoNothing();
}
}
// If we've already attached a secure buffer, don't do it again.
if (!curr_buffer->has_side_data() ||
!curr_buffer->side_data()->secure_handle) {
auto status =
decoder_->AttachSecureBuffer(current_transcrypt_task_->buffer);
if (status == CroStatus::Codes::kSecureBufferPoolEmpty) {
// We are currently out of secure buffers, so wait until this gets invoked
// again.
return;
} else if (!status.is_ok()) {
LOG(ERROR) << "Failure in attaching secure buffer";
OnBufferTranscrypted(Decryptor::kError, nullptr);
return;
}
if (curr_buffer->has_side_data() &&
curr_buffer->side_data()->secure_handle) {
// Wrap the callback so we can release the secure buffer when decoding is
// done.
current_transcrypt_task_->decode_done_cb =
base::BindOnce(&DecoderBufferTranscryptor::OnSecureBufferRelease,
weak_this_, curr_buffer->side_data()->secure_handle,
std::move(current_transcrypt_task_->decode_done_cb));
}
}
transcrypt_pending_ = true;
cdm_context_ref_->GetCdmContext()->GetDecryptor()->Decrypt(
Decryptor::kVideo, current_transcrypt_task_->buffer,
base::BindPostTaskToCurrentDefault(base::BindOnce(
&DecoderBufferTranscryptor::OnBufferTranscrypted, weak_this_)));
}
void DecoderBufferTranscryptor::OnBufferTranscrypted(
Decryptor::Status status,
scoped_refptr<DecoderBuffer> transcrypted_buffer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
transcrypt_pending_ = false;
// If we've cleared the task then drop this one.
if (!current_transcrypt_task_) {
DecryptPendingBuffer();
return;
}
const bool need_to_try_again_if_nokey = key_added_while_decrypting_;
key_added_while_decrypting_ = false;
// This should never happen w/our decryptor.
DCHECK_NE(status, Decryptor::kNeedMoreData);
if (status == Decryptor::kError) {
// Clear |current_transcrypt_task_| now so when the pipeline invokes Reset
// on us we don't try to invoke the move'd callback.
std::optional<TranscryptTask> temp_task =
std::move(current_transcrypt_task_);
current_transcrypt_task_ = std::nullopt;
transcrypt_callback_.Run(nullptr, std::move(temp_task->decode_done_cb));
return;
}
if (status == Decryptor::kNoKey) {
if (need_to_try_again_if_nokey) {
DecryptPendingBuffer();
return;
}
waiting_callback_.Run(WaitingReason::kNoDecryptionKey);
return;
}
DCHECK_EQ(status, Decryptor::kSuccess);
DCHECK(transcrypted_buffer);
const bool eos_buffer = transcrypted_buffer->end_of_stream();
std::optional<TranscryptTask> temp_task = std::move(current_transcrypt_task_);
current_transcrypt_task_ = std::nullopt;
transcrypt_callback_.Run(std::move(transcrypted_buffer),
std::move(temp_task->decode_done_cb));
// Do not post this as another task, execute it immediately instead. Otherwise
// we will not be parallelizing decrypt and decode fully. We want to have the
// Mojo IPC call for decrypt active whenever we are processing a decode task,
// and since the decoder probably just put a decode task in the queue...if we
// hand control back to the task runner it'll do decode now even though we
// have no decrypt task in flight.
if (!eos_buffer)
DecryptPendingBuffer();
}
void DecoderBufferTranscryptor::OnSecureBufferRelease(
uint64_t secure_handle,
VideoDecoder::DecodeCB decode_cb,
DecoderStatus status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
decoder_->ReleaseSecureBuffer(secure_handle);
std::move(decode_cb).Run(status);
}
} // namespace media