| // Copyright (c) 2012 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/filters/decrypting_demuxer_stream.h" |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/trace_event/trace_event.h" |
| #include "media/base/bind_to_current_loop.h" |
| #include "media/base/cdm_context.h" |
| #include "media/base/decoder_buffer.h" |
| #include "media/base/media_log.h" |
| #include "media/base/media_util.h" |
| |
| namespace media { |
| |
| static bool IsStreamValid(DemuxerStream* stream) { |
| return ((stream->type() == DemuxerStream::AUDIO && |
| stream->audio_decoder_config().IsValidConfig()) || |
| (stream->type() == DemuxerStream::VIDEO && |
| stream->video_decoder_config().IsValidConfig())); |
| } |
| |
| DecryptingDemuxerStream::DecryptingDemuxerStream( |
| const scoped_refptr<base::SequencedTaskRunner>& task_runner, |
| MediaLog* media_log, |
| const WaitingCB& waiting_cb) |
| : task_runner_(task_runner), |
| media_log_(media_log), |
| waiting_cb_(waiting_cb) { |
| DETACH_FROM_SEQUENCE(sequence_checker_); |
| } |
| |
| std::string DecryptingDemuxerStream::GetDisplayName() const { |
| return "DecryptingDemuxerStream"; |
| } |
| |
| void DecryptingDemuxerStream::Initialize(DemuxerStream* stream, |
| CdmContext* cdm_context, |
| PipelineStatusCallback status_cb) { |
| DVLOG(2) << __func__; |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_EQ(state_, kUninitialized) << state_; |
| DCHECK(stream); |
| DCHECK(cdm_context); |
| DCHECK(!demuxer_stream_); |
| |
| demuxer_stream_ = stream; |
| init_cb_ = BindToCurrentLoop(std::move(status_cb)); |
| |
| InitializeDecoderConfig(); |
| |
| if (!cdm_context->GetDecryptor()) { |
| DVLOG(1) << __func__ << ": no decryptor"; |
| state_ = kUninitialized; |
| std::move(init_cb_).Run(DECODER_ERROR_NOT_SUPPORTED); |
| return; |
| } |
| |
| decryptor_ = cdm_context->GetDecryptor(); |
| |
| event_cb_registration_ = cdm_context->RegisterEventCB(base::BindRepeating( |
| &DecryptingDemuxerStream::OnCdmContextEvent, weak_factory_.GetWeakPtr())); |
| |
| state_ = kIdle; |
| std::move(init_cb_).Run(PIPELINE_OK); |
| } |
| |
| void DecryptingDemuxerStream::Read(ReadCB read_cb) { |
| DVLOG(3) << __func__; |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_EQ(state_, kIdle) << state_; |
| DCHECK(read_cb); |
| CHECK(!read_cb_) << "Overlapping reads are not supported."; |
| |
| read_cb_ = BindToCurrentLoop(std::move(read_cb)); |
| state_ = kPendingDemuxerRead; |
| demuxer_stream_->Read( |
| base::BindOnce(&DecryptingDemuxerStream::OnBufferReadFromDemuxerStream, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void DecryptingDemuxerStream::Reset(base::OnceClosure closure) { |
| DVLOG(2) << __func__ << " - state: " << state_; |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(state_ != kUninitialized) << state_; |
| DCHECK(!reset_cb_); |
| |
| reset_cb_ = BindToCurrentLoop(std::move(closure)); |
| |
| decryptor_->CancelDecrypt(GetDecryptorStreamType()); |
| |
| // Reset() cannot complete if the read callback is still pending. |
| // Defer the resetting process in this case. The |reset_cb_| will be fired |
| // after the read callback is fired - see OnBufferReadFromDemuxerStream() and |
| // OnBufferDecrypted(). |
| if (state_ == kPendingDemuxerRead || state_ == kPendingDecrypt) { |
| DCHECK(read_cb_); |
| return; |
| } |
| |
| if (state_ == kWaitingForKey) { |
| CompleteWaitingForDecryptionKey(); |
| DCHECK(read_cb_); |
| pending_buffer_to_decrypt_ = nullptr; |
| std::move(read_cb_).Run(kAborted, nullptr); |
| } |
| |
| DCHECK(!read_cb_); |
| DoReset(); |
| } |
| |
| AudioDecoderConfig DecryptingDemuxerStream::audio_decoder_config() { |
| DCHECK(state_ != kUninitialized) << state_; |
| CHECK_EQ(demuxer_stream_->type(), AUDIO); |
| return audio_config_; |
| } |
| |
| VideoDecoderConfig DecryptingDemuxerStream::video_decoder_config() { |
| DCHECK(state_ != kUninitialized) << state_; |
| CHECK_EQ(demuxer_stream_->type(), VIDEO); |
| return video_config_; |
| } |
| |
| DemuxerStream::Type DecryptingDemuxerStream::type() const { |
| DCHECK(state_ != kUninitialized) << state_; |
| return demuxer_stream_->type(); |
| } |
| |
| DemuxerStream::Liveness DecryptingDemuxerStream::liveness() const { |
| DCHECK(state_ != kUninitialized) << state_; |
| return demuxer_stream_->liveness(); |
| } |
| |
| void DecryptingDemuxerStream::EnableBitstreamConverter() { |
| demuxer_stream_->EnableBitstreamConverter(); |
| } |
| |
| bool DecryptingDemuxerStream::SupportsConfigChanges() { |
| return demuxer_stream_->SupportsConfigChanges(); |
| } |
| |
| bool DecryptingDemuxerStream::HasClearLead() const { |
| return has_clear_lead_.value_or(false); |
| } |
| |
| DecryptingDemuxerStream::~DecryptingDemuxerStream() { |
| DVLOG(2) << __func__ << " : state_ = " << state_; |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (state_ == kUninitialized) |
| return; |
| |
| if (state_ == kWaitingForKey) |
| CompleteWaitingForDecryptionKey(); |
| if (state_ == kPendingDecrypt) |
| CompletePendingDecrypt(Decryptor::kError); |
| |
| if (decryptor_) { |
| decryptor_->CancelDecrypt(GetDecryptorStreamType()); |
| decryptor_ = nullptr; |
| } |
| if (init_cb_) |
| std::move(init_cb_).Run(PIPELINE_ERROR_ABORT); |
| if (read_cb_) |
| std::move(read_cb_).Run(kAborted, nullptr); |
| if (reset_cb_) |
| std::move(reset_cb_).Run(); |
| pending_buffer_to_decrypt_ = nullptr; |
| } |
| |
| void DecryptingDemuxerStream::OnBufferReadFromDemuxerStream( |
| DemuxerStream::Status status, |
| scoped_refptr<DecoderBuffer> buffer) { |
| DVLOG(3) << __func__ << ": status = " << status; |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_EQ(state_, kPendingDemuxerRead) << state_; |
| DCHECK(read_cb_); |
| DCHECK_EQ(buffer.get() != nullptr, status == kOk) << status; |
| |
| // Even when |reset_cb_|, we need to pass |kConfigChanged| back to |
| // the caller so that the downstream decoder can be properly reinitialized. |
| if (status == kConfigChanged) { |
| DVLOG(2) << __func__ << ": config change"; |
| DCHECK_EQ(demuxer_stream_->type() == AUDIO, audio_config_.IsValidConfig()); |
| DCHECK_EQ(demuxer_stream_->type() == VIDEO, video_config_.IsValidConfig()); |
| |
| // Update the decoder config, which the decoder will use when it is notified |
| // of kConfigChanged. |
| InitializeDecoderConfig(); |
| |
| state_ = kIdle; |
| std::move(read_cb_).Run(kConfigChanged, nullptr); |
| if (reset_cb_) |
| DoReset(); |
| return; |
| } |
| |
| if (reset_cb_) { |
| std::move(read_cb_).Run(kAborted, nullptr); |
| DoReset(); |
| return; |
| } |
| |
| if (status == kAborted || status == kError) { |
| if (status == kError) { |
| MEDIA_LOG(ERROR, media_log_) |
| << GetDisplayName() << ": demuxer stream read error."; |
| } |
| state_ = kIdle; |
| std::move(read_cb_).Run(status, nullptr); |
| return; |
| } |
| |
| DCHECK_EQ(kOk, status); |
| |
| if (buffer->end_of_stream()) { |
| DVLOG(2) << __func__ << ": EOS buffer"; |
| state_ = kIdle; |
| std::move(read_cb_).Run(kOk, std::move(buffer)); |
| return; |
| } |
| |
| // One time set of `has_clear_lead_`. |
| if (!has_clear_lead_.has_value()) { |
| has_clear_lead_ = !buffer->decrypt_config(); |
| } |
| |
| if (!buffer->decrypt_config()) { |
| DVLOG(2) << __func__ << ": clear buffer"; |
| state_ = kIdle; |
| std::move(read_cb_).Run(kOk, std::move(buffer)); |
| return; |
| } |
| |
| pending_buffer_to_decrypt_ = std::move(buffer); |
| state_ = kPendingDecrypt; |
| DecryptPendingBuffer(); |
| } |
| |
| void DecryptingDemuxerStream::DecryptPendingBuffer() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_EQ(state_, kPendingDecrypt) << state_; |
| DCHECK(!pending_buffer_to_decrypt_->end_of_stream()); |
| TRACE_EVENT_ASYNC_BEGIN2( |
| "media", "DecryptingDemuxerStream::DecryptPendingBuffer", this, "type", |
| DemuxerStream::GetTypeName(demuxer_stream_->type()), "timestamp_us", |
| pending_buffer_to_decrypt_->timestamp().InMicroseconds()); |
| decryptor_->Decrypt(GetDecryptorStreamType(), pending_buffer_to_decrypt_, |
| BindToCurrentLoop(base::BindOnce( |
| &DecryptingDemuxerStream::OnBufferDecrypted, |
| weak_factory_.GetWeakPtr()))); |
| } |
| |
| void DecryptingDemuxerStream::OnBufferDecrypted( |
| Decryptor::Status status, |
| scoped_refptr<DecoderBuffer> decrypted_buffer) { |
| DVLOG(3) << __func__ << " - status: " << status; |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_EQ(state_, kPendingDecrypt) << state_; |
| DCHECK(read_cb_); |
| DCHECK(pending_buffer_to_decrypt_); |
| CompletePendingDecrypt(status); |
| |
| bool need_to_try_again_if_nokey = key_added_while_decrypt_pending_; |
| key_added_while_decrypt_pending_ = false; |
| |
| if (reset_cb_) { |
| pending_buffer_to_decrypt_ = nullptr; |
| std::move(read_cb_).Run(kAborted, nullptr); |
| DoReset(); |
| return; |
| } |
| |
| DCHECK_EQ(status == Decryptor::kSuccess, decrypted_buffer.get() != nullptr); |
| |
| if (status == Decryptor::kError || status == Decryptor::kNeedMoreData) { |
| DVLOG(2) << __func__ << ": Error with status " << status; |
| MEDIA_LOG(ERROR, media_log_) |
| << GetDisplayName() << ": decrypt error " << status; |
| pending_buffer_to_decrypt_ = nullptr; |
| state_ = kIdle; |
| std::move(read_cb_).Run(kError, nullptr); |
| return; |
| } |
| |
| if (status == Decryptor::kNoKey) { |
| std::string key_id = pending_buffer_to_decrypt_->decrypt_config()->key_id(); |
| |
| std::string log_message = |
| "no key for key ID " + base::HexEncode(key_id.data(), key_id.size()) + |
| "; will resume decrypting after new usable key is available"; |
| DVLOG(1) << __func__ << ": " << log_message; |
| MEDIA_LOG(INFO, media_log_) << GetDisplayName() << ": " << log_message; |
| |
| if (need_to_try_again_if_nokey) { |
| // The |state_| is still kPendingDecrypt. |
| MEDIA_LOG(INFO, media_log_) |
| << GetDisplayName() << ": key was added, resuming decrypt"; |
| DecryptPendingBuffer(); |
| return; |
| } |
| |
| state_ = kWaitingForKey; |
| |
| TRACE_EVENT_ASYNC_BEGIN0( |
| "media", "DecryptingDemuxerStream::WaitingForDecryptionKey", this); |
| waiting_cb_.Run(WaitingReason::kNoDecryptionKey); |
| return; |
| } |
| |
| DCHECK_EQ(status, Decryptor::kSuccess); |
| |
| // Copy the key frame flag and duration from the encrypted to decrypted |
| // buffer. |
| // TODO(crbug.com/1116263): Ensure all fields are copied by Decryptor. |
| decrypted_buffer->set_is_key_frame( |
| pending_buffer_to_decrypt_->is_key_frame()); |
| decrypted_buffer->set_duration(pending_buffer_to_decrypt_->duration()); |
| |
| pending_buffer_to_decrypt_ = nullptr; |
| state_ = kIdle; |
| std::move(read_cb_).Run(kOk, std::move(decrypted_buffer)); |
| } |
| |
| void DecryptingDemuxerStream::OnCdmContextEvent(CdmContext::Event event) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (event != CdmContext::Event::kHasAdditionalUsableKey) |
| return; |
| |
| if (state_ == kPendingDecrypt) { |
| key_added_while_decrypt_pending_ = true; |
| return; |
| } |
| |
| // Nothing to do. |
| if (state_ != kWaitingForKey) |
| return; |
| |
| CompleteWaitingForDecryptionKey(); |
| MEDIA_LOG(INFO, media_log_) |
| << GetDisplayName() << ": key was added, resuming decrypt"; |
| state_ = kPendingDecrypt; |
| DecryptPendingBuffer(); |
| } |
| |
| void DecryptingDemuxerStream::DoReset() { |
| DCHECK(state_ != kUninitialized); |
| DCHECK(!init_cb_); |
| DCHECK(!read_cb_); |
| |
| state_ = kIdle; |
| std::move(reset_cb_).Run(); |
| } |
| |
| Decryptor::StreamType DecryptingDemuxerStream::GetDecryptorStreamType() const { |
| if (demuxer_stream_->type() == AUDIO) |
| return Decryptor::kAudio; |
| |
| DCHECK_EQ(demuxer_stream_->type(), VIDEO); |
| return Decryptor::kVideo; |
| } |
| |
| void DecryptingDemuxerStream::InitializeDecoderConfig() { |
| // The decoder selector or upstream demuxer make sure the stream is valid. |
| DCHECK(IsStreamValid(demuxer_stream_)); |
| |
| // Since |this| is a decrypted version of |demuxer_stream_|, the decoder |
| // config of |this| should always be a decrypted version of |demuxer_stream_| |
| // configs. |
| switch (demuxer_stream_->type()) { |
| case AUDIO: { |
| audio_config_ = demuxer_stream_->audio_decoder_config(); |
| if (audio_config_.is_encrypted()) |
| audio_config_.SetIsEncrypted(false); |
| break; |
| } |
| |
| case VIDEO: { |
| video_config_ = demuxer_stream_->video_decoder_config(); |
| if (video_config_.is_encrypted()) |
| video_config_.SetIsEncrypted(false); |
| break; |
| } |
| |
| default: |
| NOTREACHED(); |
| return; |
| } |
| LogMetadata(); |
| } |
| |
| void DecryptingDemuxerStream::LogMetadata() { |
| std::vector<AudioDecoderConfig> audio_metadata{audio_config_}; |
| std::vector<VideoDecoderConfig> video_metadata{video_config_}; |
| media_log_->SetProperty<MediaLogProperty::kAudioTracks>(audio_metadata); |
| media_log_->SetProperty<MediaLogProperty::kVideoTracks>(video_metadata); |
| // FFmpegDemuxer also provides a max diration, start time, and bitrate. |
| } |
| |
| void DecryptingDemuxerStream::CompletePendingDecrypt(Decryptor::Status status) { |
| DCHECK_EQ(state_, kPendingDecrypt); |
| TRACE_EVENT_ASYNC_END1("media", |
| "DecryptingDemuxerStream::DecryptPendingBuffer", this, |
| "status", Decryptor::GetStatusName(status)); |
| } |
| |
| void DecryptingDemuxerStream::CompleteWaitingForDecryptionKey() { |
| DCHECK_EQ(state_, kWaitingForKey); |
| TRACE_EVENT_ASYNC_END0( |
| "media", "DecryptingDemuxerStream::WaitingForDecryptionKey", this); |
| } |
| |
| } // namespace media |