diff options
-rw-r--r-- | media/base/decryptor.h | 1 | ||||
-rw-r--r-- | media/filters/decrypting_audio_decoder.cc | 10 | ||||
-rw-r--r-- | media/filters/decrypting_audio_decoder.h | 11 | ||||
-rw-r--r-- | media/filters/decrypting_audio_decoder_unittest.cc | 22 | ||||
-rw-r--r-- | media/filters/decrypting_demuxer_stream.cc | 350 | ||||
-rw-r--r-- | media/filters/decrypting_demuxer_stream.h | 148 | ||||
-rw-r--r-- | media/filters/decrypting_demuxer_stream_unittest.cc | 459 | ||||
-rw-r--r-- | media/filters/decrypting_video_decoder.cc | 9 | ||||
-rw-r--r-- | media/filters/decrypting_video_decoder.h | 11 | ||||
-rw-r--r-- | media/filters/decrypting_video_decoder_unittest.cc | 5 | ||||
-rw-r--r-- | media/media.gyp | 3 |
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', |