blob: f395b38c8b324224c80ac761219e94785a5acd38 [file] [log] [blame]
// 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 "cobalt/media/filters/decrypting_video_decoder.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/single_thread_task_runner.h"
#include "base/string_number_conversions.h"
#include "base/trace_event/trace_event.h"
#include "cobalt/media/base/bind_to_current_loop.h"
#include "cobalt/media/base/cdm_context.h"
#include "cobalt/media/base/decoder_buffer.h"
#include "cobalt/media/base/media_log.h"
#include "cobalt/media/base/video_frame.h"
namespace cobalt {
namespace media {
const char DecryptingVideoDecoder::kDecoderName[] = "DecryptingVideoDecoder";
DecryptingVideoDecoder::DecryptingVideoDecoder(
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
const scoped_refptr<MediaLog>& media_log,
const base::Closure& waiting_for_decryption_key_cb)
: task_runner_(task_runner),
media_log_(media_log),
state_(kUninitialized),
waiting_for_decryption_key_cb_(waiting_for_decryption_key_cb),
decryptor_(NULL),
key_added_while_decode_pending_(false),
trace_id_(0),
weak_factory_(this) {}
std::string DecryptingVideoDecoder::GetDisplayName() const {
return kDecoderName;
}
void DecryptingVideoDecoder::Initialize(const VideoDecoderConfig& config,
bool /* low_delay */,
CdmContext* cdm_context,
const InitCB& init_cb,
const OutputCB& output_cb) {
DVLOG(2) << __func__ << ": " << config.AsHumanReadableString();
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(state_ == kUninitialized || state_ == kIdle ||
state_ == kDecodeFinished)
<< state_;
DCHECK(decode_cb_.is_null());
DCHECK(reset_cb_.is_null());
DCHECK(config.IsValidConfig());
DCHECK(config.is_encrypted());
init_cb_ = BindToCurrentLoop(init_cb);
output_cb_ = BindToCurrentLoop(output_cb);
weak_this_ = weak_factory_.GetWeakPtr();
config_ = config;
if (state_ == kUninitialized) {
DCHECK(cdm_context);
if (!cdm_context->GetDecryptor()) {
MEDIA_LOG(DEBUG, media_log_) << GetDisplayName() << ": no decryptor";
base::ResetAndReturn(&init_cb_).Run(false);
return;
}
decryptor_ = cdm_context->GetDecryptor();
} else {
// Reinitialization.
decryptor_->DeinitializeDecoder(Decryptor::kVideo);
}
state_ = kPendingDecoderInit;
decryptor_->InitializeVideoDecoder(
config_, BindToCurrentLoop(base::Bind(
&DecryptingVideoDecoder::FinishInitialization, weak_this_)));
}
void DecryptingVideoDecoder::Decode(const scoped_refptr<DecoderBuffer>& buffer,
const DecodeCB& decode_cb) {
DVLOG(3) << "Decode()";
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(state_ == kIdle || state_ == kDecodeFinished || state_ == kError)
<< state_;
DCHECK(!decode_cb.is_null());
CHECK(decode_cb_.is_null()) << "Overlapping decodes are not supported.";
decode_cb_ = BindToCurrentLoop(decode_cb);
if (state_ == kError) {
base::ResetAndReturn(&decode_cb_).Run(DecodeStatus::DECODE_ERROR);
return;
}
// Return empty frames if decoding has finished.
if (state_ == kDecodeFinished) {
base::ResetAndReturn(&decode_cb_).Run(DecodeStatus::OK);
return;
}
pending_buffer_to_decode_ = buffer;
state_ = kPendingDecode;
DecodePendingBuffer();
}
void DecryptingVideoDecoder::Reset(const base::Closure& closure) {
DVLOG(2) << "Reset() - state: " << state_;
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(state_ == kIdle || state_ == kPendingDecode ||
state_ == kWaitingForKey || state_ == kDecodeFinished ||
state_ == kError)
<< state_;
DCHECK(init_cb_.is_null()); // No Reset() during pending initialization.
DCHECK(reset_cb_.is_null());
reset_cb_ = BindToCurrentLoop(closure);
decryptor_->ResetDecoder(Decryptor::kVideo);
// Reset() cannot complete if the decode callback is still pending.
// Defer the resetting process in this case. The |reset_cb_| will be fired
// after the decode callback is fired - see DecryptAndDecodeBuffer() and
// DeliverFrame().
if (state_ == kPendingDecode) {
DCHECK(!decode_cb_.is_null());
return;
}
if (state_ == kWaitingForKey) {
DCHECK(!decode_cb_.is_null());
pending_buffer_to_decode_ = NULL;
base::ResetAndReturn(&decode_cb_).Run(DecodeStatus::ABORTED);
}
DCHECK(decode_cb_.is_null());
DoReset();
}
DecryptingVideoDecoder::~DecryptingVideoDecoder() {
DCHECK(task_runner_->BelongsToCurrentThread());
if (state_ == kUninitialized) return;
if (decryptor_) {
decryptor_->DeinitializeDecoder(Decryptor::kVideo);
decryptor_ = NULL;
}
pending_buffer_to_decode_ = NULL;
if (!init_cb_.is_null()) base::ResetAndReturn(&init_cb_).Run(false);
if (!decode_cb_.is_null())
base::ResetAndReturn(&decode_cb_).Run(DecodeStatus::ABORTED);
if (!reset_cb_.is_null()) base::ResetAndReturn(&reset_cb_).Run();
}
void DecryptingVideoDecoder::FinishInitialization(bool success) {
DVLOG(2) << "FinishInitialization()";
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK_EQ(state_, kPendingDecoderInit) << state_;
DCHECK(!init_cb_.is_null());
DCHECK(reset_cb_.is_null()); // No Reset() before initialization finished.
DCHECK(decode_cb_.is_null()); // No Decode() before initialization finished.
if (!success) {
MEDIA_LOG(DEBUG, media_log_) << GetDisplayName()
<< ": failed to init decoder on decryptor";
base::ResetAndReturn(&init_cb_).Run(false);
decryptor_ = NULL;
state_ = kError;
return;
}
decryptor_->RegisterNewKeyCB(
Decryptor::kVideo, BindToCurrentLoop(base::Bind(
&DecryptingVideoDecoder::OnKeyAdded, weak_this_)));
// Success!
state_ = kIdle;
base::ResetAndReturn(&init_cb_).Run(true);
}
void DecryptingVideoDecoder::DecodePendingBuffer() {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK_EQ(state_, kPendingDecode) << state_;
TRACE_EVENT_ASYNC_BEGIN0(
"media", "DecryptingVideoDecoder::DecodePendingBuffer", ++trace_id_);
int buffer_size = 0;
if (!pending_buffer_to_decode_->end_of_stream()) {
buffer_size = pending_buffer_to_decode_->data_size();
}
decryptor_->DecryptAndDecodeVideo(
pending_buffer_to_decode_,
BindToCurrentLoop(base::Bind(&DecryptingVideoDecoder::DeliverFrame,
weak_this_, buffer_size)));
}
void DecryptingVideoDecoder::DeliverFrame(
int buffer_size, Decryptor::Status status,
const scoped_refptr<VideoFrame>& frame) {
DVLOG(3) << "DeliverFrame() - status: " << status;
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK_EQ(state_, kPendingDecode) << state_;
DCHECK(!decode_cb_.is_null());
DCHECK(pending_buffer_to_decode_.get());
TRACE_EVENT_ASYNC_END2("media", "DecryptingVideoDecoder::DecodePendingBuffer",
trace_id_, "buffer_size", buffer_size, "status",
status);
bool need_to_try_again_if_nokey_is_returned = key_added_while_decode_pending_;
key_added_while_decode_pending_ = false;
scoped_refptr<DecoderBuffer> scoped_pending_buffer_to_decode =
pending_buffer_to_decode_;
pending_buffer_to_decode_ = NULL;
if (!reset_cb_.is_null()) {
base::ResetAndReturn(&decode_cb_).Run(DecodeStatus::ABORTED);
DoReset();
return;
}
DCHECK_EQ(status == Decryptor::kSuccess, frame.get() != NULL);
if (status == Decryptor::kError) {
DVLOG(2) << "DeliverFrame() - kError";
MEDIA_LOG(ERROR, media_log_) << GetDisplayName() << ": decode error";
state_ = kError;
base::ResetAndReturn(&decode_cb_).Run(DecodeStatus::DECODE_ERROR);
return;
}
if (status == Decryptor::kNoKey) {
std::string key_id =
scoped_pending_buffer_to_decode->decrypt_config()->key_id();
std::string missing_key_id = base::HexEncode(key_id.data(), key_id.size());
DVLOG(1) << "DeliverFrame() - no key for key ID " << missing_key_id;
MEDIA_LOG(INFO, media_log_) << GetDisplayName() << ": no key for key ID "
<< missing_key_id;
// Set |pending_buffer_to_decode_| back as we need to try decoding the
// pending buffer again when new key is added to the decryptor.
pending_buffer_to_decode_ = scoped_pending_buffer_to_decode;
if (need_to_try_again_if_nokey_is_returned) {
// The |state_| is still kPendingDecode.
MEDIA_LOG(INFO, media_log_) << GetDisplayName()
<< ": key was added, resuming decode";
DecodePendingBuffer();
return;
}
state_ = kWaitingForKey;
waiting_for_decryption_key_cb_.Run();
return;
}
if (status == Decryptor::kNeedMoreData) {
DVLOG(2) << "DeliverFrame() - kNeedMoreData";
state_ = scoped_pending_buffer_to_decode->end_of_stream() ? kDecodeFinished
: kIdle;
base::ResetAndReturn(&decode_cb_).Run(DecodeStatus::OK);
return;
}
DCHECK_EQ(status, Decryptor::kSuccess);
// No frame returned with kSuccess should be end-of-stream frame.
DCHECK(!frame->metadata()->IsTrue(VideoFrameMetadata::END_OF_STREAM));
output_cb_.Run(frame);
if (scoped_pending_buffer_to_decode->end_of_stream()) {
// Set |pending_buffer_to_decode_| back as we need to keep flushing the
// decryptor.
pending_buffer_to_decode_ = scoped_pending_buffer_to_decode;
DecodePendingBuffer();
return;
}
state_ = kIdle;
base::ResetAndReturn(&decode_cb_).Run(DecodeStatus::OK);
}
void DecryptingVideoDecoder::OnKeyAdded() {
DVLOG(2) << "OnKeyAdded()";
DCHECK(task_runner_->BelongsToCurrentThread());
if (state_ == kPendingDecode) {
key_added_while_decode_pending_ = true;
return;
}
if (state_ == kWaitingForKey) {
MEDIA_LOG(INFO, media_log_) << GetDisplayName()
<< ": key added, resuming decode";
state_ = kPendingDecode;
DecodePendingBuffer();
}
}
void DecryptingVideoDecoder::DoReset() {
DCHECK(init_cb_.is_null());
DCHECK(decode_cb_.is_null());
state_ = kIdle;
base::ResetAndReturn(&reset_cb_).Run();
}
} // namespace media
} // namespace cobalt