| Index: media/filters/decrypting_video_decoder.cc
|
| diff --git a/media/filters/decrypting_video_decoder.cc b/media/filters/decrypting_video_decoder.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..0a4d7a543a71c5afee47fb0a27f1d291a3818a03
|
| --- /dev/null
|
| +++ b/media/filters/decrypting_video_decoder.cc
|
| @@ -0,0 +1,310 @@
|
| +// 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_video_decoder.h"
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/callback_helpers.h"
|
| +#include "base/location.h"
|
| +#include "base/message_loop_proxy.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"
|
| +#include "media/base/video_frame.h"
|
| +
|
| +namespace media {
|
| +
|
| +DecryptingVideoDecoder::DecryptingVideoDecoder(
|
| + const MessageLoopFactoryCB& message_loop_factory_cb,
|
| + Decryptor* decryptor)
|
| + : message_loop_factory_cb_(message_loop_factory_cb),
|
| + state_(kUninitialized),
|
| + decryptor_(decryptor) {
|
| +}
|
| +
|
| +void DecryptingVideoDecoder::Initialize(
|
| + const scoped_refptr<DemuxerStream>& stream,
|
| + const PipelineStatusCB& status_cb,
|
| + const StatisticsCB& statistics_cb) {
|
| + DCHECK(!message_loop_);
|
| + message_loop_ = base::ResetAndReturn(&message_loop_factory_cb_).Run();
|
| + message_loop_->PostTask(FROM_HERE, base::Bind(
|
| + &DecryptingVideoDecoder::DoInitialize, this,
|
| + stream, status_cb, statistics_cb));
|
| +}
|
| +
|
| +void DecryptingVideoDecoder::Read(const ReadCB& read_cb) {
|
| + // Complete operation asynchronously on different stack of execution as per
|
| + // the API contract of VideoDecoder::Read()
|
| + message_loop_->PostTask(FROM_HERE, base::Bind(
|
| + &DecryptingVideoDecoder::DoRead, this, read_cb));
|
| +}
|
| +
|
| +void DecryptingVideoDecoder::Reset(const base::Closure& closure) {
|
| + if (!message_loop_->BelongsToCurrentThread()) {
|
| + message_loop_->PostTask(FROM_HERE, base::Bind(
|
| + &DecryptingVideoDecoder::Reset, this, closure));
|
| + return;
|
| + }
|
| +
|
| + DCHECK_NE(state_, kUninitialized);
|
| + DCHECK(init_cb_.is_null()); // No Reset() during pending initialization.
|
| + DCHECK(stop_cb_.is_null()); // No Reset() during pending Stop().
|
| + DCHECK(reset_cb_.is_null());
|
| + reset_cb_ = closure;
|
| +
|
| + decryptor_->CancelDecryptAndDecodeVideo();
|
| +
|
| + // 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 DoDecryptAndDecodeBuffer() and
|
| + // DoDeliverFrame().
|
| + if (!read_cb_.is_null())
|
| + return;
|
| +
|
| + DoReset();
|
| +}
|
| +
|
| +void DecryptingVideoDecoder::Stop(const base::Closure& closure) {
|
| + if (!message_loop_->BelongsToCurrentThread()) {
|
| + message_loop_->PostTask(FROM_HERE, base::Bind(
|
| + &DecryptingVideoDecoder::Stop, this, closure));
|
| + return;
|
| + }
|
| +
|
| + DCHECK(stop_cb_.is_null());
|
| + stop_cb_ = closure;
|
| +
|
| + decryptor_->StopVideoDecoder();
|
| +
|
| + // Stop() cannot complete if the init or read callback is still pending.
|
| + // Defer the stopping process in these cases. The |stop_cb_| will be fired
|
| + // after the init or read callback is fired - see DoFinishInitialization(),
|
| + // DoDecryptAndDecodeBuffer() and DoDeliverFrame().
|
| + if (!init_cb_.is_null() || !read_cb_.is_null())
|
| + return;
|
| +
|
| + DoStop();
|
| +}
|
| +
|
| +DecryptingVideoDecoder::~DecryptingVideoDecoder() {
|
| + DCHECK_EQ(state_, kUninitialized);
|
| +}
|
| +
|
| +void DecryptingVideoDecoder::DoInitialize(
|
| + const scoped_refptr<DemuxerStream>& stream,
|
| + const PipelineStatusCB& status_cb,
|
| + const StatisticsCB& statistics_cb) {
|
| + DCHECK(message_loop_->BelongsToCurrentThread());
|
| + DCHECK(stream);
|
| + DCHECK_EQ(state_, kUninitialized);
|
| +
|
| + const VideoDecoderConfig& config = stream->video_decoder_config();
|
| + if (!config.IsValidConfig()) {
|
| + DLOG(ERROR) << "Invalid video stream config: "
|
| + << config.AsHumanReadableString();
|
| + status_cb.Run(PIPELINE_ERROR_DECODE);
|
| + return;
|
| + }
|
| +
|
| + // DecryptingVideoDecoder only accepts potentially encrypted stream.
|
| + if (!config.is_encrypted()) {
|
| + status_cb.Run(DECODER_ERROR_NOT_SUPPORTED);
|
| + return;
|
| + }
|
| +
|
| + DCHECK(!demuxer_stream_);
|
| + demuxer_stream_ = stream;
|
| + statistics_cb_ = statistics_cb;
|
| +
|
| + init_cb_ = status_cb;
|
| + decryptor_->InitializeVideoDecoder(config, base::Bind(
|
| + &DecryptingVideoDecoder::FinishInitialization, this));
|
| +}
|
| +
|
| +void DecryptingVideoDecoder::FinishInitialization(bool success) {
|
| + message_loop_->PostTask(FROM_HERE, base::Bind(
|
| + &DecryptingVideoDecoder::DoFinishInitialization, this, success));
|
| +}
|
| +
|
| +void DecryptingVideoDecoder::DoFinishInitialization(bool success) {
|
| + DCHECK(message_loop_->BelongsToCurrentThread());
|
| + DCHECK_EQ(state_, kUninitialized);
|
| + DCHECK(!init_cb_.is_null());
|
| + DCHECK(reset_cb_.is_null());
|
| + DCHECK(read_cb_.is_null());
|
| +
|
| + if (!stop_cb_.is_null()) {
|
| + base::ResetAndReturn(&init_cb_).Run(DECODER_ERROR_NOT_SUPPORTED);
|
| + DoStop();
|
| + return;
|
| + }
|
| +
|
| + if (!success) {
|
| + base::ResetAndReturn(&init_cb_).Run(DECODER_ERROR_NOT_SUPPORTED);
|
| + return;
|
| + }
|
| +
|
| + // Success!
|
| + state_ = kNormal;
|
| + base::ResetAndReturn(&init_cb_).Run(PIPELINE_OK);
|
| +}
|
| +
|
| +void DecryptingVideoDecoder::DoRead(const ReadCB& read_cb) {
|
| + DCHECK(message_loop_->BelongsToCurrentThread());
|
| + DCHECK(!read_cb.is_null());
|
| + CHECK_NE(state_, kUninitialized);
|
| + CHECK(read_cb_.is_null()) << "Overlapping decodes are not supported.";
|
| +
|
| + // Return empty frames if decoding has finished.
|
| + if (state_ == kDecodeFinished) {
|
| + read_cb.Run(kOk, VideoFrame::CreateEmptyFrame());
|
| + return;
|
| + }
|
| +
|
| + read_cb_ = read_cb;
|
| + ReadFromDemuxerStream();
|
| +}
|
| +
|
| +void DecryptingVideoDecoder::ReadFromDemuxerStream() {
|
| + DCHECK(message_loop_->BelongsToCurrentThread());
|
| + DCHECK_EQ(state_, kNormal);
|
| + DCHECK(!read_cb_.is_null());
|
| +
|
| + demuxer_stream_->Read(
|
| + base::Bind(&DecryptingVideoDecoder::DecryptAndDecodeBuffer, this));
|
| +}
|
| +
|
| +void DecryptingVideoDecoder::DecryptAndDecodeBuffer(
|
| + 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 DeliverFrame(). 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(
|
| + &DecryptingVideoDecoder::DoDecryptAndDecodeBuffer, this,
|
| + status, buffer));
|
| +}
|
| +
|
| +void DecryptingVideoDecoder::DoDecryptAndDecodeBuffer(
|
| + DemuxerStream::Status status,
|
| + const scoped_refptr<DecoderBuffer>& buffer) {
|
| + DCHECK(message_loop_->BelongsToCurrentThread());
|
| + DCHECK_EQ(state_, kNormal);
|
| + DCHECK(!read_cb_.is_null());
|
| + DCHECK_EQ(buffer != NULL, status == DemuxerStream::kOk) << status;
|
| +
|
| + if (!stop_cb_.is_null()) {
|
| + base::ResetAndReturn(&read_cb_).Run(kOk, NULL);
|
| + if (!reset_cb_.is_null())
|
| + DoReset();
|
| +
|
| + DoStop();
|
| + return;
|
| + }
|
| +
|
| + if (!reset_cb_.is_null()) {
|
| + base::ResetAndReturn(&read_cb_).Run(kOk, NULL);
|
| + DoReset();
|
| + return;
|
| + }
|
| +
|
| + if (status == DemuxerStream::kAborted) {
|
| + base::ResetAndReturn(&read_cb_).Run(kOk, NULL);
|
| + return;
|
| + }
|
| +
|
| + if (status == DemuxerStream::kConfigChanged) {
|
| + // TODO(xhwang): Add config change support.
|
| + base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
|
| + return;
|
| + }
|
| +
|
| + DCHECK_EQ(status, DemuxerStream::kOk);
|
| + decryptor_->DecryptAndDecodeVideo(
|
| + buffer, base::Bind(&DecryptingVideoDecoder::DeliverFrame, this,
|
| + buffer->GetDataSize()));
|
| +}
|
| +
|
| +void DecryptingVideoDecoder::DeliverFrame(
|
| + int buffer_size,
|
| + Decryptor::Status status,
|
| + const scoped_refptr<VideoFrame>& frame) {
|
| + // We need to force task post here because the VideoDecodeCB can be executed
|
| + // synchronously in Reset()/Stop(). 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(
|
| + &DecryptingVideoDecoder::DoDeliverFrame, this,
|
| + buffer_size, status, frame));
|
| +}
|
| +
|
| +void DecryptingVideoDecoder::DoDeliverFrame(
|
| + int buffer_size,
|
| + Decryptor::Status status,
|
| + const scoped_refptr<VideoFrame>& frame) {
|
| + DCHECK(message_loop_->BelongsToCurrentThread());
|
| + DCHECK_EQ(state_, kNormal);
|
| + DCHECK(!read_cb_.is_null());
|
| +
|
| + if (!stop_cb_.is_null()) {
|
| + base::ResetAndReturn(&read_cb_).Run(kOk, NULL);
|
| + if (!reset_cb_.is_null())
|
| + DoReset();
|
| +
|
| + DoStop();
|
| + return;
|
| + }
|
| +
|
| + if (!reset_cb_.is_null()) {
|
| + base::ResetAndReturn(&read_cb_).Run(kOk, NULL);
|
| + DoReset();
|
| + return;
|
| + }
|
| +
|
| + if (status == Decryptor::kNoKey || status == Decryptor::kError) {
|
| + DCHECK(!frame);
|
| + state_ = kDecodeFinished;
|
| + base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
|
| + return;
|
| + }
|
| +
|
| + // The buffer has been accepted by the decoder, let's report statistics.
|
| + if (buffer_size) {
|
| + PipelineStatistics statistics;
|
| + statistics.video_bytes_decoded = buffer_size;
|
| + statistics_cb_.Run(statistics);
|
| + }
|
| +
|
| + if (status == Decryptor::kNeedMoreData) {
|
| + DCHECK(!frame);
|
| + ReadFromDemuxerStream();
|
| + return;
|
| + }
|
| +
|
| + DCHECK_EQ(status, Decryptor::kSuccess);
|
| + if (frame->IsEndOfStream())
|
| + state_ = kDecodeFinished;
|
| +
|
| + base::ResetAndReturn(&read_cb_).Run(kOk, frame);
|
| +}
|
| +
|
| +void DecryptingVideoDecoder::DoReset() {
|
| + DCHECK(read_cb_.is_null());
|
| + DCHECK_NE(state_, kUninitialized);
|
| + state_ = kNormal;
|
| + base::ResetAndReturn(&reset_cb_).Run();
|
| +}
|
| +
|
| +void DecryptingVideoDecoder::DoStop() {
|
| + DCHECK(read_cb_.is_null());
|
| + state_ = kUninitialized;
|
| + base::ResetAndReturn(&stop_cb_).Run();
|
| +}
|
| +
|
| +} // namespace media
|
|
|