blob: 176a0251b0d28b56816eb838ecbd1b33941b066c [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/fuchsia/cdm/fuchsia_stream_decryptor.h"
#include <fuchsia/media/cpp/fidl.h>
#include <fuchsia/media/drm/cpp/fidl.h>
#include "base/bind.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "base/logging.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/decoder_buffer.h"
#include "media/base/decrypt_config.h"
#include "media/base/encryption_pattern.h"
#include "media/base/subsample_entry.h"
#include "media/fuchsia/common/sysmem_buffer_reader.h"
#include "media/fuchsia/common/sysmem_buffer_writer.h"
namespace media {
namespace {
// FuchsiaClearStreamDecryptor copies decrypted data immediately once it's
// available, so it doesn't need more than one output buffer.
const size_t kMinClearStreamOutputFrames = 1;
std::string GetEncryptionMode(EncryptionMode mode) {
switch (mode) {
case EncryptionMode::kCenc:
return fuchsia::media::drm::ENCRYPTION_MODE_CENC;
case EncryptionMode::kCbcs:
return fuchsia::media::drm::ENCRYPTION_MODE_CBCS;
default:
NOTREACHED() << "unknown encryption mode " << static_cast<int>(mode);
return "";
}
}
fuchsia::media::KeyId GetKeyId(const std::string& key_id) {
fuchsia::media::KeyId fuchsia_key_id;
// |key_id| may be empty when sending clear frames while we don't have
// Key Id yet. Send a zero |key_id| in that case.
// TODO(crbug.com/1012525): Remove this case once fxb/38253 is resolved.
if (key_id.empty()) {
fuchsia_key_id.data = {0};
} else {
DCHECK_EQ(key_id.size(), fuchsia::media::KEY_ID_SIZE);
std::copy(key_id.begin(), key_id.end(), fuchsia_key_id.data.begin());
}
return fuchsia_key_id;
}
std::vector<fuchsia::media::SubsampleEntry> GetSubsamples(
const std::vector<SubsampleEntry>& subsamples) {
std::vector<fuchsia::media::SubsampleEntry> fuchsia_subsamples(
subsamples.size());
for (size_t i = 0; i < subsamples.size(); i++) {
fuchsia_subsamples[i].clear_bytes = subsamples[i].clear_bytes;
fuchsia_subsamples[i].encrypted_bytes = subsamples[i].cypher_bytes;
}
return fuchsia_subsamples;
}
fuchsia::media::EncryptionPattern GetEncryptionPattern(
EncryptionPattern pattern) {
fuchsia::media::EncryptionPattern fuchsia_pattern;
fuchsia_pattern.clear_blocks = pattern.skip_byte_block();
fuchsia_pattern.encrypted_blocks = pattern.crypt_byte_block();
return fuchsia_pattern;
}
// We shouldn't need to set Key ID for clear frames, but it's currently
// required by the CDM API, see fxb/38253 . This function takes
// |placeholder_key_id| to workaround that issue.
// TODO(crbug.com/1012525): Remove |placeholder_key_id| once fxb/38253 is
// resolved.
fuchsia::media::FormatDetails GetClearFormatDetails(
size_t packet_size,
const std::string& placeholder_key_id) {
fuchsia::media::EncryptedFormat encrypted_format;
encrypted_format.set_mode(fuchsia::media::drm::ENCRYPTION_MODE_CENC)
.set_key_id(GetKeyId(placeholder_key_id))
.set_init_vector(std::vector<uint8_t>(DecryptConfig::kDecryptionKeySize));
std::vector<fuchsia::media::SubsampleEntry> subsamples(1);
subsamples[0].clear_bytes = packet_size;
subsamples[0].encrypted_bytes = 0;
encrypted_format.set_subsamples(subsamples);
fuchsia::media::FormatDetails format;
format.set_format_details_version_ordinal(0);
format.mutable_domain()->crypto().set_encrypted(std::move(encrypted_format));
return format;
}
fuchsia::media::FormatDetails GetEncryptedFormatDetails(
const DecryptConfig* config) {
DCHECK(config);
fuchsia::media::EncryptedFormat encrypted_format;
encrypted_format.set_mode(GetEncryptionMode(config->encryption_mode()))
.set_key_id(GetKeyId(config->key_id()))
.set_init_vector(
std::vector<uint8_t>(config->iv().begin(), config->iv().end()))
.set_subsamples(GetSubsamples(config->subsamples()));
if (config->encryption_mode() == EncryptionMode::kCbcs) {
DCHECK(config->encryption_pattern().has_value());
encrypted_format.set_pattern(
GetEncryptionPattern(config->encryption_pattern().value()));
}
fuchsia::media::FormatDetails format;
format.set_format_details_version_ordinal(0);
format.mutable_domain()->crypto().set_encrypted(std::move(encrypted_format));
return format;
}
} // namespace
FuchsiaStreamDecryptorBase::FuchsiaStreamDecryptorBase(
fuchsia::media::StreamProcessorPtr processor)
: processor_(std::move(processor), this) {}
FuchsiaStreamDecryptorBase::~FuchsiaStreamDecryptorBase() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void FuchsiaStreamDecryptorBase::DecryptInternal(
scoped_refptr<DecoderBuffer> encrypted) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
input_writer_queue_.EnqueueBuffer(std::move(encrypted));
}
void FuchsiaStreamDecryptorBase::ResetStream() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Close current stream and drop all the cached decoder buffers.
// Keep input and output buffers to avoid buffer re-allocation.
processor_.Reset();
input_writer_queue_.ResetQueue();
}
// StreamProcessorHelper::Client implementation:
void FuchsiaStreamDecryptorBase::AllocateInputBuffers(
const fuchsia::media::StreamBufferConstraints& stream_constraints) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::Optional<fuchsia::sysmem::BufferCollectionConstraints>
buffer_constraints =
SysmemBufferWriter::GetRecommendedConstraints(stream_constraints);
if (!buffer_constraints.has_value()) {
OnError();
return;
}
input_pool_creator_ =
allocator_.MakeBufferPoolCreator(1 /* num_shared_token */);
input_pool_creator_->Create(
std::move(buffer_constraints).value(),
base::BindOnce(&FuchsiaStreamDecryptorBase::OnInputBufferPoolCreated,
base::Unretained(this)));
}
void FuchsiaStreamDecryptorBase::OnOutputFormat(
fuchsia::media::StreamOutputFormat format) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void FuchsiaStreamDecryptorBase::OnInputBufferPoolCreated(
std::unique_ptr<SysmemBufferPool> pool) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!pool) {
DLOG(ERROR) << "Fail to allocate input buffer.";
OnError();
return;
}
input_pool_ = std::move(pool);
// Provide token before enabling writer. Tokens must be provided to
// StreamProcessor before getting the allocated buffers.
processor_.CompleteInputBuffersAllocation(input_pool_->TakeToken());
input_pool_->CreateWriter(base::BindOnce(
&FuchsiaStreamDecryptorBase::OnWriterCreated, base::Unretained(this)));
}
void FuchsiaStreamDecryptorBase::OnWriterCreated(
std::unique_ptr<SysmemBufferWriter> writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!writer) {
OnError();
return;
}
input_writer_queue_.Start(
std::move(writer),
base::BindRepeating(&FuchsiaStreamDecryptorBase::SendInputPacket,
base::Unretained(this)),
base::BindRepeating(&FuchsiaStreamDecryptorBase::ProcessEndOfStream,
base::Unretained(this)));
}
void FuchsiaStreamDecryptorBase::SendInputPacket(
const DecoderBuffer* buffer,
StreamProcessorHelper::IoPacket packet) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!packet.unit_end()) {
// The encrypted data size is too big. Decryptor should consider
// splitting the buffer and update the IV and subsample entries.
// TODO(crbug.com/1003651): Handle large encrypted buffer correctly. For
// now, just reject the decryption.
LOG(ERROR) << "DecoderBuffer doesn't fit in one packet.";
OnError();
return;
}
fuchsia::media::FormatDetails format =
(buffer->decrypt_config())
? GetEncryptedFormatDetails(buffer->decrypt_config())
: GetClearFormatDetails(packet.size(), last_new_key_id_);
packet.set_format(std::move(format));
processor_.Process(std::move(packet));
}
void FuchsiaStreamDecryptorBase::ProcessEndOfStream() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
processor_.ProcessEos();
}
std::unique_ptr<FuchsiaClearStreamDecryptor>
FuchsiaClearStreamDecryptor::Create(
fuchsia::media::drm::ContentDecryptionModule* cdm) {
DCHECK(cdm);
fuchsia::media::drm::DecryptorParams params;
params.set_require_secure_mode(false);
params.mutable_input_details()->set_format_details_version_ordinal(0);
fuchsia::media::StreamProcessorPtr stream_processor;
cdm->CreateDecryptor(std::move(params), stream_processor.NewRequest());
return std::make_unique<FuchsiaClearStreamDecryptor>(
std::move(stream_processor));
}
FuchsiaClearStreamDecryptor::FuchsiaClearStreamDecryptor(
fuchsia::media::StreamProcessorPtr processor)
: FuchsiaStreamDecryptorBase(std::move(processor)) {}
FuchsiaClearStreamDecryptor::~FuchsiaClearStreamDecryptor() = default;
void FuchsiaClearStreamDecryptor::Decrypt(
scoped_refptr<DecoderBuffer> encrypted,
Decryptor::DecryptCB decrypt_cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!decrypt_cb_);
decrypt_cb_ = std::move(decrypt_cb);
current_status_ = Decryptor::kSuccess;
DecryptInternal(std::move(encrypted));
}
void FuchsiaClearStreamDecryptor::CancelDecrypt() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ResetStream();
// Fire |decrypt_cb_| immediately as required by Decryptor::CancelDecrypt.
if (decrypt_cb_)
std::move(decrypt_cb_).Run(Decryptor::kSuccess, nullptr);
}
void FuchsiaClearStreamDecryptor::AllocateOutputBuffers(
const fuchsia::media::StreamBufferConstraints& stream_constraints) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!stream_constraints.has_packet_count_for_client_max() ||
!stream_constraints.has_packet_count_for_client_min()) {
DLOG(ERROR) << "StreamBufferConstraints doesn't contain required fields.";
OnError();
return;
}
size_t num_buffers_for_client = std::max(
kMinClearStreamOutputFrames,
static_cast<size_t>(stream_constraints.packet_count_for_client_min()));
size_t num_buffers_for_server =
stream_constraints.default_settings().packet_count_for_server();
output_pool_creator_ =
allocator_.MakeBufferPoolCreator(1 /* num_shared_token */);
output_pool_creator_->Create(
SysmemBufferReader::GetRecommendedConstraints(num_buffers_for_client),
base::BindOnce(&FuchsiaClearStreamDecryptor::OnOutputBufferPoolCreated,
base::Unretained(this), num_buffers_for_client,
num_buffers_for_server));
}
void FuchsiaClearStreamDecryptor::OnProcessEos() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Decryptor never pushes EOS frame.
NOTREACHED();
}
void FuchsiaClearStreamDecryptor::OnOutputPacket(
StreamProcessorHelper::IoPacket packet) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(decrypt_cb_);
DCHECK(output_reader_);
if (!output_pool_->is_live()) {
DLOG(ERROR) << "Output buffer pool is dead.";
return;
}
// If that's not the last packet for the current Decrypt() request then just
// store the output and wait for the next packet.
if (!packet.unit_end()) {
size_t pos = output_data_.size();
output_data_.resize(pos + packet.size());
bool read_success = output_reader_->Read(
packet.index(), packet.offset(),
base::make_span(output_data_.data() + pos, packet.size()));
if (!read_success) {
// If we've failed to read a partial packet then delay reporting the error
// until we've received the last packet to make sure we consume all output
// packets generated by the last Decrypt() call.
DLOG(ERROR) << "Fail to get decrypted result.";
current_status_ = Decryptor::kError;
output_data_.clear();
}
return;
}
// We've received the last packet. Assemble DecoderBuffer and pass it to the
// DecryptCB.
auto clear_buffer =
base::MakeRefCounted<DecoderBuffer>(output_data_.size() + packet.size());
clear_buffer->set_timestamp(packet.timestamp());
// Copy data received in the previous packets.
memcpy(clear_buffer->writable_data(), output_data_.data(),
output_data_.size());
output_data_.clear();
// Copy data received in the last packet
bool read_success = output_reader_->Read(
packet.index(), packet.offset(),
base::make_span(clear_buffer->writable_data() + output_data_.size(),
packet.size()));
if (!read_success) {
DLOG(ERROR) << "Fail to get decrypted result.";
current_status_ = Decryptor::kError;
}
std::move(decrypt_cb_)
.Run(current_status_, current_status_ == Decryptor::kSuccess
? std::move(clear_buffer)
: nullptr);
}
void FuchsiaClearStreamDecryptor::OnNoKey() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Reset the queue. The client is expected to call Decrypt() with the same
// buffer again when it gets kNoKey.
input_writer_queue_.ResetQueue();
if (decrypt_cb_)
std::move(decrypt_cb_).Run(Decryptor::kNoKey, nullptr);
}
void FuchsiaClearStreamDecryptor::OnError() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ResetStream();
if (decrypt_cb_)
std::move(decrypt_cb_).Run(Decryptor::kError, nullptr);
}
void FuchsiaClearStreamDecryptor::OnOutputBufferPoolCreated(
size_t num_buffers_for_client,
size_t num_buffers_for_server,
std::unique_ptr<SysmemBufferPool> pool) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!pool) {
LOG(ERROR) << "Fail to allocate output buffer.";
OnError();
return;
}
output_pool_ = std::move(pool);
// Provide token before enabling reader. Tokens must be provided to
// StreamProcessor before getting the allocated buffers.
processor_.CompleteOutputBuffersAllocation(num_buffers_for_client,
num_buffers_for_server,
output_pool_->TakeToken());
output_pool_->CreateReader(base::BindOnce(
&FuchsiaClearStreamDecryptor::OnOutputBufferPoolReaderCreated,
base::Unretained(this)));
}
void FuchsiaClearStreamDecryptor::OnOutputBufferPoolReaderCreated(
std::unique_ptr<SysmemBufferReader> reader) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!reader) {
LOG(ERROR) << "Fail to enable output buffer reader.";
OnError();
return;
}
DCHECK(!output_reader_);
output_reader_ = std::move(reader);
}
FuchsiaSecureStreamDecryptor::FuchsiaSecureStreamDecryptor(
fuchsia::media::StreamProcessorPtr processor,
Client* client)
: FuchsiaStreamDecryptorBase(std::move(processor)), client_(client) {}
FuchsiaSecureStreamDecryptor::~FuchsiaSecureStreamDecryptor() = default;
void FuchsiaSecureStreamDecryptor::SetOutputBufferCollectionToken(
fuchsia::sysmem::BufferCollectionTokenPtr token,
size_t num_buffers_for_decryptor,
size_t num_buffers_for_codec) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!complete_buffer_allocation_callback_);
complete_buffer_allocation_callback_ =
base::BindOnce(&StreamProcessorHelper::CompleteOutputBuffersAllocation,
base::Unretained(&processor_), num_buffers_for_decryptor,
num_buffers_for_codec, std::move(token));
if (waiting_output_buffers_) {
std::move(complete_buffer_allocation_callback_).Run();
waiting_output_buffers_ = false;
}
}
void FuchsiaSecureStreamDecryptor::Decrypt(
scoped_refptr<DecoderBuffer> encrypted) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DecryptInternal(std::move(encrypted));
}
void FuchsiaSecureStreamDecryptor::Reset() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ResetStream();
waiting_for_key_ = false;
}
void FuchsiaSecureStreamDecryptor::AllocateOutputBuffers(
const fuchsia::media::StreamBufferConstraints& stream_constraints) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (complete_buffer_allocation_callback_) {
std::move(complete_buffer_allocation_callback_).Run();
} else {
waiting_output_buffers_ = true;
}
}
void FuchsiaSecureStreamDecryptor::OnProcessEos() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
client_->OnDecryptorEndOfStreamPacket();
}
void FuchsiaSecureStreamDecryptor::OnOutputPacket(
StreamProcessorHelper::IoPacket packet) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
client_->OnDecryptorOutputPacket(std::move(packet));
}
FuchsiaSecureStreamDecryptor::NewKeyCB
FuchsiaSecureStreamDecryptor::GetOnNewKeyClosure() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return BindToCurrentLoop(base::BindRepeating(
&FuchsiaSecureStreamDecryptor::OnNewKey, weak_factory_.GetWeakPtr()));
}
void FuchsiaSecureStreamDecryptor::OnError() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ResetStream();
// No need to reset other fields since OnError() is called for non-recoverable
// errors.
client_->OnDecryptorError();
}
void FuchsiaSecureStreamDecryptor::OnNoKey() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!waiting_for_key_);
// Reset stream position, but keep all pending buffers. They will be
// resubmitted later, when we have a new key.
input_writer_queue_.ResetPositionAndPause();
if (retry_on_no_key_) {
retry_on_no_key_ = false;
input_writer_queue_.Unpause();
return;
}
waiting_for_key_ = true;
client_->OnDecryptorNoKey();
}
void FuchsiaSecureStreamDecryptor::OnNewKey(const std::string& key_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Currently Widevine CDM requires a valid key_id for frames that are not
// encrypted, but we don't have a key_id in the beginning of the stream. To
// workaround this issue we save the |key_id| here and then use it for clear
// frames in SendInputPacket().
// TODO(crbug.com/1012525): Remove this hack once fxb/38253 is resolved: CDM
// shouldn't need |key_id| to handle clear frames.
last_new_key_id_ = key_id;
if (!waiting_for_key_) {
retry_on_no_key_ = true;
return;
}
DCHECK(!retry_on_no_key_);
waiting_for_key_ = false;
input_writer_queue_.Unpause();
}
} // namespace media