summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--media/base/buffers.cc4
-rw-r--r--media/base/buffers.h6
-rw-r--r--media/base/data_buffer.cc29
-rw-r--r--media/base/data_buffer.h7
-rw-r--r--media/base/decrypt_config.cc20
-rw-r--r--media/base/decrypt_config.h32
-rw-r--r--media/base/test_data_util.cc12
-rw-r--r--media/base/test_data_util.h13
-rw-r--r--media/crypto/DEPS3
-rw-r--r--media/crypto/aes_decryptor.cc78
-rw-r--r--media/crypto/aes_decryptor.h32
-rw-r--r--media/crypto/aes_decryptor_unittest.cc56
-rw-r--r--media/filters/chunk_demuxer.cc1
-rw-r--r--media/filters/ffmpeg_audio_decoder_unittest.cc1
-rw-r--r--media/filters/ffmpeg_video_decoder.cc13
-rw-r--r--media/filters/ffmpeg_video_decoder.h3
-rw-r--r--media/filters/ffmpeg_video_decoder_unittest.cc69
-rw-r--r--media/filters/pipeline_integration_test.cc34
-rw-r--r--media/media.gyp6
-rw-r--r--media/webm/webm_cluster_parser.cc30
-rw-r--r--media/webm/webm_cluster_parser.h8
-rw-r--r--media/webm/webm_stream_parser.cc13
-rw-r--r--media/webm/webm_tracks_parser.cc20
-rw-r--r--media/webm/webm_tracks_parser.h3
24 files changed, 459 insertions, 34 deletions
diff --git a/media/base/buffers.cc b/media/base/buffers.cc
index 9f59c18..bfc1189 100644
--- a/media/base/buffers.cc
+++ b/media/base/buffers.cc
@@ -14,4 +14,8 @@ bool Buffer::IsEndOfStream() const {
return GetData() == NULL;
}
+const DecryptConfig* Buffer::GetDecryptConfig() const {
+ return NULL;
+}
+
} // namespace media
diff --git a/media/base/buffers.h b/media/base/buffers.h
index cbe89e1..379b175 100644
--- a/media/base/buffers.h
+++ b/media/base/buffers.h
@@ -34,6 +34,8 @@
namespace media {
+class DecryptConfig;
+
// Indicates an invalid or missing timestamp.
MEDIA_EXPORT extern inline base::TimeDelta kNoTimestamp() {
return base::TimeDelta::FromMicroseconds(kint64min);
@@ -84,7 +86,6 @@ class MEDIA_EXPORT StreamSample
DISALLOW_COPY_AND_ASSIGN(StreamSample);
};
-
class MEDIA_EXPORT Buffer : public StreamSample {
public:
// Returns a read only pointer to the buffer data.
@@ -96,6 +97,9 @@ class MEDIA_EXPORT Buffer : public StreamSample {
// If there's no data in this buffer, it represents end of stream.
virtual bool IsEndOfStream() const OVERRIDE;
+ // Return DecryptConfig if buffer is encrypted, or NULL otherwise.
+ virtual const DecryptConfig* GetDecryptConfig() const;
+
protected:
virtual ~Buffer() {}
};
diff --git a/media/base/data_buffer.cc b/media/base/data_buffer.cc
index b82de51..3cfb254 100644
--- a/media/base/data_buffer.cc
+++ b/media/base/data_buffer.cc
@@ -4,6 +4,10 @@
#include "base/logging.h"
#include "media/base/data_buffer.h"
+#include "media/base/decrypt_config.h"
+#if !defined(OS_ANDROID)
+#include "media/ffmpeg/ffmpeg_common.h"
+#endif
namespace media {
@@ -27,6 +31,23 @@ DataBuffer::DataBuffer(size_t buffer_size)
DataBuffer::~DataBuffer() {
}
+scoped_refptr<DataBuffer> DataBuffer::CopyFrom(const uint8* data,
+ size_t data_size) {
+ size_t padding_size = 0;
+#if !defined(OS_ANDROID)
+ // Why FF_INPUT_BUFFER_PADDING_SIZE? FFmpeg assumes all input buffers are
+ // padded with this value.
+ padding_size = FF_INPUT_BUFFER_PADDING_SIZE;
+#endif
+
+ scoped_refptr<DataBuffer> data_buffer(
+ new DataBuffer(data_size + padding_size));
+ memcpy(data_buffer->data_.get(), data, data_size);
+ memset(data_buffer->data_.get() + data_size, 0, padding_size);
+ data_buffer->SetDataSize(data_size);
+ return data_buffer;
+}
+
const uint8* DataBuffer::GetData() const {
return data_.get();
}
@@ -35,6 +56,10 @@ size_t DataBuffer::GetDataSize() const {
return data_size_;
}
+const DecryptConfig* DataBuffer::GetDecryptConfig() const {
+ return decrypt_config_.get();
+}
+
uint8* DataBuffer::GetWritableData() {
return data_.get();
}
@@ -49,4 +74,8 @@ size_t DataBuffer::GetBufferSize() const {
return buffer_size_;
}
+void DataBuffer::SetDecryptConfig(scoped_ptr<DecryptConfig> decrypt_config) {
+ decrypt_config_ = decrypt_config.Pass();
+}
+
} // namespace media
diff --git a/media/base/data_buffer.h b/media/base/data_buffer.h
index 6c5c8a9..651088f 100644
--- a/media/base/data_buffer.h
+++ b/media/base/data_buffer.h
@@ -24,9 +24,13 @@ class MEDIA_EXPORT DataBuffer : public Buffer {
// set to a NULL ptr.
explicit DataBuffer(size_t buffer_size);
+ // Create a DataBuffer whose |data_| is copied from |data|.
+ static scoped_refptr<DataBuffer> CopyFrom(const uint8* data, size_t size);
+
// Buffer implementation.
virtual const uint8* GetData() const OVERRIDE;
virtual size_t GetDataSize() const OVERRIDE;
+ virtual const DecryptConfig* GetDecryptConfig() const OVERRIDE;
// Returns a read-write pointer to the buffer data.
virtual uint8* GetWritableData();
@@ -38,6 +42,8 @@ class MEDIA_EXPORT DataBuffer : public Buffer {
// Returns the size of the underlying buffer.
virtual size_t GetBufferSize() const;
+ virtual void SetDecryptConfig(scoped_ptr<DecryptConfig> decrypt_config);
+
protected:
virtual ~DataBuffer();
@@ -45,6 +51,7 @@ class MEDIA_EXPORT DataBuffer : public Buffer {
scoped_array<uint8> data_;
size_t buffer_size_;
size_t data_size_;
+ scoped_ptr<DecryptConfig> decrypt_config_;
DISALLOW_COPY_AND_ASSIGN(DataBuffer);
};
diff --git a/media/base/decrypt_config.cc b/media/base/decrypt_config.cc
new file mode 100644
index 0000000..9ae5f19
--- /dev/null
+++ b/media/base/decrypt_config.cc
@@ -0,0 +1,20 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/base/decrypt_config.h"
+
+#include "base/logging.h"
+
+namespace media {
+
+DecryptConfig::DecryptConfig(const uint8* key_id, int key_id_size)
+ : key_id_size_(key_id_size) {
+ CHECK_GT(key_id_size, 0);
+ key_id_.reset(new uint8[key_id_size]);
+ memcpy(key_id_.get(), key_id, key_id_size);
+}
+
+DecryptConfig::~DecryptConfig() {}
+
+} // namespace media
diff --git a/media/base/decrypt_config.h b/media/base/decrypt_config.h
new file mode 100644
index 0000000..5fca787
--- /dev/null
+++ b/media/base/decrypt_config.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_BASE_DECRYPT_CONFIG_H_
+#define MEDIA_BASE_DECRYPT_CONFIG_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+// Contains all information that a decryptor needs to decrypt.
+class MEDIA_EXPORT DecryptConfig {
+ public:
+ explicit DecryptConfig(const uint8* key_id, int key_id_size);
+ ~DecryptConfig();
+
+ const uint8* key_id() const { return key_id_.get(); }
+ int key_id_size() const { return key_id_size_; }
+
+ private:
+ scoped_array<uint8> key_id_;
+ int key_id_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(DecryptConfig);
+};
+
+} // namespace media
+
+#endif // MEDIA_BASE_DECRYPT_CONFIG_H_
diff --git a/media/base/test_data_util.cc b/media/base/test_data_util.cc
index 15284da..bb08175 100644
--- a/media/base/test_data_util.cc
+++ b/media/base/test_data_util.cc
@@ -7,6 +7,8 @@
#include "base/file_util.h"
#include "base/logging.h"
#include "base/path_service.h"
+#include "media/base/buffers.h"
+#include "media/base/data_buffer.h"
#include "media/ffmpeg/ffmpeg_common.h"
namespace media {
@@ -51,11 +53,19 @@ void ReadTestDataFile(const std::string& name, scoped_array<uint8>* buffer,
*size = file_size;
}
-void ReadTestDataFile(const std::string& name, scoped_refptr<Buffer>* buffer) {
+void ReadTestDataFile(const std::string& name,
+ scoped_refptr<DataBuffer>* buffer) {
scoped_array<uint8> buf;
int buf_size;
ReadTestDataFile(name, &buf, &buf_size);
*buffer = new DataBuffer(buf.Pass(), buf_size);
}
+void ReadTestDataFile(const std::string& name,
+ scoped_refptr<Buffer>* buffer) {
+ scoped_refptr<DataBuffer> data_buffer;
+ ReadTestDataFile(name, &data_buffer);
+ *buffer = data_buffer;
+}
+
} // namespace media
diff --git a/media/base/test_data_util.h b/media/base/test_data_util.h
index 142780b..a132875 100644
--- a/media/base/test_data_util.h
+++ b/media/base/test_data_util.h
@@ -8,11 +8,14 @@
#include <string>
#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
-#include "media/base/data_buffer.h"
namespace media {
+class Buffer;
+class DataBuffer;
+
// Returns a URL path for a file in the media/test/data directory.
std::string GetTestDataURL(const std::string& name);
@@ -27,6 +30,14 @@ void ReadTestDataFile(const std::string& name,
int* size);
// Reads a test file from media/test/data directory and stores it in
+// a DataBuffer.
+//
+// |name| - The name of the file.
+// |buffer| - The contents of the file.
+void ReadTestDataFile(const std::string& name,
+ scoped_refptr<DataBuffer>* buffer);
+
+// Reads a test file from media/test/data directory and stores it in
// a Buffer.
//
// |name| - The name of the file.
diff --git a/media/crypto/DEPS b/media/crypto/DEPS
new file mode 100644
index 0000000..4ef4138
--- /dev/null
+++ b/media/crypto/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+crypto",
+]
diff --git a/media/crypto/aes_decryptor.cc b/media/crypto/aes_decryptor.cc
new file mode 100644
index 0000000..8e83daf
--- /dev/null
+++ b/media/crypto/aes_decryptor.cc
@@ -0,0 +1,78 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/crypto/aes_decryptor.h"
+
+#include <string>
+
+#include "base/logging.h"
+#include "crypto/encryptor.h"
+#include "crypto/symmetric_key.h"
+#include "media/base/buffers.h"
+#include "media/base/data_buffer.h"
+#include "media/base/decrypt_config.h"
+
+namespace media {
+
+static const char* kInitialCounter = "0000000000000000";
+
+// Decrypts |input| using |raw_key|, which is the binary data for the decryption
+// key.
+// Return a scoped_refptr to a Buffer object with the decrypted data on success.
+// Return a scoped_refptr to NULL if the data could not be decrypted.
+// TODO(xhwang): Both the input and output are copied! Any performance concern?
+static scoped_refptr<Buffer> DecryptData(const Buffer& input,
+ const uint8* raw_key,
+ int raw_key_size) {
+ CHECK(raw_key && raw_key_size > 0);
+ CHECK(input.GetDataSize());
+
+ scoped_ptr<crypto::SymmetricKey> key(crypto::SymmetricKey::Import(
+ crypto::SymmetricKey::AES,
+ std::string(reinterpret_cast<const char*>(raw_key), raw_key_size)));
+ if (!key.get()) {
+ DVLOG(1) << "Could not import key.";
+ return NULL;
+ }
+
+ // Initialize encryption data.
+ // The IV must be exactly as long as the cipher block size.
+ crypto::Encryptor encryptor;
+ if (!encryptor.Init(key.get(), crypto::Encryptor::CBC, kInitialCounter)) {
+ DVLOG(1) << "Could not initialize encryptor.";
+ return NULL;
+ }
+
+ std::string decrypt_text;
+ std::string encrypted_text(reinterpret_cast<const char*>(input.GetData()),
+ input.GetDataSize());
+ if (!encryptor.Decrypt(encrypted_text, &decrypt_text)) {
+ DVLOG(1) << "Could not decrypt data.";
+ return NULL;
+ }
+
+ return DataBuffer::CopyFrom(
+ reinterpret_cast<const uint8*>(decrypt_text.data()),
+ decrypt_text.size());
+}
+
+AesDecryptor::AesDecryptor() {}
+
+scoped_refptr<Buffer> AesDecryptor::Decrypt(
+ const scoped_refptr<Buffer>& encrypted) {
+ CHECK(encrypted->GetDecryptConfig());
+
+ // For now, the key is the key ID.
+ const uint8* key = encrypted->GetDecryptConfig()->key_id();
+ int key_size = encrypted->GetDecryptConfig()->key_id_size();
+ scoped_refptr<Buffer> decrypted = DecryptData(*encrypted, key, key_size);
+ if (decrypted) {
+ decrypted->SetTimestamp(encrypted->GetTimestamp());
+ decrypted->SetDuration(encrypted->GetDuration());
+ }
+
+ return decrypted;
+}
+
+} // namespace media
diff --git a/media/crypto/aes_decryptor.h b/media/crypto/aes_decryptor.h
new file mode 100644
index 0000000..32d6802
--- /dev/null
+++ b/media/crypto/aes_decryptor.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_CRYPTO_AES_DECRYPTOR_H_
+#define MEDIA_CRYPTO_AES_DECRYPTOR_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+class Buffer;
+
+// Decrypts AES encrypted buffer into unencrypted buffer.
+class MEDIA_EXPORT AesDecryptor {
+ public:
+ AesDecryptor();
+
+ // Decrypt |input| buffer. The |input| should not be NULL.
+ // Return a Buffer that contains decrypted data if decryption succeeded.
+ // Return NULL if decryption failed.
+ scoped_refptr<Buffer> Decrypt(const scoped_refptr<Buffer>& input);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AesDecryptor);
+};
+
+} // namespace media
+
+#endif // MEDIA_CRYPTO_AES_DECRYPTOR_H_
diff --git a/media/crypto/aes_decryptor_unittest.cc b/media/crypto/aes_decryptor_unittest.cc
new file mode 100644
index 0000000..0143bd0
--- /dev/null
+++ b/media/crypto/aes_decryptor_unittest.cc
@@ -0,0 +1,56 @@
+// 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 "media/base/data_buffer.h"
+#include "media/base/decrypt_config.h"
+#include "media/crypto/aes_decryptor.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+// |kEncryptedDataHex| is encrypted from |kOriginalData| using |kRawKey|, whose
+// length is |kKeySize|. Modifying any of these independently would fail the
+// test.
+static const char kOriginalData[] = "Original data.";
+static const int kEncryptedDataSize = 16;
+static const unsigned char kEncryptedData[] =
+ "\x82\x3A\x76\x92\xEC\x7F\xF8\x85\xEC\x23\x52\xFB\x19\xB1\xB9\x09";
+static const int kKeySize = 16;
+static const unsigned char kRawKey[] = "A wonderful key!";
+static const unsigned char kWrongKey[] = "I'm a wrong key.";
+
+class AesDecryptorTest : public testing::Test {
+ public:
+ AesDecryptorTest() {
+ encrypted_data_ = DataBuffer::CopyFrom(kEncryptedData, kEncryptedDataSize);
+ }
+
+ protected:
+ void SetKey(const uint8* key, int key_size) {
+ encrypted_data_->SetDecryptConfig(
+ scoped_ptr<DecryptConfig>(new DecryptConfig(key, key_size)));
+ }
+
+ scoped_refptr<DataBuffer> encrypted_data_;
+ AesDecryptor decryptor_;
+};
+
+TEST_F(AesDecryptorTest, NormalDecryption) {
+ SetKey(kRawKey, kKeySize);
+ scoped_refptr<Buffer> decrypted_data = decryptor_.Decrypt(encrypted_data_);
+ ASSERT_TRUE(decrypted_data.get());
+ size_t data_length = sizeof(kOriginalData) - 1;
+ ASSERT_EQ(data_length, decrypted_data->GetDataSize());
+ ASSERT_EQ(0, memcmp(kOriginalData, decrypted_data->GetData(), data_length));
+}
+
+TEST_F(AesDecryptorTest, WrongKey) {
+ SetKey(kWrongKey, kKeySize);
+ scoped_refptr<Buffer> decrypted_data = decryptor_.Decrypt(encrypted_data_);
+ EXPECT_FALSE(decrypted_data.get());
+}
+
+} // media
diff --git a/media/filters/chunk_demuxer.cc b/media/filters/chunk_demuxer.cc
index 70b0f46..d8041b1 100644
--- a/media/filters/chunk_demuxer.cc
+++ b/media/filters/chunk_demuxer.cc
@@ -711,7 +711,6 @@ bool ChunkDemuxer::OnNewVideoConfig(const VideoDecoderConfig& config) {
return true;
}
-
bool ChunkDemuxer::OnAudioBuffers(const BufferQueue& buffers) {
if (!audio_.get())
return false;
diff --git a/media/filters/ffmpeg_audio_decoder_unittest.cc b/media/filters/ffmpeg_audio_decoder_unittest.cc
index f580e79..eb634c7 100644
--- a/media/filters/ffmpeg_audio_decoder_unittest.cc
+++ b/media/filters/ffmpeg_audio_decoder_unittest.cc
@@ -7,6 +7,7 @@
#include "base/bind.h"
#include "base/message_loop.h"
#include "base/stringprintf.h"
+#include "media/base/data_buffer.h"
#include "media/base/mock_callback.h"
#include "media/base/mock_filters.h"
#include "media/base/test_data_util.h"
diff --git a/media/filters/ffmpeg_video_decoder.cc b/media/filters/ffmpeg_video_decoder.cc
index 61932ff..fe53f35 100644
--- a/media/filters/ffmpeg_video_decoder.cc
+++ b/media/filters/ffmpeg_video_decoder.cc
@@ -256,8 +256,19 @@ void FFmpegVideoDecoder::DoDecodeBuffer(const scoped_refptr<Buffer>& buffer) {
state_ = kFlushCodec;
}
+ scoped_refptr<Buffer> unencrypted_buffer = buffer;
+ if (buffer->GetDecryptConfig() && buffer->GetDataSize()) {
+ unencrypted_buffer = decryptor_.Decrypt(buffer);
+ if (!unencrypted_buffer || !unencrypted_buffer->GetDataSize()) {
+ state_ = kDecodeFinished;
+ DeliverFrame(VideoFrame::CreateEmptyFrame());
+ host()->SetError(PIPELINE_ERROR_DECODE);
+ return;
+ }
+ }
+
scoped_refptr<VideoFrame> video_frame;
- if (!Decode(buffer, &video_frame)) {
+ if (!Decode(unencrypted_buffer, &video_frame)) {
state_ = kDecodeFinished;
DeliverFrame(VideoFrame::CreateEmptyFrame());
host()->SetError(PIPELINE_ERROR_DECODE);
diff --git a/media/filters/ffmpeg_video_decoder.h b/media/filters/ffmpeg_video_decoder.h
index 19c939c..1e9f74d 100644
--- a/media/filters/ffmpeg_video_decoder.h
+++ b/media/filters/ffmpeg_video_decoder.h
@@ -9,6 +9,7 @@
#include "base/memory/scoped_ptr.h"
#include "media/base/filters.h"
+#include "media/crypto/aes_decryptor.h"
#include "ui/gfx/size.h"
class MessageLoop;
@@ -90,6 +91,8 @@ class MEDIA_EXPORT FFmpegVideoDecoder : public VideoDecoder {
// Pointer to the demuxer stream that will feed us compressed buffers.
scoped_refptr<DemuxerStream> demuxer_stream_;
+ AesDecryptor decryptor_;
+
DISALLOW_COPY_AND_ASSIGN(FFmpegVideoDecoder);
};
diff --git a/media/filters/ffmpeg_video_decoder_unittest.cc b/media/filters/ffmpeg_video_decoder_unittest.cc
index 939e689..ec583ab 100644
--- a/media/filters/ffmpeg_video_decoder_unittest.cc
+++ b/media/filters/ffmpeg_video_decoder_unittest.cc
@@ -9,6 +9,7 @@
#include "base/memory/singleton.h"
#include "base/string_util.h"
#include "media/base/data_buffer.h"
+#include "media/base/decrypt_config.h"
#include "media/base/filters.h"
#include "media/base/limits.h"
#include "media/base/mock_callback.h"
@@ -36,6 +37,9 @@ static const gfx::Rect kVisibleRect(320, 240);
static const gfx::Size kNaturalSize(522, 288);
static const AVRational kFrameRate = { 100, 1 };
static const AVRational kAspectRatio = { 1, 1 };
+static const int kKeySize = 16;
+static const unsigned char kRawKey[] = "A wonderful key!";
+static const unsigned char kWrongKey[] = "I'm a wrong key.";
ACTION_P(ReturnBuffer, buffer) {
arg0.Run(buffer);
@@ -57,6 +61,8 @@ class FFmpegVideoDecoderTest : public testing::Test {
end_of_stream_buffer_ = new DataBuffer(0);
ReadTestDataFile("vp8-I-frame-320x240", &i_frame_buffer_);
ReadTestDataFile("vp8-corrupt-I-frame", &corrupt_i_frame_buffer_);
+ ReadTestDataFile("vp8-encrypted-I-frame-320x240",
+ &encrypted_i_frame_buffer_);
config_.Initialize(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN,
kVideoFormat, kCodedSize, kVisibleRect,
@@ -203,6 +209,7 @@ class FFmpegVideoDecoderTest : public testing::Test {
scoped_refptr<Buffer> end_of_stream_buffer_;
scoped_refptr<Buffer> i_frame_buffer_;
scoped_refptr<Buffer> corrupt_i_frame_buffer_;
+ scoped_refptr<DataBuffer> encrypted_i_frame_buffer_;
// Used for generating timestamped buffers.
std::deque<int64> timestamps_;
@@ -285,7 +292,7 @@ TEST_F(FFmpegVideoDecoderTest, DecodeFrame_0ByteFrame) {
ASSERT_TRUE(video_frame_a);
ASSERT_TRUE(video_frame_b);
- ASSERT_TRUE(video_frame_a);
+ ASSERT_TRUE(video_frame_c);
EXPECT_FALSE(video_frame_a->IsEndOfStream());
EXPECT_FALSE(video_frame_b->IsEndOfStream());
@@ -354,6 +361,66 @@ TEST_F(FFmpegVideoDecoderTest, DecodeFrame_SmallerHeight) {
DecodeIFrameThenTestFile("vp8-I-frame-320x120", 320, 120);
}
+TEST_F(FFmpegVideoDecoderTest, DecodeEncryptedFrame_Normal) {
+ Initialize();
+
+ // Simulate decoding a single encrypted frame.
+ encrypted_i_frame_buffer_->SetDecryptConfig(
+ scoped_ptr<DecryptConfig>(new DecryptConfig(kRawKey, kKeySize)));
+ scoped_refptr<VideoFrame> video_frame;
+ DecodeSingleFrame(encrypted_i_frame_buffer_, &video_frame);
+
+ ASSERT_TRUE(video_frame);
+ EXPECT_FALSE(video_frame->IsEndOfStream());
+}
+
+// No key was provided with the encrypted frame. The decoder will mistakenly
+// assume the frame is not encrypted. The behavior should be the same as
+// decoding a corrupted frame.
+TEST_F(FFmpegVideoDecoderTest, DecodeEncryptedFrame_NoKey) {
+ Initialize();
+
+ EXPECT_CALL(*demuxer_, Read(_))
+ .WillRepeatedly(ReturnBuffer(encrypted_i_frame_buffer_));
+
+ // The error is only raised on the second decode attempt, so we expect at
+ // least one successful decode but we don't expect FrameReady() to be
+ // executed as an error is raised instead.
+ EXPECT_CALL(statistics_callback_, OnStatistics(_));
+ EXPECT_CALL(host_, SetError(PIPELINE_ERROR_DECODE));
+
+ // Our read should still get satisfied with end of stream frame during an
+ // error.
+ scoped_refptr<VideoFrame> video_frame;
+ Read(&video_frame);
+ ASSERT_TRUE(video_frame);
+ EXPECT_TRUE(video_frame->IsEndOfStream());
+
+ message_loop_.RunAllPending();
+}
+
+// Test decrypting a encrypted frame with a wrong key.
+TEST_F(FFmpegVideoDecoderTest, DecodeEncryptedFrame_WrongKey) {
+ Initialize();
+
+ encrypted_i_frame_buffer_->SetDecryptConfig(
+ scoped_ptr<DecryptConfig>(new DecryptConfig(kWrongKey, kKeySize)));
+
+ EXPECT_CALL(*demuxer_, Read(_))
+ .WillRepeatedly(ReturnBuffer(encrypted_i_frame_buffer_));
+
+ EXPECT_CALL(host_, SetError(PIPELINE_ERROR_DECODE));
+
+ // Our read should still get satisfied with end of stream frame during an
+ // error.
+ scoped_refptr<VideoFrame> video_frame;
+ Read(&video_frame);
+ ASSERT_TRUE(video_frame);
+ EXPECT_TRUE(video_frame->IsEndOfStream());
+
+ message_loop_.RunAllPending();
+}
+
// Test pausing when decoder has initialized but not decoded.
TEST_F(FFmpegVideoDecoderTest, Pause_Initialized) {
Initialize();
diff --git a/media/filters/pipeline_integration_test.cc b/media/filters/pipeline_integration_test.cc
index bfcb4f3..d52009d 100644
--- a/media/filters/pipeline_integration_test.cc
+++ b/media/filters/pipeline_integration_test.cc
@@ -79,6 +79,16 @@ class PipelineIntegrationTest
: public testing::Test,
public PipelineIntegrationTestBase {
public:
+ void StartPipelineWithMediaSource(MockMediaSource& source) {
+ pipeline_->Start(
+ CreateFilterCollection(&source), source.url(),
+ base::Bind(&PipelineIntegrationTest::OnEnded, base::Unretained(this)),
+ base::Bind(&PipelineIntegrationTest::OnError, base::Unretained(this)),
+ NetworkEventCB(), QuitOnStatusCB(PIPELINE_OK));
+
+ message_loop_.Run();
+ }
+
// Verifies that seeking works properly for ChunkDemuxer when the
// seek happens while there is a pending read on the ChunkDemuxer
// and no data is available.
@@ -89,15 +99,7 @@ class PipelineIntegrationTest
int seek_file_position,
int seek_append_size) {
MockMediaSource source(filename, initial_append_size);
-
- pipeline_->Start(CreateFilterCollection(&source), source.url(),
- base::Bind(&PipelineIntegrationTest::OnEnded,
- base::Unretained(this)),
- base::Bind(&PipelineIntegrationTest::OnError,
- base::Unretained(this)),
- NetworkEventCB(),
- QuitOnStatusCB(PIPELINE_OK));
- message_loop_.Run();
+ StartPipelineWithMediaSource(source);
if (pipeline_status_ != PIPELINE_OK)
return false;
@@ -127,6 +129,20 @@ TEST_F(PipelineIntegrationTest, BasicPlayback) {
ASSERT_TRUE(WaitUntilOnEnded());
}
+TEST_F(PipelineIntegrationTest, EncryptedPlayback) {
+ MockMediaSource source("bear-320x240-encrypted.webm", 219726);
+ StartPipelineWithMediaSource(source);
+
+ source.EndOfStream();
+ ASSERT_EQ(PIPELINE_OK, pipeline_status_);
+
+ Play();
+
+ ASSERT_TRUE(WaitUntilOnEnded());
+ source.Abort();
+ Stop();
+}
+
// TODO(acolwell): Fix flakiness http://crbug.com/109875
TEST_F(PipelineIntegrationTest, DISABLED_SeekWhilePaused) {
ASSERT_TRUE(Start(GetTestDataURL("bear-320x240.webm"), PIPELINE_OK));
diff --git a/media/media.gyp b/media/media.gyp
index d229ed3..30d85ed 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -17,6 +17,7 @@
'../base/base.gyp:base',
'../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
'../build/temp_gyp/googleurl.gyp:googleurl',
+ '../crypto/crypto.gyp:crypto',
'../third_party/openmax/openmax.gyp:il',
'../ui/ui.gyp:ui',
],
@@ -115,6 +116,8 @@
'base/data_buffer.h',
'base/data_source.cc',
'base/data_source.h',
+ 'base/decrypt_config.cc',
+ 'base/decrypt_config.h',
'base/demuxer.cc',
'base/demuxer.h',
'base/demuxer_factory.cc',
@@ -160,6 +163,8 @@
'base/video_frame.h',
'base/video_util.cc',
'base/video_util.h',
+ 'crypto/aes_decryptor.cc',
+ 'crypto/aes_decryptor.h',
'ffmpeg/ffmpeg_common.cc',
'ffmpeg/ffmpeg_common.h',
'ffmpeg/file_protocol.cc',
@@ -601,6 +606,7 @@
'base/video_frame_unittest.cc',
'base/video_util_unittest.cc',
'base/yuv_convert_unittest.cc',
+ 'crypto/aes_decryptor_unittest.cc',
'ffmpeg/ffmpeg_common_unittest.cc',
'filters/audio_renderer_algorithm_base_unittest.cc',
'filters/audio_renderer_base_unittest.cc',
diff --git a/media/webm/webm_cluster_parser.cc b/media/webm/webm_cluster_parser.cc
index 92f1dc7..c34cd8d 100644
--- a/media/webm/webm_cluster_parser.cc
+++ b/media/webm/webm_cluster_parser.cc
@@ -6,33 +6,33 @@
#include "base/logging.h"
#include "media/base/data_buffer.h"
-#include "media/ffmpeg/ffmpeg_common.h"
+#include "media/base/decrypt_config.h"
#include "media/webm/webm_constants.h"
namespace media {
-static Buffer* CreateBuffer(const uint8* data, size_t size) {
- // Why FF_INPUT_BUFFER_PADDING_SIZE? FFmpeg assumes all input buffers are
- // padded with this value.
- scoped_array<uint8> buf(new uint8[size + FF_INPUT_BUFFER_PADDING_SIZE]);
- memcpy(buf.get(), data, size);
- memset(buf.get() + size, 0, FF_INPUT_BUFFER_PADDING_SIZE);
- return new DataBuffer(buf.Pass(), size);
-}
-
WebMClusterParser::WebMClusterParser(int64 timecode_scale,
int audio_track_num,
base::TimeDelta audio_default_duration,
int video_track_num,
- base::TimeDelta video_default_duration)
+ base::TimeDelta video_default_duration,
+ const uint8* video_encryption_key_id,
+ int video_encryption_key_id_size)
: timecode_multiplier_(timecode_scale / 1000.0),
audio_track_num_(audio_track_num),
audio_default_duration_(audio_default_duration),
video_track_num_(video_track_num),
video_default_duration_(video_default_duration),
+ video_encryption_key_id_size_(video_encryption_key_id_size),
parser_(kWebMIdCluster, this),
last_block_timecode_(-1),
cluster_timecode_(-1) {
+ CHECK_GE(video_encryption_key_id_size, 0);
+ if (video_encryption_key_id_size > 0) {
+ video_encryption_key_id_.reset(new uint8[video_encryption_key_id_size]);
+ memcpy(video_encryption_key_id_.get(), video_encryption_key_id,
+ video_encryption_key_id_size);
+ }
}
WebMClusterParser::~WebMClusterParser() {}
@@ -115,7 +115,13 @@ bool WebMClusterParser::OnSimpleBlock(int track_num, int timecode,
base::TimeDelta timestamp = base::TimeDelta::FromMicroseconds(
(cluster_timecode_ + timecode) * timecode_multiplier_);
- scoped_refptr<Buffer> buffer(CreateBuffer(data, size));
+ scoped_refptr<DataBuffer> buffer = DataBuffer::CopyFrom(data, size);
+
+ if (track_num == video_track_num_ && video_encryption_key_id_.get()) {
+ buffer->SetDecryptConfig(scoped_ptr<DecryptConfig>(new DecryptConfig(
+ video_encryption_key_id_.get(), video_encryption_key_id_size_)));
+ }
+
buffer->SetTimestamp(timestamp);
BufferQueue* queue = NULL;
diff --git a/media/webm/webm_cluster_parser.h b/media/webm/webm_cluster_parser.h
index 2db3755..8646289 100644
--- a/media/webm/webm_cluster_parser.h
+++ b/media/webm/webm_cluster_parser.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// 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.
@@ -22,7 +22,9 @@ class WebMClusterParser : public WebMParserClient {
int audio_track_num,
base::TimeDelta audio_default_duration,
int video_track_num,
- base::TimeDelta video_default_duration);
+ base::TimeDelta video_default_duration,
+ const uint8* video_encryption_key_id,
+ int video_encryption_key_id_size);
virtual ~WebMClusterParser();
// Resets the parser state so it can accept a new cluster.
@@ -52,6 +54,8 @@ class WebMClusterParser : public WebMParserClient {
base::TimeDelta audio_default_duration_;
int video_track_num_;
base::TimeDelta video_default_duration_;
+ scoped_array<uint8> video_encryption_key_id_;
+ int video_encryption_key_id_size_;
WebMListParser parser_;
diff --git a/media/webm/webm_stream_parser.cc b/media/webm/webm_stream_parser.cc
index 036f7e7b0..d986deb 100644
--- a/media/webm/webm_stream_parser.cc
+++ b/media/webm/webm_stream_parser.cc
@@ -11,6 +11,7 @@
#include "media/filters/in_memory_url_protocol.h"
#include "media/webm/webm_cluster_parser.h"
#include "media/webm/webm_constants.h"
+#include "media/webm/webm_content_encodings.h"
#include "media/webm/webm_info_parser.h"
#include "media/webm/webm_tracks_parser.h"
@@ -33,6 +34,10 @@ class FFmpegConfigHelper {
const VideoDecoderConfig& video_config() const;
private:
+ static const uint8 kWebMHeader[];
+ static const int kSegmentSizeOffset;
+ static const uint8 kEmptyCluster[];
+
AVFormatContext* CreateFormatContext(const uint8* data, int size);
bool SetupStreamConfigs();
@@ -50,10 +55,6 @@ class FFmpegConfigHelper {
// DestroyAVFormatContext() in the destructor.
AVFormatContext* format_context_;
- static const uint8 kWebMHeader[];
- static const int kSegmentSizeOffset;
- static const uint8 kEmptyCluster[];
-
DISALLOW_COPY_AND_ASSIGN(FFmpegConfigHelper);
};
@@ -305,7 +306,9 @@ int WebMStreamParser::ParseInfoAndTracks(const uint8* data, int size) {
tracks_parser.audio_track_num(),
tracks_parser.audio_default_duration(),
tracks_parser.video_track_num(),
- tracks_parser.video_default_duration()));
+ tracks_parser.video_default_duration(),
+ tracks_parser.video_encryption_key_id(),
+ tracks_parser.video_encryption_key_id_size()));
ChangeState(PARSING_CLUSTERS);
init_cb_.Run(true, duration);
diff --git a/media/webm/webm_tracks_parser.cc b/media/webm/webm_tracks_parser.cc
index 84550b7..7aa85d5 100644
--- a/media/webm/webm_tracks_parser.cc
+++ b/media/webm/webm_tracks_parser.cc
@@ -5,7 +5,9 @@
#include "media/webm/webm_tracks_parser.h"
#include "base/logging.h"
+#include "base/string_util.h"
#include "media/webm/webm_constants.h"
+#include "media/webm/webm_content_encodings.h"
namespace media {
@@ -24,6 +26,24 @@ WebMTracksParser::WebMTracksParser(int64 timecode_scale)
WebMTracksParser::~WebMTracksParser() {}
+const uint8* WebMTracksParser::video_encryption_key_id() const {
+ if (!video_content_encodings_client_.get())
+ return NULL;
+
+ DCHECK(!video_content_encodings_client_->content_encodings().empty());
+ return video_content_encodings_client_->content_encodings()[0]->
+ encryption_key_id();
+}
+
+int WebMTracksParser::video_encryption_key_id_size() const {
+ if (!video_content_encodings_client_.get())
+ return 0;
+
+ DCHECK(!video_content_encodings_client_->content_encodings().empty());
+ return video_content_encodings_client_->content_encodings()[0]->
+ encryption_key_id_size();
+}
+
int WebMTracksParser::Parse(const uint8* buf, int size) {
track_type_ =-1;
track_num_ = -1;
diff --git a/media/webm/webm_tracks_parser.h b/media/webm/webm_tracks_parser.h
index efe45d9..db2c928 100644
--- a/media/webm/webm_tracks_parser.h
+++ b/media/webm/webm_tracks_parser.h
@@ -36,6 +36,9 @@ class WebMTracksParser : public WebMParserClient {
return video_default_duration_;
}
+ const uint8* video_encryption_key_id() const;
+ int video_encryption_key_id_size() const;
+
private:
// WebMParserClient methods
virtual WebMParserClient* OnListStart(int id) OVERRIDE;