blob: f9803ca7384b0d97a5ac1d3a3a5b364dee04b960 [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 "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/message_loop_proxy.h"
#include "media/base/audio_decoder_config.h"
#include "media/base/bind_to_loop.h"
#include "media/base/decoder_buffer.h"
#include "media/base/decryptor.h"
#include "media/base/demuxer_stream.h"
#include "media/base/pipeline.h"
#include "media/base/video_decoder_config.h"
#if defined(__LB_SHELL__) || defined(COBALT)
#include "media/base/shell_media_statistics.h"
#endif
namespace media {
#define BIND_TO_LOOP(function) \
media::BindToLoop(message_loop_, base::Bind(function, this))
static bool IsStreamValidAndEncrypted(
const scoped_refptr<DemuxerStream>& stream) {
return ((stream->type() == DemuxerStream::AUDIO &&
stream->audio_decoder_config().IsValidConfig() &&
stream->audio_decoder_config().is_encrypted()) ||
(stream->type() == DemuxerStream::VIDEO &&
stream->video_decoder_config().IsValidConfig() &&
stream->video_decoder_config().is_encrypted()));
}
DecryptingDemuxerStream::DecryptingDemuxerStream(
const scoped_refptr<base::MessageLoopProxy>& message_loop,
const SetDecryptorReadyCB& set_decryptor_ready_cb)
: message_loop_(message_loop),
state_(kUninitialized),
stream_type_(UNKNOWN),
set_decryptor_ready_cb_(set_decryptor_ready_cb),
decryptor_(NULL),
key_added_while_decrypt_pending_(false) {
}
void DecryptingDemuxerStream::Initialize(
const scoped_refptr<DemuxerStream>& stream,
const PipelineStatusCB& status_cb) {
if (!message_loop_->BelongsToCurrentThread()) {
message_loop_->PostTask(FROM_HERE, base::Bind(
&DecryptingDemuxerStream::DoInitialize, this, stream, status_cb));
return;
}
DoInitialize(stream, status_cb);
}
void DecryptingDemuxerStream::Read(const ReadCB& read_cb) {
DVLOG(3) << "Read()";
DCHECK(message_loop_->BelongsToCurrentThread());
DCHECK_EQ(state_, kIdle) << state_;
DCHECK(!read_cb.is_null());
CHECK(read_cb_.is_null()) << "Overlapping reads are not supported.";
read_cb_ = read_cb;
state_ = kPendingDemuxerRead;
demuxer_stream_->Read(
base::Bind(&DecryptingDemuxerStream::DecryptBuffer, this));
}
void DecryptingDemuxerStream::Reset(const base::Closure& closure) {
if (!message_loop_->BelongsToCurrentThread()) {
message_loop_->PostTask(FROM_HERE, base::Bind(
&DecryptingDemuxerStream::Reset, this, closure));
return;
}
DVLOG(2) << "Reset() - state: " << state_;
DCHECK(state_ != kUninitialized && state_ != kDecryptorRequested) << state_;
DCHECK(init_cb_.is_null()); // No Reset() during pending initialization.
DCHECK(reset_cb_.is_null());
reset_cb_ = BindToCurrentLoop(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 DoDecryptBuffer() and
// DoDeliverBuffer().
if (state_ == kPendingDemuxerRead || state_ == kPendingDecrypt) {
DCHECK(!read_cb_.is_null());
return;
}
if (state_ == kWaitingForKey) {
DCHECK(!read_cb_.is_null());
pending_buffer_to_decrypt_ = NULL;
base::ResetAndReturn(&read_cb_).Run(kAborted, NULL);
}
DCHECK(read_cb_.is_null());
DoReset();
}
const AudioDecoderConfig& DecryptingDemuxerStream::audio_decoder_config() {
DCHECK(state_ != kUninitialized && state_ != kDecryptorRequested) << state_;
CHECK_EQ(stream_type_, AUDIO);
return *audio_config_;
}
const VideoDecoderConfig& DecryptingDemuxerStream::video_decoder_config() {
DCHECK(state_ != kUninitialized && state_ != kDecryptorRequested) << state_;
CHECK_EQ(stream_type_, VIDEO);
return *video_config_;
}
DemuxerStream::Type DecryptingDemuxerStream::type() {
DCHECK(state_ != kUninitialized && state_ != kDecryptorRequested) << state_;
return stream_type_;
}
void DecryptingDemuxerStream::EnableBitstreamConverter() {
demuxer_stream_->EnableBitstreamConverter();
}
DecryptingDemuxerStream::~DecryptingDemuxerStream() {}
void DecryptingDemuxerStream::DoInitialize(
const scoped_refptr<DemuxerStream>& stream,
const PipelineStatusCB& status_cb) {
DVLOG(2) << "DoInitialize()";
DCHECK(message_loop_->BelongsToCurrentThread());
DCHECK_EQ(state_, kUninitialized) << state_;
DCHECK(!demuxer_stream_);
demuxer_stream_ = stream;
stream_type_ = stream->type();
init_cb_ = status_cb;
SetDecoderConfig(stream);
state_ = kDecryptorRequested;
set_decryptor_ready_cb_.Run(
BIND_TO_LOOP(&DecryptingDemuxerStream::SetDecryptor));
}
void DecryptingDemuxerStream::SetDecryptor(Decryptor* decryptor) {
DVLOG(2) << "SetDecryptor()";
DCHECK(message_loop_->BelongsToCurrentThread());
DCHECK_EQ(state_, kDecryptorRequested) << state_;
DCHECK(!init_cb_.is_null());
DCHECK(!set_decryptor_ready_cb_.is_null());
set_decryptor_ready_cb_.Reset();
if (!decryptor) {
base::ResetAndReturn(&init_cb_).Run(DECODER_ERROR_NOT_SUPPORTED);
state_ = kUninitialized;
return;
}
decryptor_ = decryptor;
decryptor_->RegisterKeyAddedCB(
GetDecryptorStreamType(),
BIND_TO_LOOP(&DecryptingDemuxerStream::OnKeyAdded));
state_ = kIdle;
base::ResetAndReturn(&init_cb_).Run(PIPELINE_OK);
}
void DecryptingDemuxerStream::DecryptBuffer(
DemuxerStream::Status status,
const scoped_refptr<DecoderBuffer>& buffer) {
// In theory, we don't need to force post the task here, because we do a
// force task post in DeliverBuffer(). Therefore, even if
// demuxer_stream_->Read() execute the read callback on the same execution
// stack we are still fine. But it looks like a force post task makes the
// logic more understandable and manageable, so why not?
message_loop_->PostTask(FROM_HERE, base::Bind(
&DecryptingDemuxerStream::DoDecryptBuffer, this, status, buffer));
}
void DecryptingDemuxerStream::DoDecryptBuffer(
DemuxerStream::Status status,
const scoped_refptr<DecoderBuffer>& buffer) {
DVLOG(3) << "DoDecryptBuffer()";
DCHECK(message_loop_->BelongsToCurrentThread());
DCHECK_EQ(state_, kPendingDemuxerRead) << state_;
DCHECK(!read_cb_.is_null());
DCHECK_EQ(buffer != NULL, status == kOk) << status;
if (!reset_cb_.is_null()) {
base::ResetAndReturn(&read_cb_).Run(kAborted, NULL);
DoReset();
return;
}
if (status == kAborted) {
DVLOG(2) << "DoDecryptBuffer() - kAborted.";
state_ = kIdle;
base::ResetAndReturn(&read_cb_).Run(kAborted, NULL);
return;
}
if (status == kConfigChanged) {
DVLOG(2) << "DoDecryptBuffer() - kConfigChanged.";
DCHECK_EQ(demuxer_stream_->type(), stream_type_);
// Update the decoder config, which the decoder will use when it is notified
// of kConfigChanged.
SetDecoderConfig(demuxer_stream_);
state_ = kIdle;
base::ResetAndReturn(&read_cb_).Run(kConfigChanged, NULL);
return;
}
if (buffer->IsEndOfStream()) {
DVLOG(2) << "DoDecryptBuffer() - EOS buffer.";
state_ = kIdle;
base::ResetAndReturn(&read_cb_).Run(status, buffer);
return;
}
pending_buffer_to_decrypt_ = buffer;
state_ = kPendingDecrypt;
DecryptPendingBuffer();
}
void DecryptingDemuxerStream::DecryptPendingBuffer() {
DCHECK(message_loop_->BelongsToCurrentThread());
DCHECK_EQ(state_, kPendingDecrypt) << state_;
#if defined(__LB_SHELL__) || defined(COBALT)
decrypting_start_ = base::Time::Now();
#endif // defined(__LB_SHELL__) || defined(COBALT)
decryptor_->Decrypt(
GetDecryptorStreamType(),
pending_buffer_to_decrypt_,
base::Bind(&DecryptingDemuxerStream::DeliverBuffer, this));
}
void DecryptingDemuxerStream::DeliverBuffer(
Decryptor::Status status,
const scoped_refptr<DecoderBuffer>& decrypted_buffer) {
#if defined(__LB_SHELL__) || defined(COBALT)
UPDATE_MEDIA_STATISTICS(STAT_TYPE_DECRYPT,
(base::Time::Now() - decrypting_start_).ToInternalValue());
#endif // defined(__LB_SHELL__) || defined(COBALT)
// We need to force task post here because the DecryptCB can be executed
// synchronously in Reset(). Instead of using more complicated logic in
// those function to fix it, why not force task post here to make everything
// simple and clear?
message_loop_->PostTask(FROM_HERE, base::Bind(
&DecryptingDemuxerStream::DoDeliverBuffer, this,
status, decrypted_buffer));
}
void DecryptingDemuxerStream::DoDeliverBuffer(
Decryptor::Status status,
const scoped_refptr<DecoderBuffer>& decrypted_buffer) {
DVLOG(3) << "DoDeliverBuffer() - status: " << status;
DCHECK(message_loop_->BelongsToCurrentThread());
DCHECK_EQ(state_, kPendingDecrypt) << state_;
DCHECK_NE(status, Decryptor::kNeedMoreData);
DCHECK(!read_cb_.is_null());
DCHECK(pending_buffer_to_decrypt_);
bool need_to_try_again_if_nokey = key_added_while_decrypt_pending_;
key_added_while_decrypt_pending_ = false;
if (!reset_cb_.is_null()) {
pending_buffer_to_decrypt_ = NULL;
base::ResetAndReturn(&read_cb_).Run(kAborted, NULL);
DoReset();
return;
}
DCHECK_EQ(status == Decryptor::kSuccess, decrypted_buffer.get() != NULL);
if (status == Decryptor::kError) {
DVLOG(2) << "DoDeliverBuffer() - kError";
pending_buffer_to_decrypt_ = NULL;
state_ = kIdle;
base::ResetAndReturn(&read_cb_).Run(kAborted, NULL);
return;
}
if (status == Decryptor::kNoKey) {
DVLOG(2) << "DoDeliverBuffer() - kNoKey";
if (need_to_try_again_if_nokey) {
// The |state_| is still kPendingDecrypt.
DecryptPendingBuffer();
return;
}
state_ = kWaitingForKey;
return;
}
DCHECK_EQ(status, Decryptor::kSuccess);
pending_buffer_to_decrypt_ = NULL;
state_ = kIdle;
base::ResetAndReturn(&read_cb_).Run(kOk, decrypted_buffer);
}
void DecryptingDemuxerStream::OnKeyAdded() {
DCHECK(message_loop_->BelongsToCurrentThread());
if (state_ == kPendingDecrypt) {
key_added_while_decrypt_pending_ = true;
return;
}
if (state_ == kWaitingForKey) {
state_ = kPendingDecrypt;
DecryptPendingBuffer();
}
}
void DecryptingDemuxerStream::DoReset() {
DCHECK(init_cb_.is_null());
DCHECK(read_cb_.is_null());
state_ = kIdle;
base::ResetAndReturn(&reset_cb_).Run();
}
Decryptor::StreamType DecryptingDemuxerStream::GetDecryptorStreamType() const {
DCHECK(stream_type_ == AUDIO || stream_type_ == VIDEO);
return stream_type_ == AUDIO ? Decryptor::kAudio : Decryptor::kVideo;
}
void DecryptingDemuxerStream::SetDecoderConfig(
const scoped_refptr<DemuxerStream>& stream) {
// The decoder selector or upstream demuxer make sure the stream is valid and
// potentially encrypted.
DCHECK(IsStreamValidAndEncrypted(stream));
switch (stream_type_) {
case AUDIO: {
const AudioDecoderConfig& input_audio_config =
stream->audio_decoder_config();
audio_config_.reset(new AudioDecoderConfig());
audio_config_->Initialize(input_audio_config.codec(),
input_audio_config.bits_per_channel(),
input_audio_config.channel_layout(),
input_audio_config.samples_per_second(),
input_audio_config.extra_data(),
input_audio_config.extra_data_size(),
false, // Output audio is not encrypted.
false);
break;
}
case VIDEO: {
const VideoDecoderConfig& input_video_config =
stream->video_decoder_config();
video_config_.reset(new VideoDecoderConfig());
video_config_->Initialize(
input_video_config.codec(), input_video_config.profile(),
input_video_config.format(), input_video_config.color_space(),
input_video_config.coded_size(), input_video_config.visible_rect(),
input_video_config.natural_size(), input_video_config.extra_data(),
input_video_config.extra_data_size(),
false, // Output video is not encrypted.
false);
video_config_->set_webm_color_metadata(
input_video_config.webm_color_metadata());
break;
}
default:
NOTREACHED();
return;
}
}
#if defined(__LB_SHELL__) || defined(COBALT)
bool DecryptingDemuxerStream::StreamWasEncrypted() const {
DCHECK(demuxer_stream_->StreamWasEncrypted());
return true;
}
#endif
} // namespace media