diff options
author | xhwang@chromium.org <xhwang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-10 23:36:31 +0000 |
---|---|---|
committer | xhwang@chromium.org <xhwang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-10 23:36:31 +0000 |
commit | 326dff446c95d772d1f80f086ccff86b3ff8bf25 (patch) | |
tree | 61c0459fc2f20a842ccab20fd868ec1fb5c84dc3 /media | |
parent | 1fba73c14c6cadc0f04045e04187d734695aec65 (diff) | |
download | chromium_src-326dff446c95d772d1f80f086ccff86b3ff8bf25.zip chromium_src-326dff446c95d772d1f80f086ccff86b3ff8bf25.tar.gz chromium_src-326dff446c95d772d1f80f086ccff86b3ff8bf25.tar.bz2 |
Add decryptor requesting and kNoKey handling in DecryptingVideoDecoder (DVD).
- These logic was previously implemented in ProxyDecryptor. Now add them into DVD so that the DVD can talk directly to other Decryptor implementations such as the PpapiDecryptor.
- Since we have more states in DVD now, add more state enum to manage state transition.
- Add RequestDecryptorNotificationCB for decryptor creation notification.
- Add KeyAddedCB for notifying the DVD that new key has been added.
BUG=141784
TEST=current unittests pass; added more unittests.
Review URL: https://chromiumcodereview.appspot.com/11074010
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@161231 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/base/decryptor.h | 6 | ||||
-rw-r--r-- | media/base/mock_filters.h | 5 | ||||
-rw-r--r-- | media/crypto/aes_decryptor.cc | 3 | ||||
-rw-r--r-- | media/crypto/aes_decryptor.h | 3 | ||||
-rw-r--r-- | media/filters/decrypting_video_decoder.cc | 223 | ||||
-rw-r--r-- | media/filters/decrypting_video_decoder.h | 70 | ||||
-rw-r--r-- | media/filters/decrypting_video_decoder_unittest.cc | 227 |
7 files changed, 397 insertions, 140 deletions
diff --git a/media/base/decryptor.h b/media/base/decryptor.h index 876129a..6c16106 100644 --- a/media/base/decryptor.h +++ b/media/base/decryptor.h @@ -105,12 +105,16 @@ class MEDIA_EXPORT Decryptor { // - Set to true if initialization was successful. False if an error occurred. typedef base::Callback<void(bool)> DecoderInitCB; + // Indicates that a key has been added to the Decryptor. + typedef base::Callback<void()> KeyAddedCB; + // Initializes a video decoder with the given |config|, executing the // |init_cb| upon completion. // Note: DecryptAndDecodeVideo(), ResetVideoDecoder() and StopVideoDecoder() // can only be called after InitializeVideoDecoder() succeeded. virtual void InitializeVideoDecoder(const VideoDecoderConfig& config, - const DecoderInitCB& init_cb) = 0; + const DecoderInitCB& init_cb, + const KeyAddedCB& key_added_cb) = 0; // Indicates completion of video decrypting and decoding operation. // diff --git a/media/base/mock_filters.h b/media/base/mock_filters.h index 4404bcf..cb5f80e 100644 --- a/media/base/mock_filters.h +++ b/media/base/mock_filters.h @@ -208,8 +208,9 @@ class MockDecryptor : public Decryptor { MOCK_METHOD2(Decrypt, void(const scoped_refptr<DecoderBuffer>& encrypted, const DecryptCB& decrypt_cb)); MOCK_METHOD0(CancelDecrypt, void()); - MOCK_METHOD2(InitializeVideoDecoder, void(const VideoDecoderConfig& config, - const DecoderInitCB& init_cb)); + MOCK_METHOD3(InitializeVideoDecoder, void(const VideoDecoderConfig& config, + const DecoderInitCB& init_cb, + const KeyAddedCB& key_added_cb)); MOCK_METHOD2(DecryptAndDecodeVideo, void(const scoped_refptr<media::DecoderBuffer>& encrypted, const VideoDecodeCB& video_decode_cb)); diff --git a/media/crypto/aes_decryptor.cc b/media/crypto/aes_decryptor.cc index 5e41ea7..b4691a5 100644 --- a/media/crypto/aes_decryptor.cc +++ b/media/crypto/aes_decryptor.cc @@ -235,7 +235,8 @@ void AesDecryptor::CancelDecrypt() { } void AesDecryptor::InitializeVideoDecoder(const VideoDecoderConfig& config, - const DecoderInitCB& init_cb) { + const DecoderInitCB& init_cb, + const KeyAddedCB& key_added_cb) { // AesDecryptor does not support video decoding. Always return false here. init_cb.Run(false); } diff --git a/media/crypto/aes_decryptor.h b/media/crypto/aes_decryptor.h index 2df8613..b239570 100644 --- a/media/crypto/aes_decryptor.h +++ b/media/crypto/aes_decryptor.h @@ -52,7 +52,8 @@ class MEDIA_EXPORT AesDecryptor : public Decryptor { const DecryptCB& decrypt_cb) OVERRIDE; virtual void CancelDecrypt() OVERRIDE; virtual void InitializeVideoDecoder(const VideoDecoderConfig& config, - const DecoderInitCB& init_cb) OVERRIDE; + const DecoderInitCB& init_cb, + const KeyAddedCB& key_added_cb) OVERRIDE; virtual void DecryptAndDecodeVideo( const scoped_refptr<DecoderBuffer>& encrypted, const VideoDecodeCB& video_decode_cb) OVERRIDE; diff --git a/media/filters/decrypting_video_decoder.cc b/media/filters/decrypting_video_decoder.cc index 0a4d7a5..c45cb07 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/location.h" #include "base/message_loop_proxy.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" @@ -17,12 +18,17 @@ namespace media { +#define BIND_TO_LOOP(function) \ + media::BindToLoop(message_loop_, base::Bind(function, this)) + DecryptingVideoDecoder::DecryptingVideoDecoder( const MessageLoopFactoryCB& message_loop_factory_cb, - Decryptor* decryptor) + const RequestDecryptorNotificationCB& request_decryptor_notification_cb) : message_loop_factory_cb_(message_loop_factory_cb), state_(kUninitialized), - decryptor_(decryptor) { + request_decryptor_notification_cb_(request_decryptor_notification_cb), + decryptor_(NULL), + key_added_while_pending_decode_(false) { } void DecryptingVideoDecoder::Initialize( @@ -50,10 +56,15 @@ void DecryptingVideoDecoder::Reset(const base::Closure& closure) { return; } - DCHECK_NE(state_, kUninitialized); + DCHECK(state_ == kIdle || + state_ == kPendingDemuxerRead || + state_ == kPendingDecode || + state_ == kWaitingForKey || + state_ == kDecodeFinished) << state_; DCHECK(init_cb_.is_null()); // No Reset() during pending initialization. DCHECK(stop_cb_.is_null()); // No Reset() during pending Stop(). DCHECK(reset_cb_.is_null()); + reset_cb_ = closure; decryptor_->CancelDecryptAndDecodeVideo(); @@ -62,9 +73,18 @@ void DecryptingVideoDecoder::Reset(const base::Closure& closure) { // Defer the resetting process in this case. The |reset_cb_| will be fired // after the read callback is fired - see DoDecryptAndDecodeBuffer() and // DoDeliverFrame(). - if (!read_cb_.is_null()) + if (state_ == kPendingDemuxerRead || state_ == kPendingDecode) { + DCHECK(!read_cb_.is_null()); return; + } + + if (state_ == kWaitingForKey) { + DCHECK(!read_cb_.is_null()); + pending_buffer_to_decode_ = NULL; + base::ResetAndReturn(&read_cb_).Run(kOk, NULL); + } + DCHECK(read_cb_.is_null()); DoReset(); } @@ -78,20 +98,56 @@ void DecryptingVideoDecoder::Stop(const base::Closure& closure) { DCHECK(stop_cb_.is_null()); stop_cb_ = closure; - decryptor_->StopVideoDecoder(); - - // Stop() cannot complete if the init or read callback is still pending. - // Defer the stopping process in these cases. The |stop_cb_| will be fired - // after the init or read callback is fired - see DoFinishInitialization(), - // DoDecryptAndDecodeBuffer() and DoDeliverFrame(). - if (!init_cb_.is_null() || !read_cb_.is_null()) - return; - - DoStop(); + // We need to call Decryptor::StopVideoDecoder() if we ever called + // Decryptor::InitializeVideoDecoder() to cancel the pending initialization if + // the initialization is still pending, or to stop the video decoder if + // the initialization has completed. + // When the state is kUninitialized and kDecryptorRequested, + // InitializeVideoDecoder() has not been called, so we are okay. + // When the state is kStopped, the video decoder should have already been + // stopped, so no need to call StopVideoDecoder() either. + // In all other cases, we need to call StopVideoDecoder()! + switch (state_) { + case kUninitialized: + case kStopped: + DoStop(); + break; + case kDecryptorRequested: + // Stop() cannot complete if the decryptor request is still pending. + // Defer the stopping process in this case. The |stop_cb_| will be fired + // after the request decryptor callback is fired - see SetDecryptor(). + request_decryptor_notification_cb_.Run(DecryptorNotificationCB()); + break; + case kIdle: + case kDecodeFinished: + decryptor_->StopVideoDecoder(); + DoStop(); + break; + case kWaitingForKey: + decryptor_->StopVideoDecoder(); + DCHECK(!read_cb_.is_null()); + pending_buffer_to_decode_ = NULL; + base::ResetAndReturn(&read_cb_).Run(kOk, NULL); + DoStop(); + break; + case kPendingDecoderInit: + case kPendingDemuxerRead: + case kPendingDecode: + // Stop() cannot complete if the init or read callback is still pending. + // Defer the stopping process in these cases. The |stop_cb_| will be + // fired after the init or read callback is fired - see + // FinishInitialization(), DoDecryptAndDecodeBuffer() and + // DoDeliverFrame(), respectively. + decryptor_->StopVideoDecoder(); + DCHECK(!init_cb_.is_null() || !read_cb_.is_null()); + break; + default: + NOTREACHED(); + } } DecryptingVideoDecoder::~DecryptingVideoDecoder() { - DCHECK_EQ(state_, kUninitialized); + DCHECK(state_ == kUninitialized || state_ == kStopped) << state_; } void DecryptingVideoDecoder::DoInitialize( @@ -99,8 +155,8 @@ void DecryptingVideoDecoder::DoInitialize( const PipelineStatusCB& status_cb, const StatisticsCB& statistics_cb) { DCHECK(message_loop_->BelongsToCurrentThread()); + DCHECK_EQ(state_, kUninitialized) << state_; DCHECK(stream); - DCHECK_EQ(state_, kUninitialized); const VideoDecoderConfig& config = stream->video_decoder_config(); if (!config.IsValidConfig()) { @@ -121,21 +177,39 @@ void DecryptingVideoDecoder::DoInitialize( statistics_cb_ = statistics_cb; init_cb_ = status_cb; - decryptor_->InitializeVideoDecoder(config, base::Bind( - &DecryptingVideoDecoder::FinishInitialization, this)); + + state_ = kDecryptorRequested; + request_decryptor_notification_cb_.Run( + BIND_TO_LOOP(&DecryptingVideoDecoder::SetDecryptor)); } -void DecryptingVideoDecoder::FinishInitialization(bool success) { - message_loop_->PostTask(FROM_HERE, base::Bind( - &DecryptingVideoDecoder::DoFinishInitialization, this, success)); +void DecryptingVideoDecoder::SetDecryptor(Decryptor* decryptor) { + DCHECK(message_loop_->BelongsToCurrentThread()); + DCHECK_EQ(state_, kDecryptorRequested) << state_; + DCHECK(!init_cb_.is_null()); + + if (!stop_cb_.is_null()) { + base::ResetAndReturn(&init_cb_).Run(DECODER_ERROR_NOT_SUPPORTED); + DoStop(); + return; + } + + decryptor_ = decryptor; + + state_ = kPendingDecoderInit; + const VideoDecoderConfig& config = demuxer_stream_->video_decoder_config(); + decryptor_->InitializeVideoDecoder( + config, + BIND_TO_LOOP(&DecryptingVideoDecoder::FinishInitialization), + BIND_TO_LOOP(&DecryptingVideoDecoder::OnKeyAdded)); } -void DecryptingVideoDecoder::DoFinishInitialization(bool success) { +void DecryptingVideoDecoder::FinishInitialization(bool success) { DCHECK(message_loop_->BelongsToCurrentThread()); - DCHECK_EQ(state_, kUninitialized); + DCHECK_EQ(state_, kPendingDecoderInit) << state_; DCHECK(!init_cb_.is_null()); - DCHECK(reset_cb_.is_null()); - DCHECK(read_cb_.is_null()); + DCHECK(reset_cb_.is_null()); // No Reset() before initialization finished. + DCHECK(read_cb_.is_null()); // No Read() before initialization finished. if (!stop_cb_.is_null()) { base::ResetAndReturn(&init_cb_).Run(DECODER_ERROR_NOT_SUPPORTED); @@ -145,18 +219,19 @@ void DecryptingVideoDecoder::DoFinishInitialization(bool success) { if (!success) { base::ResetAndReturn(&init_cb_).Run(DECODER_ERROR_NOT_SUPPORTED); + state_ = kStopped; return; } // Success! - state_ = kNormal; + state_ = kIdle; base::ResetAndReturn(&init_cb_).Run(PIPELINE_OK); } void DecryptingVideoDecoder::DoRead(const ReadCB& read_cb) { DCHECK(message_loop_->BelongsToCurrentThread()); + DCHECK(state_ == kIdle || state_ == kDecodeFinished) << state_; DCHECK(!read_cb.is_null()); - CHECK_NE(state_, kUninitialized); CHECK(read_cb_.is_null()) << "Overlapping decodes are not supported."; // Return empty frames if decoding has finished. @@ -166,12 +241,13 @@ void DecryptingVideoDecoder::DoRead(const ReadCB& read_cb) { } read_cb_ = read_cb; + state_ = kPendingDemuxerRead; ReadFromDemuxerStream(); } void DecryptingVideoDecoder::ReadFromDemuxerStream() { DCHECK(message_loop_->BelongsToCurrentThread()); - DCHECK_EQ(state_, kNormal); + DCHECK_EQ(state_, kPendingDemuxerRead) << state_; DCHECK(!read_cb_.is_null()); demuxer_stream_->Read( @@ -187,48 +263,54 @@ void DecryptingVideoDecoder::DecryptAndDecodeBuffer( // stack we are still fine. But it looks like a force post task makes the // logic more understandable and manageable, so why not? message_loop_->PostTask(FROM_HERE, base::Bind( - &DecryptingVideoDecoder::DoDecryptAndDecodeBuffer, this, - status, buffer)); + &DecryptingVideoDecoder::DoDecryptAndDecodeBuffer, this, status, buffer)); } void DecryptingVideoDecoder::DoDecryptAndDecodeBuffer( DemuxerStream::Status status, const scoped_refptr<DecoderBuffer>& buffer) { DCHECK(message_loop_->BelongsToCurrentThread()); - DCHECK_EQ(state_, kNormal); + DCHECK_EQ(state_, kPendingDemuxerRead) << state_; DCHECK(!read_cb_.is_null()); DCHECK_EQ(buffer != NULL, status == DemuxerStream::kOk) << status; - if (!stop_cb_.is_null()) { + if (!reset_cb_.is_null() || !stop_cb_.is_null()) { base::ResetAndReturn(&read_cb_).Run(kOk, NULL); if (!reset_cb_.is_null()) DoReset(); - - DoStop(); - return; - } - - if (!reset_cb_.is_null()) { - base::ResetAndReturn(&read_cb_).Run(kOk, NULL); - DoReset(); + if (!stop_cb_.is_null()) + DoStop(); return; } if (status == DemuxerStream::kAborted) { base::ResetAndReturn(&read_cb_).Run(kOk, NULL); + state_ = kIdle; return; } if (status == DemuxerStream::kConfigChanged) { // TODO(xhwang): Add config change support. + // The |state_| is chosen to be kDecodeFinished here to be consistent with + // the implementation of FFmpegVideoDecoder. + state_ = kDecodeFinished; base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL); return; } DCHECK_EQ(status, DemuxerStream::kOk); + pending_buffer_to_decode_ = buffer; + state_ = kPendingDecode; + DecodePendingBuffer(); +} + +void DecryptingVideoDecoder::DecodePendingBuffer() { + DCHECK(message_loop_->BelongsToCurrentThread()); + DCHECK_EQ(state_, kPendingDecode) << state_; decryptor_->DecryptAndDecodeVideo( - buffer, base::Bind(&DecryptingVideoDecoder::DeliverFrame, this, - buffer->GetDataSize())); + pending_buffer_to_decode_, + base::Bind(&DecryptingVideoDecoder::DeliverFrame, this, + pending_buffer_to_decode_->GetDataSize())); } void DecryptingVideoDecoder::DeliverFrame( @@ -249,28 +331,39 @@ void DecryptingVideoDecoder::DoDeliverFrame( Decryptor::Status status, const scoped_refptr<VideoFrame>& frame) { DCHECK(message_loop_->BelongsToCurrentThread()); - DCHECK_EQ(state_, kNormal); + DCHECK_EQ(state_, kPendingDecode) << state_; DCHECK(!read_cb_.is_null()); + DCHECK(pending_buffer_to_decode_); - if (!stop_cb_.is_null()) { + bool need_to_try_again_if_nokey_is_returned = key_added_while_pending_decode_; + key_added_while_pending_decode_ = false; + + if (!reset_cb_.is_null() || !stop_cb_.is_null()) { + pending_buffer_to_decode_ = NULL; base::ResetAndReturn(&read_cb_).Run(kOk, NULL); if (!reset_cb_.is_null()) DoReset(); - - DoStop(); + if (!stop_cb_.is_null()) + DoStop(); return; } - if (!reset_cb_.is_null()) { - base::ResetAndReturn(&read_cb_).Run(kOk, NULL); - DoReset(); + if (status == Decryptor::kError) { + DCHECK(!frame); + state_ = kDecodeFinished; + base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL); return; } - if (status == Decryptor::kNoKey || status == Decryptor::kError) { + if (status == Decryptor::kNoKey) { DCHECK(!frame); - state_ = kDecodeFinished; - base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL); + if (need_to_try_again_if_nokey_is_returned) { + // The |state_| is still kPendingDecode. + DecodePendingBuffer(); + return; + } + + state_ = kWaitingForKey; return; } @@ -283,27 +376,41 @@ void DecryptingVideoDecoder::DoDeliverFrame( if (status == Decryptor::kNeedMoreData) { DCHECK(!frame); + state_ = kPendingDemuxerRead; ReadFromDemuxerStream(); return; } DCHECK_EQ(status, Decryptor::kSuccess); - if (frame->IsEndOfStream()) - state_ = kDecodeFinished; - + state_ = frame->IsEndOfStream() ? kDecodeFinished : kIdle; base::ResetAndReturn(&read_cb_).Run(kOk, frame); } +void DecryptingVideoDecoder::OnKeyAdded() { + DCHECK(message_loop_->BelongsToCurrentThread()); + + if (state_ == kPendingDecode) { + key_added_while_pending_decode_ = true; + return; + } + + if (state_ == kWaitingForKey) { + state_ = kPendingDecode; + DecodePendingBuffer(); + } +} + void DecryptingVideoDecoder::DoReset() { + DCHECK(init_cb_.is_null()); DCHECK(read_cb_.is_null()); - DCHECK_NE(state_, kUninitialized); - state_ = kNormal; + state_ = kIdle; base::ResetAndReturn(&reset_cb_).Run(); } void DecryptingVideoDecoder::DoStop() { + DCHECK(init_cb_.is_null()); DCHECK(read_cb_.is_null()); - state_ = kUninitialized; + state_ = kStopped; base::ResetAndReturn(&stop_cb_).Run(); } diff --git a/media/filters/decrypting_video_decoder.h b/media/filters/decrypting_video_decoder.h index 878b78f..d9f2a27 100644 --- a/media/filters/decrypting_video_decoder.h +++ b/media/filters/decrypting_video_decoder.h @@ -22,6 +22,8 @@ class Decryptor; // Decryptor-based VideoDecoder implementation that can decrypt and decode // encrypted video buffers and return decrypted and decompressed video frames. +// All public APIs and callbacks are trampolined to the |message_loop_| so +// that no locks are required for thread safety. // // TODO(xhwang): For now, DecryptingVideoDecoder relies on the decryptor to do // both decryption and video decoding. Add the path to use the decryptor for @@ -29,14 +31,24 @@ class Decryptor; // DecryptingVideoDecoder for video decoding. class MEDIA_EXPORT DecryptingVideoDecoder : public VideoDecoder { public: + // Callback to get a message loop. typedef base::Callback< scoped_refptr<base::MessageLoopProxy>()> MessageLoopFactoryCB; - // |message_loop_factory_cb| provides message loop that the decryption - // operations run on. - // |decryptor| performs real decrypting operations. - // Note that |decryptor| must outlive this instance. - DecryptingVideoDecoder(const MessageLoopFactoryCB& message_loop_factory_cb, - Decryptor* decryptor); + // 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; + + DecryptingVideoDecoder( + const MessageLoopFactoryCB& message_loop_factory_cb, + const RequestDecryptorNotificationCB& request_decryptor_notification_cb); // VideoDecoder implementation. virtual void Initialize(const scoped_refptr<DemuxerStream>& stream, @@ -50,10 +62,19 @@ class MEDIA_EXPORT DecryptingVideoDecoder : public VideoDecoder { virtual ~DecryptingVideoDecoder(); 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. enum DecoderState { - kUninitialized, - kNormal, - kDecodeFinished + kUninitialized = 0, + kDecryptorRequested, + kPendingDecoderInit, + kIdle, + kPendingDemuxerRead, + kPendingDecode, + kWaitingForKey, + kDecodeFinished, + kStopped }; // Carries out the initialization operation scheduled by Initialize(). @@ -61,13 +82,12 @@ class MEDIA_EXPORT DecryptingVideoDecoder : public VideoDecoder { const PipelineStatusCB& status_cb, const StatisticsCB& statistics_cb); + // Callback for DecryptorHost::RequestDecryptor(). + void SetDecryptor(Decryptor* decryptor); + // Callback for Decryptor::InitializeVideoDecoder(). void FinishInitialization(bool success); - // Carries out the initialization finishing operation scheduled by - // FinishInitialization(). - void DoFinishInitialization(bool success); - // Carries out the buffer reading operation scheduled by Read(). void DoRead(const ReadCB& read_cb); @@ -82,6 +102,8 @@ class MEDIA_EXPORT DecryptingVideoDecoder : public VideoDecoder { void DoDecryptAndDecodeBuffer(DemuxerStream::Status status, const scoped_refptr<DecoderBuffer>& buffer); + void DecodePendingBuffer(); + // Callback for Decryptor::DecryptAndDecodeVideo(). void DeliverFrame(int buffer_size, Decryptor::Status status, @@ -92,6 +114,10 @@ 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. + void OnKeyAdded(); + // Reset decoder and call |reset_cb_|. void DoReset(); @@ -102,7 +128,10 @@ class MEDIA_EXPORT DecryptingVideoDecoder : public VideoDecoder { MessageLoopFactoryCB message_loop_factory_cb_; scoped_refptr<base::MessageLoopProxy> message_loop_; + + // Current state of the DecryptingVideoDecoder. DecoderState state_; + PipelineStatusCB init_cb_; StatisticsCB statistics_cb_; ReadCB read_cb_; @@ -112,7 +141,20 @@ class MEDIA_EXPORT DecryptingVideoDecoder : public VideoDecoder { // Pointer to the demuxer stream that will feed us compressed buffers. scoped_refptr<DemuxerStream> demuxer_stream_; - Decryptor* const decryptor_; + // Callback to request/cancel decryptor creation notification. + RequestDecryptorNotificationCB request_decryptor_notification_cb_; + + Decryptor* decryptor_; + + // The buffer returned by the demuxer that needs decrypting/decoding. + scoped_refptr<media::DecoderBuffer> pending_buffer_to_decode_; + + // Indicates the situation where new key is added during pending decode + // (in other words, this variable can only be set in state kPendingDecode). + // 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_; DISALLOW_COPY_AND_ASSIGN(DecryptingVideoDecoder); }; diff --git a/media/filters/decrypting_video_decoder_unittest.cc b/media/filters/decrypting_video_decoder_unittest.cc index 0b947dd..4271042 100644 --- a/media/filters/decrypting_video_decoder_unittest.cc +++ b/media/filters/decrypting_video_decoder_unittest.cc @@ -18,6 +18,7 @@ #include "testing/gmock/include/gmock/gmock.h" using ::testing::_; +using ::testing::AtMost; using ::testing::Invoke; using ::testing::IsNull; using ::testing::ReturnRef; @@ -54,6 +55,10 @@ ACTION(ReturnConfigChanged) { arg0.Run(DemuxerStream::kConfigChanged, scoped_refptr<DecoderBuffer>(NULL)); } +ACTION_P(RunCallback0, param) { + arg0.Run(param); +} + ACTION_P(RunCallback1, param) { arg1.Run(param); } @@ -62,14 +67,24 @@ ACTION_P2(RunCallback2, param1, param2) { arg1.Run(param1, param2); } +ACTION_P2(ResetAndRunCallback, callback, param) { + base::ResetAndReturn(callback).Run(param); +} + +MATCHER(IsNullCallback, "") { + return (arg.is_null()); +} + class DecryptingVideoDecoderTest : public testing::Test { public: DecryptingVideoDecoderTest() - : decryptor_(new StrictMock<MockDecryptor>()), - decoder_(new StrictMock<DecryptingVideoDecoder>( + : decoder_(new StrictMock<DecryptingVideoDecoder>( base::Bind(&Identity<scoped_refptr<base::MessageLoopProxy> >, message_loop_.message_loop_proxy()), - decryptor_.get())), + base::Bind( + &DecryptingVideoDecoderTest::RequestDecryptorNotification, + base::Unretained(this)))), + decryptor_(new StrictMock<MockDecryptor>()), demuxer_(new StrictMock<MockDemuxerStream>()), encrypted_buffer_(CreateFakeEncryptedBuffer()), decoded_video_frame_(VideoFrame::CreateBlackFrame(kCodedSize)), @@ -85,6 +100,8 @@ class DecryptingVideoDecoderTest : public testing::Test { PipelineStatus status) { EXPECT_CALL(*demuxer_, video_decoder_config()) .WillRepeatedly(ReturnRef(config)); + EXPECT_CALL(*this, RequestDecryptorNotification(_)) + .WillRepeatedly(RunCallback0(decryptor_.get())); decoder_->Initialize(demuxer_, NewExpectedStatusCB(status), base::Bind(&MockStatisticsCB::OnStatistics, @@ -93,8 +110,9 @@ class DecryptingVideoDecoderTest : public testing::Test { } void Initialize() { - EXPECT_CALL(*decryptor_, InitializeVideoDecoder(_, _)) - .WillOnce(RunCallback1(true)); + EXPECT_CALL(*decryptor_, InitializeVideoDecoder(_, _, _)) + .Times(AtMost(1)) + .WillOnce(DoAll(RunCallback1(true), SaveArg<2>(&key_added_cb_))); config_.Initialize(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN, kVideoFormat, kCodedSize, kVisibleRect, kNaturalSize, @@ -148,7 +166,7 @@ class DecryptingVideoDecoderTest : public testing::Test { } // Make the video decode callback pending by saving and not firing it. - void EnterPendingDecryptAndDecodeState() { + void EnterPendingDecodeState() { EXPECT_TRUE(pending_video_decode_cb_.is_null()); EXPECT_CALL(*demuxer_, Read(_)) .WillRepeatedly(ReturnBuffer(encrypted_buffer_)); @@ -163,6 +181,16 @@ class DecryptingVideoDecoderTest : public testing::Test { EXPECT_FALSE(pending_video_decode_cb_.is_null()); } + void EnterWaitingForKeyState() { + EXPECT_CALL(*demuxer_, Read(_)) + .WillRepeatedly(ReturnBuffer(encrypted_buffer_)); + EXPECT_CALL(*decryptor_, DecryptAndDecodeVideo(_, _)) + .WillRepeatedly(RunCallback2(Decryptor::kNoKey, null_video_frame_)); + decoder_->Read(base::Bind(&DecryptingVideoDecoderTest::FrameReady, + base::Unretained(this))); + message_loop_.RunAllPending(); + } + void AbortPendingVideoDecodeCB() { if (!pending_video_decode_cb_.is_null()) { base::ResetAndReturn(&pending_video_decode_cb_).Run( @@ -182,7 +210,7 @@ class DecryptingVideoDecoderTest : public testing::Test { void Reset() { EXPECT_CALL(*decryptor_, CancelDecryptAndDecodeVideo()) - .WillOnce(Invoke( + .WillRepeatedly(Invoke( this, &DecryptingVideoDecoderTest::AbortPendingVideoDecodeCB)); decoder_->Reset(NewExpectedClosure()); @@ -191,25 +219,29 @@ class DecryptingVideoDecoderTest : public testing::Test { void Stop() { EXPECT_CALL(*decryptor_, StopVideoDecoder()) - .WillOnce(Invoke( + .WillRepeatedly(Invoke( this, &DecryptingVideoDecoderTest::AbortAllPendingCBs)); decoder_->Stop(NewExpectedClosure()); message_loop_.RunAllPending(); } + MOCK_METHOD1(RequestDecryptorNotification, + void(const DecryptingVideoDecoder::DecryptorNotificationCB&)); + MOCK_METHOD2(FrameReady, void(VideoDecoder::Status, const scoped_refptr<VideoFrame>&)); MessageLoop message_loop_; - scoped_ptr<StrictMock<MockDecryptor> > decryptor_; scoped_refptr<StrictMock<DecryptingVideoDecoder> > decoder_; + scoped_ptr<StrictMock<MockDecryptor> > decryptor_; scoped_refptr<StrictMock<MockDemuxerStream> > demuxer_; MockStatisticsCB statistics_cb_; VideoDecoderConfig config_; DemuxerStream::ReadCB pending_demuxer_read_cb_; Decryptor::DecoderInitCB pending_init_cb_; + Decryptor::KeyAddedCB key_added_cb_; Decryptor::VideoDecodeCB pending_video_decode_cb_; // Constant buffer/frames to be returned by the |demuxer_| and |decryptor_|. @@ -248,7 +280,7 @@ TEST_F(DecryptingVideoDecoderTest, Initialize_InvalidVideoConfig) { // Ensure decoder handles unsupported video configs without crashing. TEST_F(DecryptingVideoDecoderTest, Initialize_UnsupportedVideoConfig) { - EXPECT_CALL(*decryptor_, InitializeVideoDecoder(_, _)) + EXPECT_CALL(*decryptor_, InitializeVideoDecoder(_, _, _)) .WillOnce(RunCallback1(false)); VideoDecoderConfig config(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN, @@ -279,19 +311,6 @@ TEST_F(DecryptingVideoDecoderTest, DecryptAndDecode_DecodeError) { ReadAndExpectFrameReadyWith(VideoDecoder::kDecodeError, null_video_frame_); } -// Test the case where the decryptor does not have the decryption key to do -// decrypt and decode. -TEST_F(DecryptingVideoDecoderTest, DecryptAndDecode_NoKey) { - Initialize(); - - EXPECT_CALL(*demuxer_, Read(_)) - .WillRepeatedly(ReturnBuffer(encrypted_buffer_)); - EXPECT_CALL(*decryptor_, DecryptAndDecodeVideo(_, _)) - .WillRepeatedly(RunCallback2(Decryptor::kNoKey, null_video_frame_)); - - ReadAndExpectFrameReadyWith(VideoDecoder::kDecodeError, null_video_frame_); -} - // Test the case where the decryptor returns kNeedMoreData to ask for more // buffers before it can produce a frame. TEST_F(DecryptingVideoDecoderTest, DecryptAndDecode_NeedMoreData) { @@ -317,36 +336,54 @@ TEST_F(DecryptingVideoDecoderTest, DecryptAndDecode_EndOfStream) { EnterEndOfStreamState(); } -// Test resetting when decoder has initialized but has not decoded any frame. -TEST_F(DecryptingVideoDecoderTest, Reset_Initialized) { +// Test the case where the a key is added when the decryptor is in +// kWaitingForKey state. +TEST_F(DecryptingVideoDecoderTest, KeyAdded_DuringWaitingForKey) { Initialize(); - Reset(); + EnterWaitingForKeyState(); + + EXPECT_CALL(*decryptor_, DecryptAndDecodeVideo(_, _)) + .WillRepeatedly(RunCallback2(Decryptor::kSuccess, decoded_video_frame_)); + EXPECT_CALL(statistics_cb_, OnStatistics(_)); + EXPECT_CALL(*this, FrameReady(VideoDecoder::kOk, decoded_video_frame_)); + key_added_cb_.Run(); + message_loop_.RunAllPending(); } -// Test resetting when decoder has decoded single frame. -TEST_F(DecryptingVideoDecoderTest, Reset_Decoding) { +// Test the case where the a key is added when the decryptor is in +// kPendingDecode state. +TEST_F(DecryptingVideoDecoderTest, KeyAdded_DruingPendingDecode) { Initialize(); - EnterNormalDecodingState(); - Reset(); + EnterPendingDecodeState(); + + EXPECT_CALL(*decryptor_, DecryptAndDecodeVideo(_, _)) + .WillRepeatedly(RunCallback2(Decryptor::kSuccess, decoded_video_frame_)); + EXPECT_CALL(statistics_cb_, OnStatistics(_)); + EXPECT_CALL(*this, FrameReady(VideoDecoder::kOk, decoded_video_frame_)); + // The video decode callback is returned after the correct decryption key is + // added. + key_added_cb_.Run(); + base::ResetAndReturn(&pending_video_decode_cb_).Run(Decryptor::kNoKey, + null_video_frame_); + message_loop_.RunAllPending(); } -// Test resetting when decoder has hit end of stream. -TEST_F(DecryptingVideoDecoderTest, Reset_EndOfStream) { +// Test resetting when the decoder is in kIdle state but has not decoded any +// frame. +TEST_F(DecryptingVideoDecoderTest, Reset_DuringIdleAfterInitialization) { Initialize(); - EnterNormalDecodingState(); - EnterEndOfStreamState(); Reset(); } -// Test resetting after the decoder has been reset. -TEST_F(DecryptingVideoDecoderTest, Reset_AfterReset) { +// Test resetting when the decoder is in kIdle state after it has decoded one +// frame. +TEST_F(DecryptingVideoDecoderTest, Reset_DuringIdleAfterDecodedOneFrame) { Initialize(); EnterNormalDecodingState(); Reset(); - Reset(); } -// Test resetting when there is a pending read on the demuxer. +// Test resetting when the decoder is in kPendingDemuxerRead state. TEST_F(DecryptingVideoDecoderTest, Reset_DuringPendingDemuxerRead) { Initialize(); EnterPendingReadState(); @@ -359,41 +396,72 @@ TEST_F(DecryptingVideoDecoderTest, Reset_DuringPendingDemuxerRead) { message_loop_.RunAllPending(); } -// Test resetting when there is a pending video decode callback on the -// decryptor. -TEST_F(DecryptingVideoDecoderTest, Reset_DuringPendingDecryptAndDecode) { +// Test resetting when the decoder is in kPendingDecode state. +TEST_F(DecryptingVideoDecoderTest, Reset_DuringPendingDecode) { Initialize(); - EnterPendingDecryptAndDecodeState(); + EnterPendingDecodeState(); EXPECT_CALL(*this, FrameReady(VideoDecoder::kOk, IsNull())); Reset(); } -// Test stopping when decoder has initialized but has not decoded any frame. -TEST_F(DecryptingVideoDecoderTest, Stop_Initialized) { +// Test resetting when the decoder is in kWaitingForKey state. +TEST_F(DecryptingVideoDecoderTest, Reset_DuringWaitingForKey) { Initialize(); - Stop(); + EnterWaitingForKeyState(); + + EXPECT_CALL(*this, FrameReady(VideoDecoder::kOk, IsNull())); + + Reset(); } -// Test stopping when decoder has decoded single frame. -TEST_F(DecryptingVideoDecoderTest, Stop_Decoding) { +// Test resetting when the decoder has hit end of stream and is in +// kDecodeFinished state. +TEST_F(DecryptingVideoDecoderTest, Reset_AfterDecodeFinished) { Initialize(); EnterNormalDecodingState(); - Stop(); + EnterEndOfStreamState(); + Reset(); } -// Test stopping when decoder has hit end of stream. -TEST_F(DecryptingVideoDecoderTest, Stop_EndOfStream) { +// Test resetting after the decoder has been reset. +TEST_F(DecryptingVideoDecoderTest, Reset_AfterReset) { Initialize(); EnterNormalDecodingState(); - EnterEndOfStreamState(); + Reset(); + Reset(); +} + +// Test stopping when the decoder is in kDecryptorRequested state. +TEST_F(DecryptingVideoDecoderTest, Stop_DuringDecryptorRequested) { + config_.Initialize(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN, kVideoFormat, + kCodedSize, kVisibleRect, kNaturalSize, + NULL, 0, true, true); + EXPECT_CALL(*demuxer_, video_decoder_config()) + .WillRepeatedly(ReturnRef(config_)); + DecryptingVideoDecoder::DecryptorNotificationCB decryptor_notification_cb; + EXPECT_CALL(*this, RequestDecryptorNotification(_)) + .WillOnce(SaveArg<0>(&decryptor_notification_cb)); + decoder_->Initialize(demuxer_, + NewExpectedStatusCB(DECODER_ERROR_NOT_SUPPORTED), + base::Bind(&MockStatisticsCB::OnStatistics, + base::Unretained(&statistics_cb_))); + message_loop_.RunAllPending(); + // |decryptor_notification_cb| is saved but not called here. + EXPECT_FALSE(decryptor_notification_cb.is_null()); + + // During stop, RequestDecryptorNotification() should be called with a NULL + // callback to cancel the |decryptor_notification_cb|. + EXPECT_CALL(*this, RequestDecryptorNotification(IsNullCallback())) + .WillOnce(ResetAndRunCallback(&decryptor_notification_cb, + reinterpret_cast<Decryptor*>(NULL))); Stop(); } -// Test stopping when there is a pending read on the demuxer. -TEST_F(DecryptingVideoDecoderTest, Stop_DuringPendingInitialize) { - EXPECT_CALL(*decryptor_, InitializeVideoDecoder(_, _)) +// Test stopping when the decoder is in kPendingDecoderInit state. +TEST_F(DecryptingVideoDecoderTest, Stop_DuringPendingDecoderInit) { + EXPECT_CALL(*decryptor_, InitializeVideoDecoder(_, _, _)) .WillOnce(SaveArg<1>(&pending_init_cb_)); config_.Initialize(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN, kVideoFormat, @@ -405,7 +473,22 @@ TEST_F(DecryptingVideoDecoderTest, Stop_DuringPendingInitialize) { Stop(); } -// Test stopping when there is a pending read on the demuxer. +// Test stopping when the decoder is in kIdle state but has not decoded any +// frame. +TEST_F(DecryptingVideoDecoderTest, Stop_DuringIdleAfterInitialization) { + Initialize(); + Stop(); +} + +// Test stopping when the decoder is in kIdle state after it has decoded one +// frame. +TEST_F(DecryptingVideoDecoderTest, Stop_DuringIdleAfterDecodedOneFrame) { + Initialize(); + EnterNormalDecodingState(); + Stop(); +} + +// Test stopping when the decoder is in kPendingDemuxerRead state. TEST_F(DecryptingVideoDecoderTest, Stop_DuringPendingDemuxerRead) { Initialize(); EnterPendingReadState(); @@ -418,23 +501,41 @@ TEST_F(DecryptingVideoDecoderTest, Stop_DuringPendingDemuxerRead) { message_loop_.RunAllPending(); } -// Test stopping when there is a pending video decode callback on the -// decryptor. -TEST_F(DecryptingVideoDecoderTest, Stop_DuringPendingDecryptAndDecode) { +// Test stopping when the decoder is in kPendingDecode state. +TEST_F(DecryptingVideoDecoderTest, Stop_DuringPendingDecode) { + Initialize(); + EnterPendingDecodeState(); + + EXPECT_CALL(*this, FrameReady(VideoDecoder::kOk, IsNull())); + + Stop(); +} + +// Test stopping when the decoder is in kWaitingForKey state. +TEST_F(DecryptingVideoDecoderTest, Stop_DuringWaitingForKey) { Initialize(); - EnterPendingDecryptAndDecodeState(); + EnterWaitingForKeyState(); EXPECT_CALL(*this, FrameReady(VideoDecoder::kOk, IsNull())); Stop(); } +// Test stopping when the decoder has hit end of stream and is in +// kDecodeFinished state. +TEST_F(DecryptingVideoDecoderTest, Stop_AfterDecodeFinished) { + Initialize(); + EnterNormalDecodingState(); + EnterEndOfStreamState(); + Stop(); +} + // Test stopping when there is a pending reset on the decoder. // Reset is pending because it cannot complete when the video decode callback // is pending. TEST_F(DecryptingVideoDecoderTest, Stop_DuringPendingReset) { Initialize(); - EnterPendingDecryptAndDecodeState(); + EnterPendingDecodeState(); EXPECT_CALL(*decryptor_, CancelDecryptAndDecodeVideo()); EXPECT_CALL(*this, FrameReady(VideoDecoder::kOk, IsNull())); @@ -460,7 +561,7 @@ TEST_F(DecryptingVideoDecoderTest, Stop_AfterStop) { } // Test aborted read on the demuxer stream. -TEST_F(DecryptingVideoDecoderTest, AbortPendingDemuxerRead) { +TEST_F(DecryptingVideoDecoderTest, DemuxerRead_Aborted) { Initialize(); // ReturnBuffer() with NULL triggers aborted demuxer read. @@ -471,7 +572,7 @@ TEST_F(DecryptingVideoDecoderTest, AbortPendingDemuxerRead) { } // Test aborted read on the demuxer stream when the decoder is being reset. -TEST_F(DecryptingVideoDecoderTest, AbortPendingDemuxerReadDuringReset) { +TEST_F(DecryptingVideoDecoderTest, DemuxerRead_AbortedDuringReset) { Initialize(); EnterPendingReadState(); @@ -485,7 +586,7 @@ TEST_F(DecryptingVideoDecoderTest, AbortPendingDemuxerReadDuringReset) { } // Test config change on the demuxer stream. -TEST_F(DecryptingVideoDecoderTest, ConfigChanged) { +TEST_F(DecryptingVideoDecoderTest, DemuxerRead_ConfigChanged) { Initialize(); EXPECT_CALL(*demuxer_, Read(_)) |