summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorxhwang@chromium.org <xhwang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-10-10 23:36:31 +0000
committerxhwang@chromium.org <xhwang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-10-10 23:36:31 +0000
commit326dff446c95d772d1f80f086ccff86b3ff8bf25 (patch)
tree61c0459fc2f20a842ccab20fd868ec1fb5c84dc3 /media
parent1fba73c14c6cadc0f04045e04187d734695aec65 (diff)
downloadchromium_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.h6
-rw-r--r--media/base/mock_filters.h5
-rw-r--r--media/crypto/aes_decryptor.cc3
-rw-r--r--media/crypto/aes_decryptor.h3
-rw-r--r--media/filters/decrypting_video_decoder.cc223
-rw-r--r--media/filters/decrypting_video_decoder.h70
-rw-r--r--media/filters/decrypting_video_decoder_unittest.cc227
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(_))