diff options
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; |