| // Copyright 2019 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/cdm/fuchsia/fuchsia_stream_decryptor.h" |
| |
| #include <fuchsia/media/cpp/fidl.h> |
| #include <fuchsia/media/drm/cpp/fidl.h> |
| |
| #include "base/fuchsia/fuchsia_logging.h" |
| #include "base/functional/bind.h" |
| #include "base/logging.h" |
| #include "base/task/bind_post_task.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" |
| |
| namespace media { |
| namespace { |
| |
| std::string GetEncryptionScheme(EncryptionScheme mode) { |
| switch (mode) { |
| case EncryptionScheme::kCenc: |
| return fuchsia::media::ENCRYPTION_SCHEME_CENC; |
| case EncryptionScheme::kCbcs: |
| return fuchsia::media::ENCRYPTION_SCHEME_CBCS; |
| default: |
| NOTREACHED() << "unknown encryption mode " << static_cast<int>(mode); |
| return ""; |
| } |
| } |
| |
| 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; |
| } |
| |
| fuchsia::media::FormatDetails GetClearFormatDetails() { |
| fuchsia::media::EncryptedFormat encrypted_format; |
| encrypted_format.set_scheme(fuchsia::media::ENCRYPTION_SCHEME_UNENCRYPTED) |
| .set_subsamples({}) |
| .set_init_vector({}); |
| |
| 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_scheme(GetEncryptionScheme(config->encryption_scheme())) |
| .set_key_id(std::vector<uint8_t>(config->key_id().begin(), |
| config->key_id().end())) |
| .set_init_vector( |
| std::vector<uint8_t>(config->iv().begin(), config->iv().end())) |
| .set_subsamples(GetSubsamples(config->subsamples())); |
| if (config->encryption_scheme() == EncryptionScheme::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 |
| |
| FuchsiaStreamDecryptor::FuchsiaStreamDecryptor( |
| fuchsia::media::StreamProcessorPtr processor) |
| : processor_(std::move(processor), this), |
| allocator_("CrFuchsiaStreamDecryptor") {} |
| |
| FuchsiaStreamDecryptor::~FuchsiaStreamDecryptor() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| } |
| |
| base::RepeatingClosure FuchsiaStreamDecryptor::GetOnNewKeyClosure() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| return base::BindPostTaskToCurrentDefault(base::BindRepeating( |
| &FuchsiaStreamDecryptor::OnNewKey, weak_factory_.GetWeakPtr())); |
| } |
| |
| void FuchsiaStreamDecryptor::Initialize(Sink* sink, |
| size_t min_buffer_size, |
| size_t min_buffer_count) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| sink_ = sink; |
| |
| min_buffer_size_ = min_buffer_size; |
| min_buffer_count_ = min_buffer_count; |
| |
| input_buffer_collection_ = allocator_.AllocateNewCollection(); |
| input_buffer_collection_->CreateSharedToken( |
| base::BindOnce(&StreamProcessorHelper::SetInputBufferCollectionToken, |
| base::Unretained(&processor_))); |
| auto buffer_constraints = VmoBuffer::GetRecommendedConstraints( |
| kInputBufferCount, min_buffer_size_, /*writable=*/true); |
| input_buffer_collection_->Initialize(std::move(buffer_constraints), |
| "CrFuchsiaStreamDecryptor"); |
| input_buffer_collection_->AcquireBuffers(base::BindOnce( |
| &FuchsiaStreamDecryptor::OnInputBuffersAcquired, base::Unretained(this))); |
| } |
| |
| void FuchsiaStreamDecryptor::EnqueueBuffer( |
| scoped_refptr<DecoderBuffer> buffer) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| input_writer_queue_.EnqueueBuffer(std::move(buffer)); |
| } |
| |
| void FuchsiaStreamDecryptor::Reset() { |
| 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(); |
| waiting_for_key_ = false; |
| } |
| |
| void FuchsiaStreamDecryptor::OnStreamProcessorAllocateOutputBuffers( |
| const fuchsia::media::StreamBufferConstraints& stream_constraints) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| output_buffer_collection_ = allocator_.AllocateNewCollection(); |
| output_buffer_collection_->CreateSharedToken( |
| base::BindOnce(&StreamProcessorHelper::CompleteOutputBuffersAllocation, |
| base::Unretained(&processor_))); |
| output_buffer_collection_->CreateSharedToken( |
| base::BindOnce(&Sink::OnSysmemBufferStreamBufferCollectionToken, |
| base::Unretained(sink_))); |
| |
| fuchsia::sysmem::BufferCollectionConstraints buffer_constraints; |
| buffer_constraints.usage.none = fuchsia::sysmem::noneUsage; |
| buffer_constraints.min_buffer_count = min_buffer_count_; |
| buffer_constraints.has_buffer_memory_constraints = true; |
| buffer_constraints.buffer_memory_constraints.min_size_bytes = |
| min_buffer_size_; |
| buffer_constraints.buffer_memory_constraints.ram_domain_supported = true; |
| buffer_constraints.buffer_memory_constraints.cpu_domain_supported = true; |
| buffer_constraints.buffer_memory_constraints.inaccessible_domain_supported = |
| true; |
| |
| output_buffer_collection_->Initialize(std::move(buffer_constraints), |
| "CrFuchsiaStreamDecryptorOutput"); |
| } |
| |
| void FuchsiaStreamDecryptor::OnStreamProcessorEndOfStream() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| sink_->OnSysmemBufferStreamEndOfStream(); |
| } |
| |
| void FuchsiaStreamDecryptor::OnStreamProcessorOutputFormat( |
| fuchsia::media::StreamOutputFormat format) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| } |
| |
| void FuchsiaStreamDecryptor::OnStreamProcessorOutputPacket( |
| StreamProcessorHelper::IoPacket packet) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| sink_->OnSysmemBufferStreamOutputPacket(std::move(packet)); |
| } |
| |
| void FuchsiaStreamDecryptor::OnStreamProcessorNoKey() { |
| 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_event_) { |
| retry_on_no_key_event_ = false; |
| input_writer_queue_.Unpause(); |
| return; |
| } |
| |
| waiting_for_key_ = true; |
| sink_->OnSysmemBufferStreamNoKey(); |
| } |
| |
| void FuchsiaStreamDecryptor::OnStreamProcessorError() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| OnError(); |
| } |
| |
| void FuchsiaStreamDecryptor::OnError() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| Reset(); |
| |
| // No need to reset other fields since OnError() is called for non-recoverable |
| // errors. |
| |
| sink_->OnSysmemBufferStreamError(); |
| } |
| |
| void FuchsiaStreamDecryptor::OnInputBuffersAcquired( |
| std::vector<VmoBuffer> buffers, |
| const fuchsia::sysmem::SingleBufferSettings&) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (buffers.empty()) { |
| OnError(); |
| return; |
| } |
| |
| input_writer_queue_.Start( |
| std::move(buffers), |
| base::BindRepeating(&FuchsiaStreamDecryptor::SendInputPacket, |
| base::Unretained(this)), |
| base::BindRepeating(&FuchsiaStreamDecryptor::ProcessEndOfStream, |
| base::Unretained(this))); |
| } |
| |
| void FuchsiaStreamDecryptor::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.set_format(std::move(format)); |
| processor_.Process(std::move(packet)); |
| } |
| |
| void FuchsiaStreamDecryptor::ProcessEndOfStream() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| processor_.ProcessEos(); |
| } |
| |
| void FuchsiaStreamDecryptor::OnNewKey() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!waiting_for_key_) { |
| retry_on_no_key_event_ = true; |
| return; |
| } |
| |
| DCHECK(!retry_on_no_key_event_); |
| waiting_for_key_ = false; |
| input_writer_queue_.Unpause(); |
| } |
| |
| } // namespace media |