Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(488)

Unified Diff: media/filters/decrypting_demuxer_stream_unittest.cc

Issue 11342031: Add DecryptingDemuxerStream. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: rebase Created 8 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « media/filters/decrypting_demuxer_stream.cc ('k') | media/filters/decrypting_video_decoder.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: media/filters/decrypting_demuxer_stream_unittest.cc
diff --git a/media/filters/decrypting_demuxer_stream_unittest.cc b/media/filters/decrypting_demuxer_stream_unittest.cc
new file mode 100644
index 0000000000000000000000000000000000000000..247abf3636085a8c88dd352aee13bfbd3c176d12
--- /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
« no previous file with comments | « media/filters/decrypting_demuxer_stream.cc ('k') | media/filters/decrypting_video_decoder.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698