summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorxhwang@chromium.org <xhwang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-11-15 03:57:58 +0000
committerxhwang@chromium.org <xhwang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-11-15 03:57:58 +0000
commit2b454ff838c56a72d7e705bf5c31c3291a025df0 (patch)
tree9390f56eb1f74bab1dafe9c7f6ceadfdd4dcc83f /media
parent357b2331eee05e9f5e13c832c3ef5814bbf0d0ee (diff)
downloadchromium_src-2b454ff838c56a72d7e705bf5c31c3291a025df0.zip
chromium_src-2b454ff838c56a72d7e705bf5c31c3291a025df0.tar.gz
chromium_src-2b454ff838c56a72d7e705bf5c31c3291a025df0.tar.bz2
Add DecryptingDemuxerStream.
DecryptingDemuxerStream transforms an encrypted demuxer stream into a clear demuxer stream with the same stream type (audio/video) and codec config. It may be blocking because it relies on a Decryptor for decryption. Thus other than implementing the DemuxerStream interface, it also exposes a Reset() methods so that caller can cancel the decryption operation. BUG=123421,141786 TEST=media_unittests pass Review URL: https://chromiumcodereview.appspot.com/11342031 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@167852 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r--media/base/decryptor.h1
-rw-r--r--media/filters/decrypting_audio_decoder.cc10
-rw-r--r--media/filters/decrypting_audio_decoder.h11
-rw-r--r--media/filters/decrypting_audio_decoder_unittest.cc22
-rw-r--r--media/filters/decrypting_demuxer_stream.cc350
-rw-r--r--media/filters/decrypting_demuxer_stream.h148
-rw-r--r--media/filters/decrypting_demuxer_stream_unittest.cc459
-rw-r--r--media/filters/decrypting_video_decoder.cc9
-rw-r--r--media/filters/decrypting_video_decoder.h11
-rw-r--r--media/filters/decrypting_video_decoder_unittest.cc5
-rw-r--r--media/media.gyp3
11 files changed, 993 insertions, 36 deletions
diff --git a/media/base/decryptor.h b/media/base/decryptor.h
index 2efd03b..87c275d 100644
--- a/media/base/decryptor.h
+++ b/media/base/decryptor.h
@@ -56,6 +56,7 @@ class MEDIA_EXPORT Decryptor {
kError // Key is available but an error occurred during decryption.
};
+ // TODO(xhwang): Unify this with DemuxerStream::Type.
enum StreamType {
kAudio,
kVideo
diff --git a/media/filters/decrypting_audio_decoder.cc b/media/filters/decrypting_audio_decoder.cc
index f5adcb9..d53498e 100644
--- a/media/filters/decrypting_audio_decoder.cc
+++ b/media/filters/decrypting_audio_decoder.cc
@@ -9,8 +9,8 @@
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/location.h"
-#include "base/message_loop_proxy.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/buffers.h"
@@ -41,7 +41,7 @@ DecryptingAudioDecoder::DecryptingAudioDecoder(
state_(kUninitialized),
request_decryptor_notification_cb_(request_decryptor_notification_cb),
decryptor_(NULL),
- key_added_while_pending_decode_(false),
+ key_added_while_decode_pending_(false),
bits_per_channel_(0),
channel_layout_(CHANNEL_LAYOUT_NONE),
samples_per_second_(0),
@@ -328,8 +328,8 @@ void DecryptingAudioDecoder::DoDeliverFrame(
DCHECK(pending_buffer_to_decode_);
DCHECK(queued_audio_frames_.empty());
- bool need_to_try_again_if_nokey_is_returned = key_added_while_pending_decode_;
- key_added_while_pending_decode_ = false;
+ 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_;
@@ -400,7 +400,7 @@ void DecryptingAudioDecoder::OnKeyAdded() {
DCHECK(message_loop_->BelongsToCurrentThread());
if (state_ == kPendingDecode) {
- key_added_while_pending_decode_ = true;
+ key_added_while_decode_pending_ = true;
return;
}
diff --git a/media/filters/decrypting_audio_decoder.h b/media/filters/decrypting_audio_decoder.h
index 533b9a2..4fb08c8 100644
--- a/media/filters/decrypting_audio_decoder.h
+++ b/media/filters/decrypting_audio_decoder.h
@@ -68,7 +68,7 @@ class MEDIA_EXPORT DecryptingAudioDecoder : public AudioDecoder {
// TODO(xhwang): Add a ASCII state diagram in this file after this class
// stabilizes.
// TODO(xhwang): Update this diagram for DecryptingAudioDecoder.
- enum DecoderState {
+ enum State {
kUninitialized = 0,
kDecryptorRequested,
kPendingDecoderInit,
@@ -116,8 +116,8 @@ class MEDIA_EXPORT DecryptingAudioDecoder : public AudioDecoder {
Decryptor::Status status,
const Decryptor::AudioBuffers& frames);
- // Callback for the |decryptor_| to notify the DecryptingAudioDecoder that
- // a new key has been added.
+ // Callback for the |decryptor_| to notify this object that a new key has been
+ // added.
void OnKeyAdded();
// Resets decoder and calls |reset_cb_|.
@@ -135,8 +135,7 @@ class MEDIA_EXPORT DecryptingAudioDecoder : public AudioDecoder {
scoped_refptr<base::MessageLoopProxy> message_loop_;
- // Current state of the DecryptingAudioDecoder.
- DecoderState state_;
+ State state_;
PipelineStatusCB init_cb_;
StatisticsCB statistics_cb_;
@@ -159,7 +158,7 @@ class MEDIA_EXPORT DecryptingAudioDecoder : public AudioDecoder {
// If this variable is true and kNoKey is returned then we need to try
// decrypting/decoding again in case the newly added key is the correct
// decryption key.
- bool key_added_while_pending_decode_;
+ bool key_added_while_decode_pending_;
Decryptor::AudioBuffers queued_audio_frames_;
diff --git a/media/filters/decrypting_audio_decoder_unittest.cc b/media/filters/decrypting_audio_decoder_unittest.cc
index a191308..cbb1a5f 100644
--- a/media/filters/decrypting_audio_decoder_unittest.cc
+++ b/media/filters/decrypting_audio_decoder_unittest.cc
@@ -21,7 +21,6 @@
using ::testing::_;
using ::testing::AtMost;
-using ::testing::Invoke;
using ::testing::IsNull;
using ::testing::ReturnRef;
using ::testing::SaveArg;
@@ -72,7 +71,7 @@ MATCHER(IsEndOfStream, "end of stream") {
class DecryptingAudioDecoderTest : public testing::Test {
public:
DecryptingAudioDecoderTest()
- : decoder_(new StrictMock<DecryptingAudioDecoder>(
+ : decoder_(new DecryptingAudioDecoder(
base::Bind(&Identity<scoped_refptr<base::MessageLoopProxy> >,
message_loop_.message_loop_proxy()),
base::Bind(
@@ -84,10 +83,10 @@ class DecryptingAudioDecoderTest : public testing::Test {
decoded_frame_(NULL),
end_of_stream_frame_(new DataBuffer(0)),
decoded_frame_list_() {
- // TODO(xhwang): Fix this after DataBuffer(data, size) is public.
- scoped_refptr<DataBuffer> buffer = new DataBuffer(kFakeAudioFrameSize);
- buffer->SetDataSize(kFakeAudioFrameSize);
- decoded_frame_ = buffer;
+ scoped_refptr<DataBuffer> data_buffer = new DataBuffer(kFakeAudioFrameSize);
+ data_buffer->SetDataSize(kFakeAudioFrameSize);
+ // |decoded_frame_| contains random data.
+ decoded_frame_ = data_buffer;
decoded_frame_list_.push_back(decoded_frame_);
}
@@ -111,10 +110,10 @@ class DecryptingAudioDecoderTest : public testing::Test {
EXPECT_CALL(*decryptor_, RegisterKeyAddedCB(Decryptor::kAudio, _))
.WillOnce(SaveArg<1>(&key_added_cb_));
- config_.Initialize(kCodecVorbis, 16, CHANNEL_LAYOUT_STEREO, 44100,
- NULL, 0, true, true);
+ AudioDecoderConfig config(kCodecVorbis, 16, CHANNEL_LAYOUT_STEREO, 44100,
+ NULL, 0, true);
+ InitializeAndExpectStatus(config, PIPELINE_OK);
- InitializeAndExpectStatus(config_, PIPELINE_OK);
EXPECT_EQ(16, decoder_->bits_per_channel());
EXPECT_EQ(CHANNEL_LAYOUT_STEREO, decoder_->channel_layout());
EXPECT_EQ(44100, decoder_->samples_per_second());
@@ -190,7 +189,7 @@ class DecryptingAudioDecoderTest : public testing::Test {
void EnterWaitingForKeyState() {
EXPECT_CALL(*demuxer_, Read(_))
.WillRepeatedly(ReturnBuffer(encrypted_buffer_));
- EXPECT_CALL(*decryptor_, DecryptAndDecodeAudio(_, _))
+ EXPECT_CALL(*decryptor_, DecryptAndDecodeAudio(encrypted_buffer_, _))
.WillRepeatedly(RunCallback<1>(Decryptor::kNoKey,
Decryptor::AudioBuffers()));
decoder_->Read(base::Bind(&DecryptingAudioDecoderTest::FrameReady,
@@ -221,11 +220,10 @@ class DecryptingAudioDecoderTest : public testing::Test {
const scoped_refptr<Buffer>&));
MessageLoop message_loop_;
- scoped_refptr<StrictMock<DecryptingAudioDecoder> > decoder_;
+ scoped_refptr<DecryptingAudioDecoder> decoder_;
scoped_ptr<StrictMock<MockDecryptor> > decryptor_;
scoped_refptr<StrictMock<MockDemuxerStream> > demuxer_;
MockStatisticsCB statistics_cb_;
- AudioDecoderConfig config_;
DemuxerStream::ReadCB pending_demuxer_read_cb_;
Decryptor::DecoderInitCB pending_init_cb_;
diff --git a/media/filters/decrypting_demuxer_stream.cc b/media/filters/decrypting_demuxer_stream.cc
new file mode 100644
index 0000000..02ee0c5
--- /dev/null
+++ b/media/filters/decrypting_demuxer_stream.cc
@@ -0,0 +1,350 @@
+// 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/video_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"
+
+namespace media {
+
+#define BIND_TO_LOOP(function) \
+ media::BindToLoop(message_loop_, base::Bind(function, this))
+
+DecryptingDemuxerStream::DecryptingDemuxerStream(
+ const MessageLoopFactoryCB& message_loop_factory_cb,
+ const RequestDecryptorNotificationCB& request_decryptor_notification_cb)
+ : message_loop_factory_cb_(message_loop_factory_cb),
+ state_(kUninitialized),
+ stream_type_(UNKNOWN),
+ request_decryptor_notification_cb_(request_decryptor_notification_cb),
+ decryptor_(NULL),
+ key_added_while_decrypt_pending_(false) {
+}
+
+void DecryptingDemuxerStream::Initialize(
+ const scoped_refptr<DemuxerStream>& stream,
+ const PipelineStatusCB& status_cb) {
+ DCHECK(!message_loop_);
+ message_loop_ = base::ResetAndReturn(&message_loop_factory_cb_).Run();
+ message_loop_->PostTask(FROM_HERE, base::Bind(
+ &DecryptingDemuxerStream::DoInitialize, this,
+ 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_ = 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_;
+
+ // Only valid potentially encrypted audio or video stream is accepted.
+ if (!((stream->type() == AUDIO &&
+ stream->audio_decoder_config().IsValidConfig() &&
+ stream->audio_decoder_config().is_encrypted()) ||
+ (stream->type() == VIDEO &&
+ stream->video_decoder_config().IsValidConfig() &&
+ stream->video_decoder_config().is_encrypted()))) {
+ status_cb.Run(DEMUXER_ERROR_NO_SUPPORTED_STREAMS);
+ return;
+ }
+
+ DCHECK(!demuxer_stream_);
+ demuxer_stream_ = stream;
+ stream_type_ = stream->type();
+ init_cb_ = status_cb;
+
+ state_ = kDecryptorRequested;
+ request_decryptor_notification_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(!request_decryptor_notification_cb_.is_null());
+
+ request_decryptor_notification_cb_.Reset();
+ decryptor_ = decryptor;
+
+ switch (stream_type_) {
+ case AUDIO: {
+ const AudioDecoderConfig& input_audio_config =
+ demuxer_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 =
+ demuxer_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.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);
+ break;
+ }
+
+ default:
+ NOTREACHED();
+ return;
+ }
+
+ 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.";
+ state_ = kIdle;
+ // TODO(xhwang): Support kConfigChanged!
+ base::ResetAndReturn(&read_cb_).Run(kAborted, 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_;
+ decryptor_->Decrypt(
+ GetDecryptorStreamType(),
+ pending_buffer_to_decrypt_,
+ base::Bind(&DecryptingDemuxerStream::DeliverBuffer, this));
+}
+
+void DecryptingDemuxerStream::DeliverBuffer(
+ Decryptor::Status status,
+ const scoped_refptr<DecoderBuffer>& decrypted_buffer) {
+ // 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;
+}
+
+} // namespace media
diff --git a/media/filters/decrypting_demuxer_stream.h b/media/filters/decrypting_demuxer_stream.h
new file mode 100644
index 0000000..28174be
--- /dev/null
+++ b/media/filters/decrypting_demuxer_stream.h
@@ -0,0 +1,148 @@
+// 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.
+
+#ifndef MEDIA_FILTERS_DECRYPTING_DEMUXER_STREAM_H_
+#define MEDIA_FILTERS_DECRYPTING_DEMUXER_STREAM_H_
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "media/base/decryptor.h"
+#include "media/base/demuxer_stream.h"
+
+namespace base {
+class MessageLoopProxy;
+}
+
+namespace media {
+
+class DecoderBuffer;
+
+// Decryptor-based DemuxerStream implementation that converts a potentially
+// encrypted demuxer stream to a clear demuxer stream.
+// All public APIs and callbacks are trampolined to the |message_loop_| so
+// that no locks are required for thread safety.
+class MEDIA_EXPORT DecryptingDemuxerStream : public DemuxerStream {
+ public:
+ // Callback to get a message loop.
+ typedef base::Callback<
+ scoped_refptr<base::MessageLoopProxy>()> MessageLoopFactoryCB;
+
+ // Callback to notify decryptor creation.
+ typedef base::Callback<void(Decryptor*)> DecryptorNotificationCB;
+
+ // Callback to request/cancel decryptor creation notification.
+ // Calling this callback with a non-null callback registers decryptor creation
+ // notification. When the decryptor is created, notification will be sent
+ // through the provided callback.
+ // Calling this callback with a null callback cancels previously registered
+ // decryptor creation notification. Any previously provided callback will be
+ // fired immediately with NULL.
+ typedef base::Callback<void(const DecryptorNotificationCB&)>
+ RequestDecryptorNotificationCB;
+
+ DecryptingDemuxerStream(
+ const MessageLoopFactoryCB& message_loop_factory_cb,
+ const RequestDecryptorNotificationCB& request_decryptor_notification_cb);
+
+ void Initialize(const scoped_refptr<DemuxerStream>& stream,
+ const PipelineStatusCB& status_cb);
+ void Reset(const base::Closure& closure);
+
+ // DemuxerStream implementation.
+ virtual void Read(const ReadCB& read_cb) OVERRIDE;
+ virtual const AudioDecoderConfig& audio_decoder_config() OVERRIDE;
+ virtual const VideoDecoderConfig& video_decoder_config() OVERRIDE;
+ virtual Type type() OVERRIDE;
+ virtual void EnableBitstreamConverter() OVERRIDE;
+
+ protected:
+ virtual ~DecryptingDemuxerStream();
+
+ private:
+ // For a detailed state diagram please see this link: http://goo.gl/8jAok
+ // TODO(xhwang): Add a ASCII state diagram in this file after this class
+ // stabilizes.
+ // TODO(xhwang): Update this diagram for DecryptingDemuxerStream.
+ enum State {
+ kUninitialized = 0,
+ kDecryptorRequested,
+ kIdle,
+ kPendingDemuxerRead,
+ kPendingDecrypt,
+ kWaitingForKey,
+ };
+
+ // Carries out the initialization operation scheduled by Initialize().
+ void DoInitialize(const scoped_refptr<DemuxerStream>& stream,
+ const PipelineStatusCB& status_cb);
+
+ // Callback for DecryptorHost::RequestDecryptor().
+ void SetDecryptor(Decryptor* decryptor);
+
+ // Callback for DemuxerStream::Read().
+ void DecryptBuffer(DemuxerStream::Status status,
+ const scoped_refptr<DecoderBuffer>& buffer);
+
+ // Carries out the buffer decryption operation scheduled by DecryptBuffer().
+ void DoDecryptBuffer(DemuxerStream::Status status,
+ const scoped_refptr<DecoderBuffer>& buffer);
+
+ void DecryptPendingBuffer();
+
+ // Callback for Decryptor::Decrypt().
+ void DeliverBuffer(Decryptor::Status status,
+ const scoped_refptr<DecoderBuffer>& decrypted_buffer);
+
+ // Carries out the frame delivery operation scheduled by DeliverBuffer().
+ void DoDeliverBuffer(Decryptor::Status status,
+ const scoped_refptr<DecoderBuffer>& decrypted_buffer);
+
+ // Callback for the |decryptor_| to notify this object that a new key has been
+ // added.
+ void OnKeyAdded();
+
+ // Resets decoder and calls |reset_cb_|.
+ void DoReset();
+
+ // Returns Decryptor::StreamType converted from |stream_type_|.
+ Decryptor::StreamType GetDecryptorStreamType() const;
+
+ // This is !is_null() iff Initialize() hasn't been called.
+ MessageLoopFactoryCB message_loop_factory_cb_;
+
+ scoped_refptr<base::MessageLoopProxy> message_loop_;
+
+ State state_;
+
+ PipelineStatusCB init_cb_;
+ ReadCB read_cb_;
+ base::Closure reset_cb_;
+
+ // Pointer to the input demuxer stream that will feed us encrypted buffers.
+ scoped_refptr<DemuxerStream> demuxer_stream_;
+
+ Type stream_type_;
+ scoped_ptr<AudioDecoderConfig> audio_config_;
+ scoped_ptr<VideoDecoderConfig> video_config_;
+
+ // Callback to request/cancel decryptor creation notification.
+ RequestDecryptorNotificationCB request_decryptor_notification_cb_;
+
+ Decryptor* decryptor_;
+
+ // The buffer returned by the demuxer that needs to be decrypted.
+ scoped_refptr<media::DecoderBuffer> pending_buffer_to_decrypt_;
+
+ // Indicates the situation where new key is added during pending decryption
+ // (in other words, this variable can only be set in state kPendingDecrypt).
+ // If this variable is true and kNoKey is returned then we need to try
+ // decrypting again in case the newly added key is the correct decryption key.
+ bool key_added_while_decrypt_pending_;
+
+ DISALLOW_COPY_AND_ASSIGN(DecryptingDemuxerStream);
+};
+
+} // namespace media
+
+#endif // MEDIA_FILTERS_DECRYPTING_DEMUXER_STREAM_H_
diff --git a/media/filters/decrypting_demuxer_stream_unittest.cc b/media/filters/decrypting_demuxer_stream_unittest.cc
new file mode 100644
index 0000000..247abf3
--- /dev/null
+++ b/media/filters/decrypting_demuxer_stream_unittest.cc
@@ -0,0 +1,459 @@
+// 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 <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/message_loop.h"
+#include "media/base/decoder_buffer.h"
+#include "media/base/decrypt_config.h"
+#include "media/base/gmock_callback_support.h"
+#include "media/base/mock_callback.h"
+#include "media/base/mock_filters.h"
+#include "media/filters/decrypting_demuxer_stream.h"
+#include "media/filters/ffmpeg_decoder_unittest.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using ::testing::_;
+using ::testing::IsNull;
+using ::testing::Return;
+using ::testing::ReturnRef;
+using ::testing::SaveArg;
+using ::testing::StrictMock;
+
+namespace media {
+
+static const int kFakeBufferSize = 16;
+static const VideoFrame::Format kVideoFormat = VideoFrame::YV12;
+static const gfx::Size kCodedSize(320, 240);
+static const gfx::Rect kVisibleRect(320, 240);
+static const gfx::Size kNaturalSize(320, 240);
+static const uint8 kFakeKeyId[] = { 0x4b, 0x65, 0x79, 0x20, 0x49, 0x44 };
+static const uint8 kFakeIv[DecryptConfig::kDecryptionKeySize] = { 0 };
+
+// Create a fake non-empty encrypted buffer.
+static scoped_refptr<DecoderBuffer> CreateFakeEncryptedBuffer() {
+ scoped_refptr<DecoderBuffer> buffer(new DecoderBuffer(kFakeBufferSize));
+ buffer->SetDecryptConfig(scoped_ptr<DecryptConfig>(new DecryptConfig(
+ std::string(reinterpret_cast<const char*>(kFakeKeyId),
+ arraysize(kFakeKeyId)),
+ std::string(reinterpret_cast<const char*>(kFakeIv), arraysize(kFakeIv)),
+ 0,
+ std::vector<SubsampleEntry>())));
+ return buffer;
+}
+
+// Use anonymous namespace here to prevent the actions to be defined multiple
+// times across multiple test files. Sadly we can't use static for them.
+namespace {
+
+ACTION_P(ReturnBuffer, buffer) {
+ arg0.Run(buffer ? DemuxerStream::kOk : DemuxerStream::kAborted, buffer);
+}
+
+ACTION_P(RunCallbackIfNotNull, param) {
+ if (!arg0.is_null())
+ arg0.Run(param);
+}
+
+ACTION_P2(ResetAndRunCallback, callback, param) {
+ base::ResetAndReturn(callback).Run(param);
+}
+
+MATCHER(IsEndOfStream, "end of stream") {
+ return (arg->IsEndOfStream());
+}
+
+} // namespace
+
+class DecryptingDemuxerStreamTest : public testing::Test {
+ public:
+ DecryptingDemuxerStreamTest()
+ : demuxer_stream_(new DecryptingDemuxerStream(
+ base::Bind(&Identity<scoped_refptr<base::MessageLoopProxy> >,
+ message_loop_.message_loop_proxy()),
+ base::Bind(
+ &DecryptingDemuxerStreamTest::RequestDecryptorNotification,
+ base::Unretained(this)))),
+ decryptor_(new StrictMock<MockDecryptor>()),
+ input_audio_stream_(new StrictMock<MockDemuxerStream>()),
+ input_video_stream_(new StrictMock<MockDemuxerStream>()),
+ encrypted_buffer_(CreateFakeEncryptedBuffer()),
+ decrypted_buffer_(new DecoderBuffer(kFakeBufferSize)) {
+ }
+
+ void InitializeAudioAndExpectStatus(const AudioDecoderConfig& config,
+ PipelineStatus status) {
+ EXPECT_CALL(*input_audio_stream_, audio_decoder_config())
+ .WillRepeatedly(ReturnRef(config));
+ EXPECT_CALL(*input_audio_stream_, type())
+ .WillRepeatedly(Return(DemuxerStream::AUDIO));
+
+ demuxer_stream_->Initialize(input_audio_stream_,
+ NewExpectedStatusCB(status));
+ message_loop_.RunUntilIdle();
+ }
+
+ void InitializeVideoAndExpectStatus(const VideoDecoderConfig& config,
+ PipelineStatus status) {
+ EXPECT_CALL(*input_video_stream_, video_decoder_config())
+ .WillRepeatedly(ReturnRef(config));
+ EXPECT_CALL(*input_video_stream_, type())
+ .WillRepeatedly(Return(DemuxerStream::VIDEO));
+
+ demuxer_stream_->Initialize(input_video_stream_,
+ NewExpectedStatusCB(status));
+ message_loop_.RunUntilIdle();
+ }
+
+ // The following functions are used to test stream-type-neutral logic in
+ // DecryptingDemuxerStream. Therefore, we don't specify audio or video in the
+ // function names. But for testing purpose, they all use an audio input
+ // demuxer stream.
+
+ void Initialize() {
+ EXPECT_CALL(*this, RequestDecryptorNotification(_))
+ .WillOnce(RunCallbackIfNotNull(decryptor_.get()));
+ EXPECT_CALL(*decryptor_, RegisterKeyAddedCB(Decryptor::kAudio, _))
+ .WillOnce(SaveArg<1>(&key_added_cb_));
+
+ AudioDecoderConfig input_config(
+ kCodecVorbis, 16, CHANNEL_LAYOUT_STEREO, 44100, NULL, 0, true);
+ InitializeAudioAndExpectStatus(input_config, PIPELINE_OK);
+
+ const AudioDecoderConfig& output_config =
+ demuxer_stream_->audio_decoder_config();
+ EXPECT_EQ(DemuxerStream::AUDIO, demuxer_stream_->type());
+ EXPECT_FALSE(output_config.is_encrypted());
+ EXPECT_EQ(16, output_config.bits_per_channel());
+ EXPECT_EQ(CHANNEL_LAYOUT_STEREO, output_config.channel_layout());
+ EXPECT_EQ(44100, output_config.samples_per_second());
+ }
+
+ void ReadAndExpectBufferReadyWith(
+ DemuxerStream::Status status,
+ const scoped_refptr<DecoderBuffer>& decrypted_buffer) {
+ if (status != DemuxerStream::kOk)
+ EXPECT_CALL(*this, BufferReady(status, IsNull()));
+ else if (decrypted_buffer->IsEndOfStream())
+ EXPECT_CALL(*this, BufferReady(status, IsEndOfStream()));
+ else
+ EXPECT_CALL(*this, BufferReady(status, decrypted_buffer));
+
+ demuxer_stream_->Read(base::Bind(&DecryptingDemuxerStreamTest::BufferReady,
+ base::Unretained(this)));
+ message_loop_.RunUntilIdle();
+ }
+
+ // Sets up expectations and actions to put DecryptingDemuxerStream in an
+ // active normal reading state.
+ void EnterNormalReadingState() {
+ EXPECT_CALL(*input_audio_stream_, Read(_))
+ .WillOnce(ReturnBuffer(encrypted_buffer_));
+ EXPECT_CALL(*decryptor_, Decrypt(_, _, _))
+ .WillOnce(RunCallback<2>(Decryptor::kSuccess, decrypted_buffer_));
+
+ ReadAndExpectBufferReadyWith(DemuxerStream::kOk, decrypted_buffer_);
+ }
+
+ // Make the read callback pending by saving and not firing it.
+ void EnterPendingReadState() {
+ EXPECT_TRUE(pending_demuxer_read_cb_.is_null());
+ EXPECT_CALL(*input_audio_stream_, Read(_))
+ .WillOnce(SaveArg<0>(&pending_demuxer_read_cb_));
+ demuxer_stream_->Read(base::Bind(&DecryptingDemuxerStreamTest::BufferReady,
+ base::Unretained(this)));
+ message_loop_.RunUntilIdle();
+ // Make sure the Read() triggers a Read() on the input demuxer stream.
+ EXPECT_FALSE(pending_demuxer_read_cb_.is_null());
+ }
+
+ // Make the decrypt callback pending by saving and not firing it.
+ void EnterPendingDecryptState() {
+ EXPECT_TRUE(pending_decrypt_cb_.is_null());
+ EXPECT_CALL(*input_audio_stream_, Read(_))
+ .WillRepeatedly(ReturnBuffer(encrypted_buffer_));
+ EXPECT_CALL(*decryptor_, Decrypt(_, encrypted_buffer_, _))
+ .WillOnce(SaveArg<2>(&pending_decrypt_cb_));
+
+ demuxer_stream_->Read(base::Bind(&DecryptingDemuxerStreamTest::BufferReady,
+ base::Unretained(this)));
+ message_loop_.RunUntilIdle();
+ // Make sure Read() triggers a Decrypt() on the decryptor.
+ EXPECT_FALSE(pending_decrypt_cb_.is_null());
+ }
+
+ void EnterWaitingForKeyState() {
+ EXPECT_CALL(*input_audio_stream_, Read(_))
+ .WillRepeatedly(ReturnBuffer(encrypted_buffer_));
+ EXPECT_CALL(*decryptor_, Decrypt(_, encrypted_buffer_, _))
+ .WillRepeatedly(RunCallback<2>(Decryptor::kNoKey,
+ scoped_refptr<DecoderBuffer>()));
+ demuxer_stream_->Read(base::Bind(&DecryptingDemuxerStreamTest::BufferReady,
+ base::Unretained(this)));
+ message_loop_.RunUntilIdle();
+ }
+
+ void AbortPendingDecryptCB() {
+ if (!pending_decrypt_cb_.is_null()) {
+ base::ResetAndReturn(&pending_decrypt_cb_).Run(Decryptor::kSuccess, NULL);
+ }
+ }
+
+ void Reset() {
+ EXPECT_CALL(*decryptor_, CancelDecrypt(Decryptor::kAudio))
+ .WillRepeatedly(InvokeWithoutArgs(
+ this, &DecryptingDemuxerStreamTest::AbortPendingDecryptCB));
+
+ demuxer_stream_->Reset(NewExpectedClosure());
+ message_loop_.RunUntilIdle();
+ }
+
+ MOCK_METHOD1(RequestDecryptorNotification,
+ void(const DecryptingDemuxerStream::DecryptorNotificationCB&));
+
+ MOCK_METHOD2(BufferReady, void(DemuxerStream::Status,
+ const scoped_refptr<DecoderBuffer>&));
+
+ MessageLoop message_loop_;
+ scoped_refptr<DecryptingDemuxerStream> demuxer_stream_;
+ scoped_ptr<StrictMock<MockDecryptor> > decryptor_;
+ scoped_refptr<StrictMock<MockDemuxerStream> > input_audio_stream_;
+ scoped_refptr<StrictMock<MockDemuxerStream> > input_video_stream_;
+
+ DemuxerStream::ReadCB pending_demuxer_read_cb_;
+ Decryptor::KeyAddedCB key_added_cb_;
+ Decryptor::DecryptCB pending_decrypt_cb_;
+
+ // Constant buffers to be returned by the input demuxer streams and the
+ // |decryptor_|.
+ scoped_refptr<DecoderBuffer> encrypted_buffer_;
+ scoped_refptr<DecoderBuffer> decrypted_buffer_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DecryptingDemuxerStreamTest);
+};
+
+TEST_F(DecryptingDemuxerStreamTest, Initialize_NormalAudio) {
+ Initialize();
+}
+
+// Ensure that DecryptingDemuxerStream only accepts encrypted audio.
+TEST_F(DecryptingDemuxerStreamTest, Initialize_UnencryptedAudioConfig) {
+ AudioDecoderConfig config(kCodecVorbis, 16, CHANNEL_LAYOUT_STEREO, 44100,
+ NULL, 0, false);
+
+ InitializeAudioAndExpectStatus(config, DEMUXER_ERROR_NO_SUPPORTED_STREAMS);
+}
+
+// Ensure DecryptingDemuxerStream handles invalid audio config without crashing.
+TEST_F(DecryptingDemuxerStreamTest, Initialize_InvalidAudioConfig) {
+ AudioDecoderConfig config(kUnknownAudioCodec, 0, CHANNEL_LAYOUT_STEREO, 0,
+ NULL, 0, true);
+
+ InitializeAudioAndExpectStatus(config, DEMUXER_ERROR_NO_SUPPORTED_STREAMS);
+}
+
+TEST_F(DecryptingDemuxerStreamTest, Initialize_NormalVideo) {
+ EXPECT_CALL(*this, RequestDecryptorNotification(_))
+ .WillOnce(RunCallbackIfNotNull(decryptor_.get()));
+ EXPECT_CALL(*decryptor_, RegisterKeyAddedCB(Decryptor::kVideo, _))
+ .WillOnce(SaveArg<1>(&key_added_cb_));
+
+ VideoDecoderConfig config(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN,
+ kVideoFormat,
+ kCodedSize, kVisibleRect, kNaturalSize,
+ NULL, 0, true);
+ InitializeVideoAndExpectStatus(config, PIPELINE_OK);
+
+ const VideoDecoderConfig& output_config =
+ demuxer_stream_->video_decoder_config();
+ EXPECT_EQ(DemuxerStream::VIDEO, demuxer_stream_->type());
+ EXPECT_FALSE(output_config.is_encrypted());
+ EXPECT_EQ(kCodecVP8, output_config.codec());
+ EXPECT_EQ(kVideoFormat, output_config.format());
+ EXPECT_EQ(VIDEO_CODEC_PROFILE_UNKNOWN, output_config.profile());
+ EXPECT_EQ(kCodedSize, output_config.coded_size());
+ EXPECT_EQ(kVisibleRect, output_config.visible_rect());
+ EXPECT_EQ(kNaturalSize, output_config.natural_size());
+ EXPECT_FALSE(output_config.extra_data());
+ EXPECT_EQ(0u, output_config.extra_data_size());
+}
+
+// Ensure that DecryptingDemuxerStream only accepts encrypted video.
+TEST_F(DecryptingDemuxerStreamTest, Initialize_UnencryptedVideoConfig) {
+ VideoDecoderConfig config(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN,
+ kVideoFormat,
+ kCodedSize, kVisibleRect, kNaturalSize,
+ NULL, 0, false);
+
+ InitializeVideoAndExpectStatus(config, DEMUXER_ERROR_NO_SUPPORTED_STREAMS);
+}
+
+// Ensure DecryptingDemuxerStream handles invalid video config without crashing.
+TEST_F(DecryptingDemuxerStreamTest, Initialize_InvalidVideoConfig) {
+ VideoDecoderConfig config(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN,
+ VideoFrame::INVALID,
+ kCodedSize, kVisibleRect, kNaturalSize,
+ NULL, 0, true);
+
+ InitializeVideoAndExpectStatus(config, DEMUXER_ERROR_NO_SUPPORTED_STREAMS);
+}
+
+// Test normal read case.
+TEST_F(DecryptingDemuxerStreamTest, Read_Normal) {
+ Initialize();
+ EnterNormalReadingState();
+}
+
+// Test the case where the decryptor returns error during read.
+TEST_F(DecryptingDemuxerStreamTest, Read_DecryptError) {
+ Initialize();
+
+ EXPECT_CALL(*input_audio_stream_, Read(_))
+ .WillRepeatedly(ReturnBuffer(encrypted_buffer_));
+ EXPECT_CALL(*decryptor_, Decrypt(_, encrypted_buffer_, _))
+ .WillRepeatedly(RunCallback<2>(Decryptor::kError,
+ scoped_refptr<DecoderBuffer>()));
+ ReadAndExpectBufferReadyWith(DemuxerStream::kAborted, NULL);
+}
+
+// Test the case where the input is an end-of-stream buffer.
+TEST_F(DecryptingDemuxerStreamTest, Read_EndOfStream) {
+ Initialize();
+ EnterNormalReadingState();
+
+ // No Decryptor::Decrypt() call is expected for EOS buffer.
+ EXPECT_CALL(*input_audio_stream_, Read(_))
+ .WillOnce(ReturnBuffer(DecoderBuffer::CreateEOSBuffer()));
+
+ ReadAndExpectBufferReadyWith(DemuxerStream::kOk,
+ DecoderBuffer::CreateEOSBuffer());
+}
+
+// Test the case where the a key is added when the decryptor is in
+// kWaitingForKey state.
+TEST_F(DecryptingDemuxerStreamTest, KeyAdded_DuringWaitingForKey) {
+ Initialize();
+ EnterWaitingForKeyState();
+
+ EXPECT_CALL(*decryptor_, Decrypt(_, encrypted_buffer_, _))
+ .WillRepeatedly(RunCallback<2>(Decryptor::kSuccess, decrypted_buffer_));
+ EXPECT_CALL(*this, BufferReady(DemuxerStream::kOk, decrypted_buffer_));
+ key_added_cb_.Run();
+ message_loop_.RunUntilIdle();
+}
+
+// Test the case where the a key is added when the decryptor is in
+// kPendingDecrypt state.
+TEST_F(DecryptingDemuxerStreamTest, KeyAdded_DruingPendingDecrypt) {
+ Initialize();
+ EnterPendingDecryptState();
+
+ EXPECT_CALL(*decryptor_, Decrypt(_, encrypted_buffer_, _))
+ .WillRepeatedly(RunCallback<2>(Decryptor::kSuccess, decrypted_buffer_));
+ EXPECT_CALL(*this, BufferReady(DemuxerStream::kOk, decrypted_buffer_));
+ // The decrypt callback is returned after the correct decryption key is added.
+ key_added_cb_.Run();
+ base::ResetAndReturn(&pending_decrypt_cb_).Run(Decryptor::kNoKey, NULL);
+ message_loop_.RunUntilIdle();
+}
+
+// Test resetting when the DecryptingDemuxerStream is in kIdle state but has
+// not returned any buffer.
+TEST_F(DecryptingDemuxerStreamTest, Reset_DuringIdleAfterInitialization) {
+ Initialize();
+ Reset();
+}
+
+// Test resetting when the DecryptingDemuxerStream is in kIdle state after it
+// has returned one buffer.
+TEST_F(DecryptingDemuxerStreamTest, Reset_DuringIdleAfterReadOneBuffer) {
+ Initialize();
+ EnterNormalReadingState();
+ Reset();
+}
+
+// Test resetting when DecryptingDemuxerStream is in kPendingDemuxerRead state.
+TEST_F(DecryptingDemuxerStreamTest, Reset_DuringPendingDemuxerRead) {
+ Initialize();
+ EnterPendingReadState();
+
+ EXPECT_CALL(*this, BufferReady(DemuxerStream::kAborted, IsNull()));
+
+ Reset();
+ base::ResetAndReturn(&pending_demuxer_read_cb_).Run(DemuxerStream::kOk,
+ encrypted_buffer_);
+ message_loop_.RunUntilIdle();
+}
+
+// Test resetting when the DecryptingDemuxerStream is in kPendingDecrypt state.
+TEST_F(DecryptingDemuxerStreamTest, Reset_DuringPendingDecrypt) {
+ Initialize();
+ EnterPendingDecryptState();
+
+ EXPECT_CALL(*this, BufferReady(DemuxerStream::kAborted, IsNull()));
+
+ Reset();
+}
+
+// Test resetting when the DecryptingDemuxerStream is in kWaitingForKey state.
+TEST_F(DecryptingDemuxerStreamTest, Reset_DuringWaitingForKey) {
+ Initialize();
+ EnterWaitingForKeyState();
+
+ EXPECT_CALL(*this, BufferReady(DemuxerStream::kAborted, IsNull()));
+
+ Reset();
+}
+
+// Test resetting after the DecryptingDemuxerStream has been reset.
+TEST_F(DecryptingDemuxerStreamTest, Reset_AfterReset) {
+ Initialize();
+ EnterNormalReadingState();
+ Reset();
+ Reset();
+}
+
+// Test aborted read on the demuxer stream.
+TEST_F(DecryptingDemuxerStreamTest, DemuxerRead_Aborted) {
+ Initialize();
+
+ // ReturnBuffer() with NULL triggers aborted demuxer read.
+ EXPECT_CALL(*input_audio_stream_, Read(_))
+ .WillOnce(ReturnBuffer(scoped_refptr<DecoderBuffer>()));
+
+ ReadAndExpectBufferReadyWith(DemuxerStream::kAborted, NULL);
+}
+
+// Test aborted read on the input demuxer stream when the
+// DecryptingDemuxerStream is being reset.
+TEST_F(DecryptingDemuxerStreamTest, DemuxerRead_AbortedDuringReset) {
+ Initialize();
+ EnterPendingReadState();
+
+ // Make sure we get a NULL audio frame returned.
+ EXPECT_CALL(*this, BufferReady(DemuxerStream::kAborted, IsNull()));
+
+ Reset();
+ base::ResetAndReturn(&pending_demuxer_read_cb_).Run(DemuxerStream::kAborted,
+ NULL);
+ message_loop_.RunUntilIdle();
+}
+
+// Test config change on the input demuxer stream.
+TEST_F(DecryptingDemuxerStreamTest, DemuxerRead_ConfigChanged) {
+ Initialize();
+
+ EXPECT_CALL(*input_audio_stream_, Read(_))
+ .WillOnce(RunCallback<0>(DemuxerStream::kConfigChanged,
+ scoped_refptr<DecoderBuffer>()));
+
+ // TODO(xhwang): Update this when kConfigChanged is supported.
+ ReadAndExpectBufferReadyWith(DemuxerStream::kAborted, NULL);
+}
+
+} // namespace media
diff --git a/media/filters/decrypting_video_decoder.cc b/media/filters/decrypting_video_decoder.cc
index c819a2e..72fcd8b 100644
--- a/media/filters/decrypting_video_decoder.cc
+++ b/media/filters/decrypting_video_decoder.cc
@@ -8,6 +8,7 @@
#include "base/callback_helpers.h"
#include "base/debug/trace_event.h"
#include "base/location.h"
+#include "base/logging.h"
#include "base/message_loop_proxy.h"
#include "media/base/bind_to_loop.h"
#include "media/base/decoder_buffer.h"
@@ -29,7 +30,7 @@ DecryptingVideoDecoder::DecryptingVideoDecoder(
state_(kUninitialized),
request_decryptor_notification_cb_(request_decryptor_notification_cb),
decryptor_(NULL),
- key_added_while_pending_decode_(false),
+ key_added_while_decode_pending_(false),
trace_id_(0) {
}
@@ -332,8 +333,8 @@ void DecryptingVideoDecoder::DoDeliverFrame(
DCHECK(!read_cb_.is_null());
DCHECK(pending_buffer_to_decode_);
- bool need_to_try_again_if_nokey_is_returned = key_added_while_pending_decode_;
- key_added_while_pending_decode_ = false;
+ 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_;
@@ -403,7 +404,7 @@ void DecryptingVideoDecoder::OnKeyAdded() {
DCHECK(message_loop_->BelongsToCurrentThread());
if (state_ == kPendingDecode) {
- key_added_while_pending_decode_ = true;
+ key_added_while_decode_pending_ = true;
return;
}
diff --git a/media/filters/decrypting_video_decoder.h b/media/filters/decrypting_video_decoder.h
index d2fb04b..ac9136e 100644
--- a/media/filters/decrypting_video_decoder.h
+++ b/media/filters/decrypting_video_decoder.h
@@ -65,7 +65,7 @@ class MEDIA_EXPORT DecryptingVideoDecoder : public VideoDecoder {
// For a detailed state diagram please see this link: http://goo.gl/8jAok
// TODO(xhwang): Add a ASCII state diagram in this file after this class
// stabilizes.
- enum DecoderState {
+ enum State {
kUninitialized = 0,
kDecryptorRequested,
kPendingDecoderInit,
@@ -114,8 +114,8 @@ class MEDIA_EXPORT DecryptingVideoDecoder : public VideoDecoder {
Decryptor::Status status,
const scoped_refptr<VideoFrame>& frame);
- // Callback for the |decryptor_| to notify the DecryptingVideoDecoder that
- // a new key has been added.
+ // Callback for the |decryptor_| to notify this object that a new key has been
+ // added.
void OnKeyAdded();
// Reset decoder and call |reset_cb_|.
@@ -129,8 +129,7 @@ class MEDIA_EXPORT DecryptingVideoDecoder : public VideoDecoder {
scoped_refptr<base::MessageLoopProxy> message_loop_;
- // Current state of the DecryptingVideoDecoder.
- DecoderState state_;
+ State state_;
PipelineStatusCB init_cb_;
StatisticsCB statistics_cb_;
@@ -153,7 +152,7 @@ class MEDIA_EXPORT DecryptingVideoDecoder : public VideoDecoder {
// If this variable is true and kNoKey is returned then we need to try
// decrypting/decoding again in case the newly added key is the correct
// decryption key.
- bool key_added_while_pending_decode_;
+ bool key_added_while_decode_pending_;
// A unique ID to trace Decryptor::DecryptAndDecodeVideo() call and the
// matching DecryptCB call (in DoDeliverFrame()).
diff --git a/media/filters/decrypting_video_decoder_unittest.cc b/media/filters/decrypting_video_decoder_unittest.cc
index aa06d41..f1991d0 100644
--- a/media/filters/decrypting_video_decoder_unittest.cc
+++ b/media/filters/decrypting_video_decoder_unittest.cc
@@ -20,7 +20,6 @@
using ::testing::_;
using ::testing::AtMost;
-using ::testing::Invoke;
using ::testing::IsNull;
using ::testing::ReturnRef;
using ::testing::SaveArg;
@@ -74,7 +73,7 @@ MATCHER(IsEndOfStream, "end of stream") {
class DecryptingVideoDecoderTest : public testing::Test {
public:
DecryptingVideoDecoderTest()
- : decoder_(new StrictMock<DecryptingVideoDecoder>(
+ : decoder_(new DecryptingVideoDecoder(
base::Bind(&Identity<scoped_refptr<base::MessageLoopProxy> >,
message_loop_.message_loop_proxy()),
base::Bind(
@@ -239,7 +238,7 @@ class DecryptingVideoDecoderTest : public testing::Test {
const scoped_refptr<VideoFrame>&));
MessageLoop message_loop_;
- scoped_refptr<StrictMock<DecryptingVideoDecoder> > decoder_;
+ scoped_refptr<DecryptingVideoDecoder> decoder_;
scoped_ptr<StrictMock<MockDecryptor> > decryptor_;
scoped_refptr<StrictMock<MockDemuxerStream> > demuxer_;
MockStatisticsCB statistics_cb_;
diff --git a/media/media.gyp b/media/media.gyp
index d3b12c6..bee289b 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -259,6 +259,8 @@
'filters/chunk_demuxer.h',
'filters/decrypting_audio_decoder.cc',
'filters/decrypting_audio_decoder.h',
+ 'filters/decrypting_demuxer_stream.cc',
+ 'filters/decrypting_demuxer_stream.h',
'filters/decrypting_video_decoder.cc',
'filters/decrypting_video_decoder.h',
'filters/dummy_demuxer.cc',
@@ -654,6 +656,7 @@
'filters/blocking_url_protocol_unittest.cc',
'filters/chunk_demuxer_unittest.cc',
'filters/decrypting_audio_decoder_unittest.cc',
+ 'filters/decrypting_demuxer_stream_unittest.cc',
'filters/decrypting_video_decoder_unittest.cc',
'filters/ffmpeg_audio_decoder_unittest.cc',
'filters/ffmpeg_decoder_unittest.h',