diff options
24 files changed, 667 insertions, 202 deletions
diff --git a/media/base/mock_filters.cc b/media/base/mock_filters.cc index a5977a4..b10d42a 100644 --- a/media/base/mock_filters.cc +++ b/media/base/mock_filters.cc @@ -41,6 +41,26 @@ MockAudioRenderer::MockAudioRenderer() {} MockAudioRenderer::~MockAudioRenderer() {} +MockDecryptorClient::MockDecryptorClient() {} + +MockDecryptorClient::~MockDecryptorClient() {} + +void MockDecryptorClient::KeyMessage(const std::string& key_system, + const std::string& session_id, + scoped_array<uint8> message, + int message_length, + const std::string& default_url) { + KeyMessageMock(key_system, session_id, message.get(), message_length, + default_url); +} + +void MockDecryptorClient::NeedKey(const std::string& key_system, + const std::string& session_id, + scoped_array<uint8> init_data, + int init_data_length) { + NeedKeyMock(key_system, session_id, init_data.get(), init_data_length); +} + MockFilterCollection::MockFilterCollection() : demuxer_(new MockDemuxer()), video_decoder_(new MockVideoDecoder()), diff --git a/media/base/mock_filters.h b/media/base/mock_filters.h index 626392c..cf8fa28 100644 --- a/media/base/mock_filters.h +++ b/media/base/mock_filters.h @@ -26,6 +26,7 @@ #include "media/base/video_decoder_config.h" #include "media/base/video_frame.h" #include "media/base/video_renderer.h" +#include "media/crypto/decryptor_client.h" #include "testing/gmock/include/gmock/gmock.h" namespace media { @@ -209,6 +210,41 @@ class MockAudioRenderer : public AudioRenderer { DISALLOW_COPY_AND_ASSIGN(MockAudioRenderer); }; +class MockDecryptorClient : public DecryptorClient { + public: + MockDecryptorClient(); + virtual ~MockDecryptorClient(); + + MOCK_METHOD2(KeyAdded, void(const std::string&, const std::string&)); + MOCK_METHOD4(KeyError, void(const std::string&, const std::string&, + AesDecryptor::KeyError, int)); + // TODO(xhwang): This is a workaround of the issue that move-only parameters + // are not supported in mocked methods. Remove this when the issue is fixed + // (http://code.google.com/p/googletest/issues/detail?id=395) or when we use + // std::string instead of scoped_array<uint8> (http://crbug.com/130689). + MOCK_METHOD5(KeyMessageMock, void(const std::string& key_system, + const std::string& session_id, + const uint8* message, + int message_length, + const std::string& default_url)); + MOCK_METHOD4(NeedKeyMock, void(const std::string& key_system, + const std::string& session_id, + const uint8* init_data, + int init_data_length)); + virtual void KeyMessage(const std::string& key_system, + const std::string& session_id, + scoped_array<uint8> message, + int message_length, + const std::string& default_url) OVERRIDE; + virtual void NeedKey(const std::string& key_system, + const std::string& session_id, + scoped_array<uint8> init_data, + int init_data_length) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(MockDecryptorClient); +}; + // FilterFactory that returns canned instances of mock filters. You can set // expectations on the filters and then pass the collection into a pipeline. class MockFilterCollection { diff --git a/media/base/stream_parser.h b/media/base/stream_parser.h index cdf146d..3ad3cd7 100644 --- a/media/base/stream_parser.h +++ b/media/base/stream_parser.h @@ -63,7 +63,7 @@ class MEDIA_EXPORT StreamParser { // Return value - True indicates that the initialization data is accepted. // False if something was wrong with the initialization data // and a parsing error should be signalled. - typedef base::Callback<bool(scoped_array<uint8>, int)> KeyNeededCB; + typedef base::Callback<bool(scoped_array<uint8>, int)> NeedKeyCB; // Initialize the parser with necessary callbacks. Must be called before any // data is passed to Parse(). |init_cb| will be called once enough data has @@ -73,7 +73,7 @@ class MEDIA_EXPORT StreamParser { const NewConfigCB& config_cb, const NewBuffersCB& audio_cb, const NewBuffersCB& video_cb, - const KeyNeededCB& key_needed_cb, + const NeedKeyCB& need_key_cb, const NewMediaSegmentCB& new_segment_cb) = 0; // Called when a seek occurs. This flushes the current parser state diff --git a/media/crypto/aes_decryptor.cc b/media/crypto/aes_decryptor.cc index cbb75b3..49275fb 100644 --- a/media/crypto/aes_decryptor.cc +++ b/media/crypto/aes_decryptor.cc @@ -6,17 +6,21 @@ #include "base/logging.h" #include "base/stl_util.h" +#include "base/string_number_conversions.h" #include "base/string_piece.h" #include "crypto/encryptor.h" #include "crypto/symmetric_key.h" #include "media/base/decoder_buffer.h" #include "media/base/decrypt_config.h" +#include "media/crypto/decryptor_client.h" namespace media { // TODO(xhwang): Get real IV from frames. static const char kInitialCounter[] = "0000000000000000"; +uint32 AesDecryptor::next_session_id_ = 1; + // Decrypt |input| using |key|. // Return a DecoderBuffer with the decrypted data if decryption succeeded. // Return NULL if decryption failed. @@ -48,35 +52,83 @@ static scoped_refptr<DecoderBuffer> DecryptData(const DecoderBuffer& input, decrypted_text.size()); } -AesDecryptor::AesDecryptor() {} +AesDecryptor::AesDecryptor(DecryptorClient* client) + : client_(client) { +} AesDecryptor::~AesDecryptor() { STLDeleteValues(&key_map_); } -void AesDecryptor::AddKey(const uint8* key_id, int key_id_size, - const uint8* key, int key_size) { - CHECK(key_id && key); - CHECK_GT(key_id_size, 0); - CHECK_GT(key_size, 0); +void AesDecryptor::GenerateKeyRequest(const std::string& key_system, + const uint8* init_data, + int init_data_length) { + std::string session_id_string(base::UintToString(next_session_id_++)); - std::string key_id_string(reinterpret_cast<const char*>(key_id), key_id_size); - std::string key_string(reinterpret_cast<const char*>(key) , key_size); + // For now, just fire the event with the |init_data| as the request. + int message_length = init_data_length; + scoped_array<uint8> message(new uint8[message_length]); + memcpy(message.get(), init_data, message_length); + + client_->KeyMessage(key_system, session_id_string, + message.Pass(), message_length, ""); +} + +void AesDecryptor::AddKey(const std::string& key_system, + const uint8* key, + int key_length, + const uint8* init_data, + int init_data_length, + const std::string& session_id) { + CHECK(key); + CHECK_GT(key_length, 0); + + // TODO(xhwang): Add |session_id| check after we figure out how: + // https://www.w3.org/Bugs/Public/show_bug.cgi?id=16550 + + const int kSupportedKeyLength = 16; // 128-bit key. + if (key_length != kSupportedKeyLength) { + DVLOG(1) << "Invalid key length: " << key_length; + client_->KeyError(key_system, session_id, kUnknownError, 0); + return; + } + // TODO(xhwang): Fix the decryptor to accept no |init_data|. See + // http://crbug.com/123265. Until then, ensure a non-empty value is passed. + static const uint8 kDummyInitData[1] = { 0 }; + if (!init_data) { + init_data = kDummyInitData; + init_data_length = arraysize(kDummyInitData); + } + + // TODO(xhwang): For now, use |init_data| for key ID. Make this more spec + // compliant later (http://crbug.com/123262, http://crbug.com/123265). + std::string key_id_string(reinterpret_cast<const char*>(init_data), + init_data_length); + std::string key_string(reinterpret_cast<const char*>(key) , key_length); crypto::SymmetricKey* symmetric_key = crypto::SymmetricKey::Import( crypto::SymmetricKey::AES, key_string); if (!symmetric_key) { DVLOG(1) << "Could not import key."; + client_->KeyError(key_system, session_id, kUnknownError, 0); return; } - base::AutoLock auto_lock(lock_); - KeyMap::iterator found = key_map_.find(key_id_string); - if (found != key_map_.end()) { - delete found->second; - key_map_.erase(found); + { + base::AutoLock auto_lock(key_map_lock_); + KeyMap::iterator found = key_map_.find(key_id_string); + if (found != key_map_.end()) { + delete found->second; + key_map_.erase(found); + } + key_map_[key_id_string] = symmetric_key; } - key_map_[key_id_string] = symmetric_key; + + client_->KeyAdded(key_system, session_id); +} + +void AesDecryptor::CancelKeyRequest(const std::string& key_system, + const std::string& session_id) { } scoped_refptr<DecoderBuffer> AesDecryptor::Decrypt( @@ -90,7 +142,7 @@ scoped_refptr<DecoderBuffer> AesDecryptor::Decrypt( crypto::SymmetricKey* key = NULL; { - base::AutoLock auto_lock(lock_); + base::AutoLock auto_lock(key_map_lock_); KeyMap::const_iterator found = key_map_.find(key_id_string); if (found == key_map_.end()) { DVLOG(1) << "Could not find a matching key for given key ID."; diff --git a/media/crypto/aes_decryptor.h b/media/crypto/aes_decryptor.h index d62528f..3471c53 100644 --- a/media/crypto/aes_decryptor.h +++ b/media/crypto/aes_decryptor.h @@ -20,23 +20,55 @@ class SymmetricKey; namespace media { class DecoderBuffer; +class DecryptorClient; // Decrypts AES encrypted buffer into unencrypted buffer. +// All public methods other than Decrypt() will be called on the renderer +// thread. Therefore, these calls should be fast and nonblocking, with key +// events fired asynchronously. Decrypt() will be called on the (video/audio) +// decoder thread synchronously. class MEDIA_EXPORT AesDecryptor { public: - AesDecryptor(); + enum KeyError { + kUnknownError = 1, + kClientError, + kServiceError, + kOutputError, + kHardwareChangeError, + kDomainError + }; + + // The AesDecryptor does not take ownership of the |client|. The |client| + // must be valid throughout the lifetime of the AesDecryptor. + explicit AesDecryptor(DecryptorClient* client); ~AesDecryptor(); - // Add a |key_id| and |key| pair to the key system. The key is not limited to - // a decryption key. It can be any data that the key system accepts, such as - // a license. If multiple calls of this function set different keys for the - // same |key_id|, the older key will be replaced by the newer key. - void AddKey(const uint8* key_id, int key_id_size, - const uint8* key, int key_size); + // Generates a key request. The result of this call will be reported via the + // client's KeyMessage() or KeyError() methods. + void GenerateKeyRequest(const std::string& key_system, + const uint8* init_data, + int init_data_length); + + // Adds a |key| to the key system. The key is not limited to a decryption key. + // It can be any data that the key system accepts, such as a license. + // If multiple calls of this function set different keys for the same + // |key_id|, the older key will be replaced by the newer key. + // The result of this call will be reported via the client's KeyAdded(), + // KeyMessage() or KeyError() methods. + void AddKey(const std::string& key_system, + const uint8* key, + int key_length, + const uint8* init_data, + int init_data_length, + const std::string& session_id); + + // Cancels the key request specified by |session_id|. + void CancelKeyRequest(const std::string& key_system, + const std::string& session_id); - // Decrypt |input| buffer. The |input| should not be NULL. - // Return a DecoderBuffer with the decrypted data if decryption succeeded. - // Return NULL if decryption failed. + // Decrypts the |input| buffer, which should not be NULL. + // Returns a DecoderBuffer with the decrypted data if decryption succeeded. + // Returns NULL if decryption failed. scoped_refptr<DecoderBuffer> Decrypt( const scoped_refptr<DecoderBuffer>& input); @@ -44,8 +76,19 @@ class MEDIA_EXPORT AesDecryptor { // KeyMap owns the crypto::SymmetricKey* and must delete them when they are // not needed any more. typedef base::hash_map<std::string, crypto::SymmetricKey*> KeyMap; - KeyMap key_map_; - base::Lock lock_; + + // Since only Decrypt() is called off the renderer thread, we only need to + // protect |key_map_|, the only member variable that is shared between + // Decrypt() and other methods. + KeyMap key_map_; // Protected by the |key_map_lock_|. + base::Lock key_map_lock_; // Protects the |key_map_|. + + DecryptorClient* client_; + + // Make session ID unique per renderer by making it static. + // TODO(xhwang): Make session ID more strictly defined if needed: + // https://www.w3.org/Bugs/Public/show_bug.cgi?id=16739#c0 + static uint32 next_session_id_; DISALLOW_COPY_AND_ASSIGN(AesDecryptor); }; diff --git a/media/crypto/aes_decryptor_unittest.cc b/media/crypto/aes_decryptor_unittest.cc index 0b33f61..69cca61 100644 --- a/media/crypto/aes_decryptor_unittest.cc +++ b/media/crypto/aes_decryptor_unittest.cc @@ -4,45 +4,93 @@ #include <string> +#include "base/basictypes.h" #include "media/base/decoder_buffer.h" #include "media/base/decrypt_config.h" +#include "media/base/mock_filters.h" #include "media/crypto/aes_decryptor.h" #include "testing/gtest/include/gtest/gtest.h" +using ::testing::_; +using ::testing::Gt; +using ::testing::NotNull; +using ::testing::SaveArg; +using ::testing::StrNe; + namespace media { -// |kEncryptedData| is encrypted from |kOriginalData| using |kRightKey|, 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 kRightKey[] = "A wonderful key!"; -static const unsigned char kWrongKey[] = "I'm a wrong key."; -static const int kKeyIdSize = 9; -static const unsigned char kKeyId1[] = "Key ID 1."; -static const unsigned char kKeyId2[] = "Key ID 2."; +static const char kClearKeySystem[] = "org.w3.clearkey"; +static const uint8 kInitData[] = { 0x69, 0x6e, 0x69, 0x74 }; +// |kEncryptedData| is encrypted from |kOriginalData| using |kRightKey|. +// Modifying any of these independently would fail the test. +static const uint8 kOriginalData[] = { + 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, + 0x20, 0x64, 0x61, 0x74, 0x61, 0x2e +}; +static const uint8 kEncryptedData[] = { + 0x82, 0x3A, 0x76, 0x92, 0xEC, 0x7F, 0xF8, 0x85, + 0xEC, 0x23, 0x52, 0xFB, 0x19, 0xB1, 0xB9, 0x09 +}; +static const uint8 kRightKey[] = { + 0x41, 0x20, 0x77, 0x6f, 0x6e, 0x64, 0x65, 0x72, + 0x66, 0x75, 0x6c, 0x20, 0x6b, 0x65, 0x79, 0x21 +}; +static const uint8 kWrongKey[] = { + 0x49, 0x27, 0x6d, 0x20, 0x61, 0x20, 0x77, 0x72, + 0x6f, 0x6e, 0x67, 0x20, 0x6b, 0x65, 0x79, 0x2e +}; +static const uint8 kWrongSizedKey[] = { 0x20, 0x20 }; +static const uint8 kKeyId1[] = { + 0x4b, 0x65, 0x79, 0x20, 0x49, 0x44, 0x20, 0x31 +}; +static const uint8 kKeyId2[] = { + 0x4b, 0x65, 0x79, 0x20, 0x49, 0x44, 0x20, 0x32 +}; class AesDecryptorTest : public testing::Test { public: - AesDecryptorTest() { - encrypted_data_ = DecoderBuffer::CopyFrom( - kEncryptedData, kEncryptedDataSize); + AesDecryptorTest() : decryptor_(&client_) { + encrypted_data_ = DecoderBuffer::CopyFrom(kEncryptedData, + arraysize(kEncryptedData)); } protected: - void SetKeyIdForEncryptedData(const uint8* key_id, int key_id_size) { + void GenerateKeyRequest() { + EXPECT_CALL(client_, KeyMessageMock(kClearKeySystem, StrNe(std::string()), + NotNull(), Gt(0), "")) + .WillOnce(SaveArg<1>(&session_id_string_)); + decryptor_.GenerateKeyRequest(kClearKeySystem, + kInitData, arraysize(kInitData)); + } + + template <int KeyIdSize, int KeySize> + void AddKeyAndExpectToSucceed(const uint8 (&key_id)[KeyIdSize], + const uint8 (&key)[KeySize]) { + EXPECT_CALL(client_, KeyAdded(kClearKeySystem, session_id_string_)); + decryptor_.AddKey(kClearKeySystem, key, KeySize, key_id, KeyIdSize, + session_id_string_); + } + + template <int KeyIdSize, int KeySize> + void AddKeyAndExpectToFail(const uint8 (&key_id)[KeyIdSize], + const uint8 (&key)[KeySize]) { + EXPECT_CALL(client_, KeyError(kClearKeySystem, session_id_string_, + AesDecryptor::kUnknownError, 0)); + decryptor_.AddKey(kClearKeySystem, key, KeySize, key_id, KeyIdSize, + session_id_string_); + } + + template <int KeyIdSize> + void SetKeyIdForEncryptedData(const uint8 (&key_id)[KeyIdSize]) { encrypted_data_->SetDecryptConfig( - scoped_ptr<DecryptConfig>(new DecryptConfig(key_id, key_id_size))); + scoped_ptr<DecryptConfig>(new DecryptConfig(key_id, KeyIdSize))); } void DecryptAndExpectToSucceed() { scoped_refptr<DecoderBuffer> decrypted = decryptor_.Decrypt(encrypted_data_); ASSERT_TRUE(decrypted); - int data_length = sizeof(kOriginalData) - 1; + int data_length = sizeof(kOriginalData); ASSERT_EQ(data_length, decrypted->GetDataSize()); EXPECT_EQ(0, memcmp(kOriginalData, decrypted->GetData(), data_length)); } @@ -54,34 +102,45 @@ class AesDecryptorTest : public testing::Test { } scoped_refptr<DecoderBuffer> encrypted_data_; + MockDecryptorClient client_; AesDecryptor decryptor_; + std::string session_id_string_; }; TEST_F(AesDecryptorTest, NormalDecryption) { - decryptor_.AddKey(kKeyId1, kKeyIdSize, kRightKey, kKeySize); - SetKeyIdForEncryptedData(kKeyId1, kKeyIdSize); + GenerateKeyRequest(); + AddKeyAndExpectToSucceed(kKeyId1, kRightKey); + SetKeyIdForEncryptedData(kKeyId1); ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed()); } TEST_F(AesDecryptorTest, WrongKey) { - decryptor_.AddKey(kKeyId1, kKeyIdSize, kWrongKey, kKeySize); - SetKeyIdForEncryptedData(kKeyId1, kKeyIdSize); + GenerateKeyRequest(); + AddKeyAndExpectToSucceed(kKeyId1, kWrongKey); + SetKeyIdForEncryptedData(kKeyId1); ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToFail()); } TEST_F(AesDecryptorTest, MultipleKeys) { - decryptor_.AddKey(kKeyId1, kKeyIdSize, kRightKey, kKeySize); - decryptor_.AddKey(kKeyId2, kKeyIdSize, kWrongKey, kKeySize); - SetKeyIdForEncryptedData(kKeyId1, kKeyIdSize); + GenerateKeyRequest(); + AddKeyAndExpectToSucceed(kKeyId1, kRightKey); + AddKeyAndExpectToSucceed(kKeyId2, kWrongKey); + SetKeyIdForEncryptedData(kKeyId1); ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed()); } TEST_F(AesDecryptorTest, KeyReplacement) { - SetKeyIdForEncryptedData(kKeyId1, kKeyIdSize); - decryptor_.AddKey(kKeyId1, kKeyIdSize, kWrongKey, kKeySize); + GenerateKeyRequest(); + SetKeyIdForEncryptedData(kKeyId1); + AddKeyAndExpectToSucceed(kKeyId1, kWrongKey); ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToFail()); - decryptor_.AddKey(kKeyId1, kKeyIdSize, kRightKey, kKeySize); + AddKeyAndExpectToSucceed(kKeyId1, kRightKey); ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed()); } +TEST_F(AesDecryptorTest, WrongSizedKey) { + GenerateKeyRequest(); + AddKeyAndExpectToFail(kKeyId1, kWrongSizedKey); +} + } // media diff --git a/media/crypto/decryptor_client.h b/media/crypto/decryptor_client.h new file mode 100644 index 0000000..c15a2f0 --- /dev/null +++ b/media/crypto/decryptor_client.h @@ -0,0 +1,50 @@ +// 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_DECRYPTOR_CLIENT_H_ +#define MEDIA_CRYPTO_DECRYPTOR_CLIENT_H_ + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "media/crypto/aes_decryptor.h" + +namespace media { + +// Interface used by a decryptor to fire key events. +// See: http://dvcs.w3.org/hg/html-media/raw-file/tip/encrypted-media/encrypted-media.html#event-summary +class DecryptorClient { + public: + // Signals that a key has been added. + virtual void KeyAdded(const std::string& key_system, + const std::string& session_id) = 0; + + // Signals that a key error occurred. The |system_code| is key + // system-dependent. For clear key system, the |system_code| is always zero. + virtual void KeyError(const std::string& key_system, + const std::string& session_id, + AesDecryptor::KeyError error_code, + int system_code) = 0; + + // Signals that a key message has been generated. + virtual void KeyMessage(const std::string& key_system, + const std::string& session_id, + scoped_array<uint8> message, + int message_length, + const std::string& default_url) = 0; + + // Signals that a key is needed for decryption. |key_system| and |session_id| + // can be empty if the key system has not been selected. + virtual void NeedKey(const std::string& key_system, + const std::string& session_id, + scoped_array<uint8> init_data, + int init_data_length) = 0; + + protected: + virtual ~DecryptorClient() {} +}; + +} // namespace media + +#endif // MEDIA_CRYPTO_DECRYPTOR_CLIENT_H_ diff --git a/media/filters/chunk_demuxer.cc b/media/filters/chunk_demuxer.cc index 2cc1bbc..1c87590 100644 --- a/media/filters/chunk_demuxer.cc +++ b/media/filters/chunk_demuxer.cc @@ -605,7 +605,7 @@ ChunkDemuxer::Status ChunkDemuxer::AddId(const std::string& id, has_audio, has_video), audio_cb, video_cb, - base::Bind(&ChunkDemuxer::OnKeyNeeded, base::Unretained(this)), + base::Bind(&ChunkDemuxer::OnNeedKey, base::Unretained(this)), base::Bind(&ChunkDemuxer::OnNewMediaSegment, base::Unretained(this), id)); stream_parser_map_[id] = stream_parser.release(); @@ -1017,9 +1017,9 @@ bool ChunkDemuxer::OnVideoBuffers(const StreamParser::BufferQueue& buffers) { return video_->Append(buffers); } -bool ChunkDemuxer::OnKeyNeeded(scoped_array<uint8> init_data, - int init_data_size) { - client_->KeyNeeded(init_data.Pass(), init_data_size); +bool ChunkDemuxer::OnNeedKey(scoped_array<uint8> init_data, + int init_data_size) { + client_->DemuxerNeedKey(init_data.Pass(), init_data_size); return true; } diff --git a/media/filters/chunk_demuxer.h b/media/filters/chunk_demuxer.h index 5945526..a03d50f 100644 --- a/media/filters/chunk_demuxer.h +++ b/media/filters/chunk_demuxer.h @@ -117,7 +117,7 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer { const VideoDecoderConfig& video_config); bool OnAudioBuffers(const StreamParser::BufferQueue& buffers); bool OnVideoBuffers(const StreamParser::BufferQueue& buffers); - bool OnKeyNeeded(scoped_array<uint8> init_data, int init_data_size); + bool OnNeedKey(scoped_array<uint8> init_data, int init_data_size); void OnNewMediaSegment(const std::string& source_id, base::TimeDelta start_timestamp); diff --git a/media/filters/chunk_demuxer_client.h b/media/filters/chunk_demuxer_client.h index 3fbe6e3..4ac2f78 100644 --- a/media/filters/chunk_demuxer_client.h +++ b/media/filters/chunk_demuxer_client.h @@ -25,7 +25,11 @@ class ChunkDemuxerClient { // A decryption key associated with |init_data| may be needed to decrypt the // media being demuxed before decoding. Note that the demuxing itself does not // need decryption. - virtual void KeyNeeded(scoped_array<uint8> init_data, int init_data_size) = 0; + virtual void DemuxerNeedKey(scoped_array<uint8> init_data, + int init_data_size) = 0; + + protected: + virtual ~ChunkDemuxerClient() {} }; } // namespace media diff --git a/media/filters/chunk_demuxer_unittest.cc b/media/filters/chunk_demuxer_unittest.cc index 941bbcb..d55dff4 100644 --- a/media/filters/chunk_demuxer_unittest.cc +++ b/media/filters/chunk_demuxer_unittest.cc @@ -90,11 +90,12 @@ class MockChunkDemuxerClient : public ChunkDemuxerClient { MOCK_METHOD1(DemuxerOpened, void(ChunkDemuxer* demuxer)); MOCK_METHOD0(DemuxerClosed, void()); // TODO(xhwang): This is a workaround of the issue that move-only parameters - // are not supported in mocked methods. Remove this when the issue is fixed. - // See http://code.google.com/p/googletest/issues/detail?id=395 - MOCK_METHOD2(KeyNeededMock, void(const uint8* init_data, int init_data_size)); - void KeyNeeded(scoped_array<uint8> init_data, int init_data_size) { - KeyNeededMock(init_data.get(), init_data_size); + // are not supported in mocked methods. Remove this when the issue is fixed + // (http://code.google.com/p/googletest/issues/detail?id=395) or when we use + // std::string instead of scoped_array<uint8> (http://crbug.com/130689). + MOCK_METHOD2(NeedKeyMock, void(const uint8* init_data, int init_data_size)); + void DemuxerNeedKey(scoped_array<uint8> init_data, int init_data_size) { + NeedKeyMock(init_data.get(), init_data_size); } private: @@ -590,7 +591,7 @@ TEST_F(ChunkDemuxerTest, TestInit) { client_.reset(new MockChunkDemuxerClient()); demuxer_ = new ChunkDemuxer(client_.get()); if (has_video && video_content_encoded) - EXPECT_CALL(*client_, KeyNeededMock(NotNull(), 16)); + EXPECT_CALL(*client_, NeedKeyMock(NotNull(), 16)); ASSERT_TRUE(InitDemuxer(has_audio, has_video, video_content_encoded)); @@ -896,7 +897,7 @@ TEST_F(ChunkDemuxerTest, TestNetworkErrorEndOfStream) { // Read() behavior. class EndOfStreamHelper { public: - EndOfStreamHelper(const scoped_refptr<Demuxer> demuxer) + explicit EndOfStreamHelper(const scoped_refptr<Demuxer> demuxer) : demuxer_(demuxer), audio_read_done_(false), video_read_done_(false) { diff --git a/media/filters/ffmpeg_video_decoder_unittest.cc b/media/filters/ffmpeg_video_decoder_unittest.cc index 2b4fc94..d2e6908 100644 --- a/media/filters/ffmpeg_video_decoder_unittest.cc +++ b/media/filters/ffmpeg_video_decoder_unittest.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include <deque> +#include <string> #include "base/bind.h" #include "base/message_loop.h" @@ -24,10 +25,13 @@ using ::testing::_; using ::testing::AnyNumber; +using ::testing::Gt; using ::testing::Invoke; +using ::testing::NotNull; using ::testing::ReturnRef; using ::testing::SaveArg; using ::testing::StrictMock; +using ::testing::StrNe; namespace media { @@ -37,9 +41,17 @@ 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 unsigned char kRawKey[] = "A wonderful key!"; -static const unsigned char kWrongKey[] = "I'm a wrong key."; -static const unsigned char kKeyId[] = "A normal key ID."; +static const char kClearKeySystem[] = "org.w3.clearkey"; +static const uint8 kInitData[] = { 0x69, 0x6e, 0x69, 0x74 }; +static const uint8 kRightKey[] = { + 0x41, 0x20, 0x77, 0x6f, 0x6e, 0x64, 0x65, 0x72, + 0x66, 0x75, 0x6c, 0x20, 0x6b, 0x65, 0x79, 0x21 +}; +static const uint8 kWrongKey[] = { + 0x49, 0x27, 0x6d, 0x20, 0x61, 0x20, 0x77, 0x72, + 0x6f, 0x6e, 0x67, 0x20, 0x6b, 0x65, 0x79, 0x2e +}; +static const uint8 kKeyId[] = { 0x4b, 0x65, 0x79, 0x20, 0x49, 0x44 }; ACTION_P(ReturnBuffer, buffer) { arg0.Run(buffer); @@ -48,7 +60,7 @@ ACTION_P(ReturnBuffer, buffer) { class FFmpegVideoDecoderTest : public testing::Test { public: FFmpegVideoDecoderTest() - : decryptor_(new AesDecryptor()), + : decryptor_(new AesDecryptor(&decryptor_client_)), decoder_(new FFmpegVideoDecoder(base::Bind(&Identity<MessageLoop*>, &message_loop_))), demuxer_(new StrictMock<MockDemuxerStream>()), @@ -202,6 +214,7 @@ class FFmpegVideoDecoderTest : public testing::Test { scoped_refptr<StrictMock<MockDemuxerStream> > demuxer_; MockStatisticsCB statistics_cb_; VideoDecoderConfig config_; + MockDecryptorClient decryptor_client_; VideoDecoder::ReadCB read_cb_; @@ -375,12 +388,20 @@ TEST_F(FFmpegVideoDecoderTest, DecodeFrame_SmallerHeight) { TEST_F(FFmpegVideoDecoderTest, DecodeEncryptedFrame_Normal) { Initialize(); - decryptor_->AddKey(kKeyId, arraysize(kKeyId) - 1, - kRawKey, arraysize(kRawKey) - 1); + std::string sessing_id_string; + EXPECT_CALL(decryptor_client_, + KeyMessageMock(kClearKeySystem, StrNe(std::string()), + NotNull(), Gt(0), "")) + .WillOnce(SaveArg<1>(&sessing_id_string)); + decryptor_->GenerateKeyRequest(kClearKeySystem, + kInitData, arraysize(kInitData)); + EXPECT_CALL(decryptor_client_, KeyAdded(kClearKeySystem, sessing_id_string)); + decryptor_->AddKey(kClearKeySystem, kRightKey, arraysize(kRightKey), + kKeyId, arraysize(kKeyId), sessing_id_string); // Simulate decoding a single encrypted frame. encrypted_i_frame_buffer_->SetDecryptConfig(scoped_ptr<DecryptConfig>( - new DecryptConfig(kKeyId, arraysize(kKeyId) - 1))); + new DecryptConfig(kKeyId, arraysize(kKeyId)))); VideoDecoder::DecoderStatus status; scoped_refptr<VideoFrame> video_frame; DecodeSingleFrame(encrypted_i_frame_buffer_, &status, &video_frame); @@ -396,7 +417,7 @@ TEST_F(FFmpegVideoDecoderTest, DecodeEncryptedFrame_NoKey) { // Simulate decoding a single encrypted frame. encrypted_i_frame_buffer_->SetDecryptConfig(scoped_ptr<DecryptConfig>( - new DecryptConfig(kKeyId, arraysize(kKeyId) - 1))); + new DecryptConfig(kKeyId, arraysize(kKeyId)))); EXPECT_CALL(*demuxer_, Read(_)) .WillRepeatedly(ReturnBuffer(encrypted_i_frame_buffer_)); @@ -415,11 +436,12 @@ TEST_F(FFmpegVideoDecoderTest, DecodeEncryptedFrame_NoKey) { // Test decrypting an encrypted frame with a wrong key. TEST_F(FFmpegVideoDecoderTest, DecodeEncryptedFrame_WrongKey) { Initialize(); - decryptor_->AddKey(kKeyId, arraysize(kKeyId) - 1, - kWrongKey, arraysize(kWrongKey) - 1); + EXPECT_CALL(decryptor_client_, KeyAdded("", "")); + decryptor_->AddKey("", kWrongKey, arraysize(kWrongKey), + kKeyId, arraysize(kKeyId), ""); encrypted_i_frame_buffer_->SetDecryptConfig(scoped_ptr<DecryptConfig>( - new DecryptConfig(kKeyId, arraysize(kKeyId) - 1))); + new DecryptConfig(kKeyId, arraysize(kKeyId)))); EXPECT_CALL(*demuxer_, Read(_)) .WillRepeatedly(ReturnBuffer(encrypted_i_frame_buffer_)); diff --git a/media/filters/pipeline_integration_test.cc b/media/filters/pipeline_integration_test.cc index 2ad7b2a..c0a5b67 100644 --- a/media/filters/pipeline_integration_test.cc +++ b/media/filters/pipeline_integration_test.cc @@ -6,16 +6,17 @@ #include "base/bind.h" #include "media/base/decoder_buffer.h" +#include "media/base/mock_filters.h" #include "media/base/test_data_util.h" +#include "media/crypto/aes_decryptor.h" +#include "media/crypto/decryptor_client.h" #include "media/filters/chunk_demuxer_client.h" namespace media { -// Key ID of the video track in test file "bear-320x240-encrypted.webm". -static const unsigned char kKeyId[] = - "\x11\xa5\x18\x37\xc4\x73\x84\x03\xe5\xe6\x57\xed\x8e\x06\xd9\x7c"; - -static const char* kSourceId = "SourceId"; +static const char kSourceId[] = "SourceId"; +static const char kClearKeySystem[] = "org.w3.clearkey"; +static const uint8 kInitData[] = { 0x69, 0x6e, 0x69, 0x74 }; // Helper class that emulates calls made on the ChunkDemuxer by the // Media Source API. @@ -36,11 +37,8 @@ class MockMediaSource : public ChunkDemuxerClient { virtual ~MockMediaSource() {} - void set_decryptor(AesDecryptor* decryptor) { - decryptor_ = decryptor; - } - AesDecryptor* decryptor() const { - return decryptor_; + void set_decryptor_client(DecryptorClient* decryptor_client) { + decryptor_client_ = decryptor_client; } const std::string& url() const { return url_; } @@ -95,14 +93,12 @@ class MockMediaSource : public ChunkDemuxerClient { chunk_demuxer_ = NULL; } - virtual void KeyNeeded(scoped_array<uint8> init_data, int init_data_size) { + virtual void DemuxerNeedKey(scoped_array<uint8> init_data, + int init_data_size) { DCHECK(init_data.get()); DCHECK_EQ(init_data_size, 16); - DCHECK(decryptor()); - // In test file bear-320x240-encrypted.webm, the decryption key is equal to - // |init_data|. - decryptor()->AddKey(init_data.get(), init_data_size, - init_data.get(), init_data_size); + DCHECK(decryptor_client_); + decryptor_client_->NeedKey("", "", init_data.Pass(), init_data_size); } private: @@ -113,22 +109,103 @@ class MockMediaSource : public ChunkDemuxerClient { bool has_audio_; bool has_video_; scoped_refptr<ChunkDemuxer> chunk_demuxer_; - AesDecryptor* decryptor_; + DecryptorClient* decryptor_client_; +}; + +class MockDecryptorClientImpl : public DecryptorClient { + public: + MockDecryptorClientImpl() : decryptor_(this) {} + + AesDecryptor* decryptor() { + return &decryptor_; + } + + // DecryptorClient implementation. + virtual void KeyAdded(const std::string& key_system, + const std::string& session_id) { + EXPECT_EQ(kClearKeySystem, key_system); + EXPECT_FALSE(session_id.empty()); + } + + virtual void KeyError(const std::string& key_system, + const std::string& session_id, + AesDecryptor::KeyError error_code, + int system_code) { + NOTIMPLEMENTED(); + } + + virtual void KeyMessage(const std::string& key_system, + const std::string& session_id, + scoped_array<uint8> message, + int message_length, + const std::string& default_url) { + EXPECT_EQ(kClearKeySystem, key_system); + EXPECT_FALSE(session_id.empty()); + EXPECT_TRUE(message.get()); + EXPECT_GT(message_length, 0); + + current_key_system_ = key_system; + current_session_id_ = session_id; + } + + virtual void NeedKey(const std::string& key_system, + const std::string& session_id, + scoped_array<uint8> init_data, + int init_data_length) { + current_key_system_ = key_system; + current_session_id_ = session_id; + + // When NeedKey is called from the demuxer, the |key_system| will be empty. + // In this case, we need to call GenerateKeyRequest() to initialize a + // session (which will call KeyMessage). + if (current_key_system_.empty()) { + DCHECK(current_session_id_.empty()); + decryptor_.GenerateKeyRequest(kClearKeySystem, + kInitData, arraysize(kInitData)); + } + + EXPECT_FALSE(current_key_system_.empty()); + EXPECT_FALSE(current_session_id_.empty()); + // In test file bear-320x240-encrypted.webm, the decryption key is equal to + // |init_data|. + decryptor_.AddKey(current_key_system_, init_data.get(), init_data_length, + init_data.get(), init_data_length, current_session_id_); + } + + private: + AesDecryptor decryptor_; + std::string current_key_system_; + std::string current_session_id_; }; class PipelineIntegrationTest : public testing::Test, public PipelineIntegrationTestBase { public: - void StartPipelineWithMediaSource(MockMediaSource& source) { + void StartPipelineWithMediaSource(MockMediaSource* source) { + pipeline_->Start( + CreateFilterCollection(source), + base::Bind(&PipelineIntegrationTest::OnEnded, base::Unretained(this)), + base::Bind(&PipelineIntegrationTest::OnError, base::Unretained(this)), + QuitOnStatusCB(PIPELINE_OK)); + + ASSERT_TRUE(decoder_.get()); + + message_loop_.Run(); + } + + void StartPipelineWithEncryptedMedia( + MockMediaSource* source, + MockDecryptorClientImpl* encrypted_media) { pipeline_->Start( - CreateFilterCollection(&source), + CreateFilterCollection(source), base::Bind(&PipelineIntegrationTest::OnEnded, base::Unretained(this)), base::Bind(&PipelineIntegrationTest::OnError, base::Unretained(this)), QuitOnStatusCB(PIPELINE_OK)); ASSERT_TRUE(decoder_.get()); - source.set_decryptor(decryptor_.get()); + decoder_->set_decryptor(encrypted_media->decryptor()); + source->set_decryptor_client(encrypted_media); message_loop_.Run(); } @@ -145,7 +222,7 @@ class PipelineIntegrationTest bool has_audio, bool has_video) { MockMediaSource source(filename, initial_append_size, has_audio, has_video); - StartPipelineWithMediaSource(source); + StartPipelineWithMediaSource(&source); if (pipeline_status_ != PIPELINE_OK) return false; @@ -188,7 +265,8 @@ TEST_F(PipelineIntegrationTest, BasicPlaybackHashed) { TEST_F(PipelineIntegrationTest, EncryptedPlayback) { MockMediaSource source("bear-320x240-encrypted.webm", 219726, true, true); - StartPipelineWithMediaSource(source); + MockDecryptorClientImpl encrypted_media; + StartPipelineWithEncryptedMedia(&source, &encrypted_media); source.EndOfStream(); ASSERT_EQ(PIPELINE_OK, pipeline_status_); diff --git a/media/filters/pipeline_integration_test_base.cc b/media/filters/pipeline_integration_test_base.cc index 565d3b7..13e596b 100644 --- a/media/filters/pipeline_integration_test_base.cc +++ b/media/filters/pipeline_integration_test_base.cc @@ -6,7 +6,6 @@ #include "base/bind.h" #include "media/base/media_log.h" -#include "media/crypto/aes_decryptor.h" #include "media/filters/audio_renderer_impl.h" #include "media/filters/chunk_demuxer.h" #include "media/filters/ffmpeg_audio_decoder.h" @@ -186,7 +185,6 @@ PipelineIntegrationTestBase::CreateFilterCollection( const scoped_refptr<Demuxer>& demuxer) { scoped_ptr<FilterCollection> collection(new FilterCollection()); collection->SetDemuxer(demuxer); - decryptor_.reset(new AesDecryptor()); collection->AddAudioDecoder(new FFmpegAudioDecoder( base::Bind(&MessageLoopFactory::GetMessageLoop, base::Unretained(message_loop_factory_.get()), @@ -195,7 +193,6 @@ PipelineIntegrationTestBase::CreateFilterCollection( base::Bind(&MessageLoopFactory::GetMessageLoop, base::Unretained(message_loop_factory_.get()), "VideoDecoderThread")); - decoder_->set_decryptor(decryptor_.get()); collection->AddVideoDecoder(decoder_); // Disable frame dropping if hashing is enabled. renderer_ = new VideoRendererBase( diff --git a/media/filters/pipeline_integration_test_base.h b/media/filters/pipeline_integration_test_base.h index 4fcb33b..1268b39 100644 --- a/media/filters/pipeline_integration_test_base.h +++ b/media/filters/pipeline_integration_test_base.h @@ -18,8 +18,6 @@ namespace media { -class AesDecryptor; - // Empty MD5 hash string. Used to verify empty audio or video tracks. extern const char kNullHash[]; @@ -74,7 +72,6 @@ class PipelineIntegrationTestBase { bool hashing_enabled_; scoped_ptr<MessageLoopFactory> message_loop_factory_; scoped_refptr<Pipeline> pipeline_; - scoped_ptr<AesDecryptor> decryptor_; scoped_refptr<FFmpegVideoDecoder> decoder_; scoped_refptr<VideoRendererBase> renderer_; scoped_refptr<NullAudioSink> audio_sink_; diff --git a/media/media.gyp b/media/media.gyp index 22c6ca0..51e7215 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -196,6 +196,7 @@ 'base/video_util.h', 'crypto/aes_decryptor.cc', 'crypto/aes_decryptor.h', + 'crypto/decryptor_client.h', 'ffmpeg/ffmpeg_common.cc', 'ffmpeg/ffmpeg_common.h', 'ffmpeg/file_protocol.cc', diff --git a/media/mp4/mp4_stream_parser.cc b/media/mp4/mp4_stream_parser.cc index 3d6a190..cf09f49 100644 --- a/media/mp4/mp4_stream_parser.cc +++ b/media/mp4/mp4_stream_parser.cc @@ -31,21 +31,21 @@ void MP4StreamParser::Init(const InitCB& init_cb, const NewConfigCB& config_cb, const NewBuffersCB& audio_cb, const NewBuffersCB& video_cb, - const KeyNeededCB& key_needed_cb, + const NeedKeyCB& need_key_cb, const NewMediaSegmentCB& new_segment_cb) { DCHECK_EQ(state_, kWaitingForInit); DCHECK(init_cb_.is_null()); DCHECK(!init_cb.is_null()); DCHECK(!config_cb.is_null()); DCHECK(!audio_cb.is_null() || !video_cb.is_null()); - DCHECK(!key_needed_cb.is_null()); + DCHECK(!need_key_cb.is_null()); ChangeState(kParsingBoxes); init_cb_ = init_cb; config_cb_ = config_cb; audio_cb_ = audio_cb; video_cb_ = video_cb; - key_needed_cb_ = key_needed_cb; + need_key_cb_ = need_key_cb; new_segment_cb_ = new_segment_cb; } diff --git a/media/mp4/mp4_stream_parser.h b/media/mp4/mp4_stream_parser.h index 269b9eb0..5d0d0f9 100644 --- a/media/mp4/mp4_stream_parser.h +++ b/media/mp4/mp4_stream_parser.h @@ -28,7 +28,7 @@ class MEDIA_EXPORT MP4StreamParser : public StreamParser { virtual void Init(const InitCB& init_cb, const NewConfigCB& config_cb, const NewBuffersCB& audio_cb, const NewBuffersCB& video_cb, - const KeyNeededCB& key_needed_cb, + const NeedKeyCB& need_key_cb, const NewMediaSegmentCB& new_segment_cb) OVERRIDE; virtual void Flush() OVERRIDE; virtual bool Parse(const uint8* buf, int size) OVERRIDE; @@ -59,7 +59,7 @@ class MEDIA_EXPORT MP4StreamParser : public StreamParser { NewConfigCB config_cb_; NewBuffersCB audio_cb_; NewBuffersCB video_cb_; - KeyNeededCB key_needed_cb_; + NeedKeyCB need_key_cb_; NewMediaSegmentCB new_segment_cb_; OffsetByteQueue queue_; diff --git a/media/webm/webm_stream_parser.cc b/media/webm/webm_stream_parser.cc index b063a26..38c5a7e 100644 --- a/media/webm/webm_stream_parser.cc +++ b/media/webm/webm_stream_parser.cc @@ -191,14 +191,14 @@ void WebMStreamParser::Init(const InitCB& init_cb, const NewConfigCB& config_cb, const NewBuffersCB& audio_cb, const NewBuffersCB& video_cb, - const KeyNeededCB& key_needed_cb, + const NeedKeyCB& need_key_cb, const NewMediaSegmentCB& new_segment_cb) { DCHECK_EQ(state_, kWaitingForInit); DCHECK(init_cb_.is_null()); DCHECK(!init_cb.is_null()); DCHECK(!config_cb.is_null()); DCHECK(!audio_cb.is_null() || !video_cb.is_null()); - DCHECK(!key_needed_cb.is_null()); + DCHECK(!need_key_cb.is_null()); DCHECK(!new_segment_cb.is_null()); ChangeState(kParsingHeaders); @@ -206,7 +206,7 @@ void WebMStreamParser::Init(const InitCB& init_cb, config_cb_ = config_cb; audio_cb_ = audio_cb; video_cb_ = video_cb; - key_needed_cb_ = key_needed_cb; + need_key_cb_ = need_key_cb; new_segment_cb_ = new_segment_cb; } @@ -354,7 +354,7 @@ int WebMStreamParser::ParseInfoAndTracks(const uint8* data, int size) { CHECK_GT(key_id_size, 0); scoped_array<uint8> key_id(new uint8[key_id_size]); memcpy(key_id.get(), tracks_parser.video_encryption_key_id(), key_id_size); - key_needed_cb_.Run(key_id.Pass(), key_id_size); + need_key_cb_.Run(key_id.Pass(), key_id_size); } cluster_parser_.reset(new WebMClusterParser( diff --git a/media/webm/webm_stream_parser.h b/media/webm/webm_stream_parser.h index 003171e..f555278 100644 --- a/media/webm/webm_stream_parser.h +++ b/media/webm/webm_stream_parser.h @@ -25,7 +25,7 @@ class WebMStreamParser : public StreamParser { virtual void Init(const InitCB& init_cb, const NewConfigCB& config_cb, const NewBuffersCB& audio_cb, const NewBuffersCB& video_cb, - const KeyNeededCB& key_needed_cb, + const NeedKeyCB& need_key_cb, const NewMediaSegmentCB& new_segment_cb) OVERRIDE; virtual void Flush() OVERRIDE; virtual bool Parse(const uint8* buf, int size) OVERRIDE; @@ -64,7 +64,7 @@ class WebMStreamParser : public StreamParser { NewConfigCB config_cb_; NewBuffersCB audio_cb_; NewBuffersCB video_cb_; - KeyNeededCB key_needed_cb_; + NeedKeyCB need_key_cb_; NewMediaSegmentCB new_segment_cb_; // True if a new cluster id has been seen, but no audio or video buffers have diff --git a/webkit/media/webmediaplayer_impl.cc b/webkit/media/webmediaplayer_impl.cc index bc7631d..588801c 100644 --- a/webkit/media/webmediaplayer_impl.cc +++ b/webkit/media/webmediaplayer_impl.cc @@ -6,6 +6,7 @@ #include <limits> #include <string> +#include <vector> #include "base/bind.h" #include "base/callback.h" @@ -154,7 +155,7 @@ WebMediaPlayerImpl::WebMediaPlayerImpl( filter_collection_->AddAudioRenderer( new media::AudioRendererImpl(new media::NullAudioSink())); - decryptor_.reset(new media::AesDecryptor()); + decryptor_.reset(new media::AesDecryptor(proxy_.get())); } WebMediaPlayerImpl::~WebMediaPlayerImpl() { @@ -690,7 +691,7 @@ void WebMediaPlayerImpl::sourceEndOfStream( DCHECK_EQ(main_loop_, MessageLoop::current()); media::PipelineStatus pipeline_status = media::PIPELINE_OK; - switch(status) { + switch (status) { case WebMediaPlayer::EndOfStreamStatusNoError: break; case WebMediaPlayer::EndOfStreamStatusNetworkError: @@ -713,36 +714,12 @@ WebMediaPlayerImpl::generateKeyRequest(const WebString& key_system, if (!IsSupportedKeySystem(key_system)) return WebKit::WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported; - // Every request call creates a unique ID. - // TODO(ddorwin): Move this to the CDM implementations since the CDMs may - // create their own IDs and since CDMs supporting multiple renderer processes - // need globally unique IDs. - // Everything from here until the return should probably be handled by - // the decryptor - see http://crbug.com/123260. - static uint32_t next_available_session_id = 1; - uint32_t session_id = next_available_session_id++; - - WebString session_id_string(base::UintToString16(session_id)); - DVLOG(1) << "generateKeyRequest: " << key_system.utf8().data() << ": " << std::string(reinterpret_cast<const char*>(init_data), - static_cast<size_t>(init_data_length)) - << " [" << session_id_string.utf8().data() << "]"; - - // TODO(ddorwin): Generate a key request in the decryptor and fire - // keyMessage when it completes. - // For now, just fire the event with the init_data as the request. - const unsigned char* message = init_data; - unsigned message_length = init_data_length; - - MessageLoop::current()->PostTask(FROM_HERE, base::Bind( - &WebKit::WebMediaPlayerClient::keyMessage, - base::Unretained(GetClient()), - key_system, - session_id_string, - message, - message_length)); + static_cast<size_t>(init_data_length)); + decryptor_->GenerateKeyRequest(key_system.utf8(), + init_data, init_data_length); return WebKit::WebMediaPlayer::MediaKeyExceptionNoError; } @@ -759,7 +736,6 @@ WebKit::WebMediaPlayer::MediaKeyException WebMediaPlayerImpl::addKey( if (!IsSupportedKeySystem(key_system)) return WebKit::WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported; - DVLOG(1) << "addKey: " << key_system.utf8().data() << ": " << std::string(reinterpret_cast<const char*>(key), static_cast<size_t>(key_length)) << ", " @@ -767,38 +743,8 @@ WebKit::WebMediaPlayer::MediaKeyException WebMediaPlayerImpl::addKey( static_cast<size_t>(init_data_length)) << " [" << session_id.utf8().data() << "]"; - // TODO(ddorwin): Everything from here until the return should probably be - // handled by the decryptor - see http://crbug.com/123260. - // Temporarily, fire an error for invalid key length so we can test the error - // event and fire the keyAdded event in all other cases. - const unsigned kSupportedKeyLength = 16; // 128-bit key. - if (key_length != kSupportedKeyLength) { - DLOG(ERROR) << "addKey: invalid key length: " << key_length; - MessageLoop::current()->PostTask(FROM_HERE, base::Bind( - &WebKit::WebMediaPlayerClient::keyError, - base::Unretained(GetClient()), - key_system, - session_id, - WebKit::WebMediaPlayerClient::MediaKeyErrorCodeUnknown, - 0)); - } else { - // TODO(ddorwin): Fix the decryptor to accept no |init_data|. See - // http://crbug.com/123265. Until then, ensure a non-empty value is passed. - static const unsigned char kDummyInitData[1] = {0}; - if (!init_data) { - init_data = kDummyInitData; - init_data_length = arraysize(kDummyInitData); - } - - decryptor_->AddKey(init_data, init_data_length, key, key_length); - - MessageLoop::current()->PostTask(FROM_HERE, base::Bind( - &WebKit::WebMediaPlayerClient::keyAdded, - base::Unretained(GetClient()), - key_system, - session_id)); - } - + decryptor_->AddKey(key_system.utf8(), key, key_length, + init_data, init_data_length, session_id.utf8()); return WebKit::WebMediaPlayer::MediaKeyExceptionNoError; } @@ -808,8 +754,7 @@ WebKit::WebMediaPlayer::MediaKeyException WebMediaPlayerImpl::cancelKeyRequest( if (!IsSupportedKeySystem(key_system)) return WebKit::WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported; - // TODO(ddorwin): Cancel the key request in the decryptor. - + decryptor_->CancelKeyRequest(key_system.utf8(), session_id.utf8()); return WebKit::WebMediaPlayer::MediaKeyExceptionNoError; } @@ -926,11 +871,50 @@ void WebMediaPlayerImpl::OnDemuxerOpened() { GetClient()->sourceOpened(); } -void WebMediaPlayerImpl::OnKeyNeeded(scoped_array<uint8> init_data, - int init_data_size) { +void WebMediaPlayerImpl::OnKeyAdded(const std::string& key_system, + const std::string& session_id) { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + GetClient()->keyAdded(WebString::fromUTF8(key_system), + WebString::fromUTF8(session_id)); +} + +void WebMediaPlayerImpl::OnNeedKey(const std::string& key_system, + const std::string& session_id, + scoped_array<uint8> init_data, + int init_data_size) { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + GetClient()->keyNeeded(WebString::fromUTF8(key_system), + WebString::fromUTF8(session_id), + init_data.get(), + init_data_size); +} + +void WebMediaPlayerImpl::OnKeyError(const std::string& key_system, + const std::string& session_id, + media::AesDecryptor::KeyError error_code, + int system_code) { + DCHECK_EQ(main_loop_, MessageLoop::current()); + + GetClient()->keyError( + WebString::fromUTF8(key_system), + WebString::fromUTF8(session_id), + static_cast<WebKit::WebMediaPlayerClient::MediaKeyErrorCode>(error_code), + system_code); +} + +void WebMediaPlayerImpl::OnKeyMessage(const std::string& key_system, + const std::string& session_id, + scoped_array<uint8> message, + int message_length, + const std::string& /* default_url */) { DCHECK_EQ(main_loop_, MessageLoop::current()); - GetClient()->keyNeeded("", "", init_data.get(), init_data_size); + GetClient()->keyMessage(WebString::fromUTF8(key_system), + WebString::fromUTF8(session_id), + message.get(), + message_length); } void WebMediaPlayerImpl::SetOpaque(bool opaque) { diff --git a/webkit/media/webmediaplayer_impl.h b/webkit/media/webmediaplayer_impl.h index 6ef94b2..30d15f0 100644 --- a/webkit/media/webmediaplayer_impl.h +++ b/webkit/media/webmediaplayer_impl.h @@ -48,6 +48,8 @@ #ifndef WEBKIT_MEDIA_WEBMEDIAPLAYER_IMPL_H_ #define WEBKIT_MEDIA_WEBMEDIAPLAYER_IMPL_H_ +#include <string> + #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" @@ -57,6 +59,7 @@ #include "media/base/filters.h" #include "media/base/message_loop_factory.h" #include "media/base/pipeline.h" +#include "media/crypto/aes_decryptor.h" #include "skia/ext/platform_canvas.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebAudioSourceProvider.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebMediaPlayer.h" @@ -213,7 +216,6 @@ class WebMediaPlayerImpl const WebKit::WebString& key_system, const WebKit::WebString& session_id); - // As we are closing the tab or even the browser, |main_loop_| is destroyed // even before this object gets destructed, so we need to know when // |main_loop_| is being destroyed and we can stop posting repaint task @@ -227,7 +229,20 @@ class WebMediaPlayerImpl void OnPipelineEnded(media::PipelineStatus status); void OnPipelineError(media::PipelineStatus error); void OnDemuxerOpened(); - void OnKeyNeeded(scoped_array<uint8> init_data, int init_data_size); + void OnKeyAdded(const std::string& key_system, const std::string& session_id); + void OnKeyError(const std::string& key_system, + const std::string& session_id, + media::AesDecryptor::KeyError error_code, + int system_code); + void OnKeyMessage(const std::string& key_system, + const std::string& session_id, + scoped_array<uint8> message, + int message_length, + const std::string& default_url); + void OnNeedKey(const std::string& key_system, + const std::string& session_id, + scoped_array<uint8> init_data, + int init_data_size); void SetOpaque(bool); private: diff --git a/webkit/media/webmediaplayer_proxy.cc b/webkit/media/webmediaplayer_proxy.cc index fdbd544..461f67e 100644 --- a/webkit/media/webmediaplayer_proxy.cc +++ b/webkit/media/webmediaplayer_proxy.cc @@ -177,10 +177,10 @@ void WebMediaPlayerProxy::DemuxerClosed() { &WebMediaPlayerProxy::DemuxerClosedTask, this)); } -void WebMediaPlayerProxy::KeyNeeded(scoped_array<uint8> init_data, - int init_data_size) { +void WebMediaPlayerProxy::DemuxerNeedKey(scoped_array<uint8> init_data, + int init_data_size) { render_loop_->PostTask(FROM_HERE, base::Bind( - &WebMediaPlayerProxy::KeyNeededTask, this, + &WebMediaPlayerProxy::NeedKeyTask, this, "", "", base::Passed(&init_data), init_data_size)); } @@ -237,11 +237,76 @@ void WebMediaPlayerProxy::DemuxerClosedTask() { chunk_demuxer_ = NULL; } -void WebMediaPlayerProxy::KeyNeededTask(scoped_array<uint8> init_data, - int init_data_size) { +void WebMediaPlayerProxy::KeyAdded(const std::string& key_system, + const std::string& session_id) { + render_loop_->PostTask(FROM_HERE, base::Bind( + &WebMediaPlayerProxy::KeyAddedTask, this, key_system, session_id)); +} + +void WebMediaPlayerProxy::KeyError(const std::string& key_system, + const std::string& session_id, + media::AesDecryptor::KeyError error_code, + int system_code) { + render_loop_->PostTask(FROM_HERE, base::Bind( + &WebMediaPlayerProxy::KeyErrorTask, this, key_system, session_id, + error_code, system_code)); +} + +void WebMediaPlayerProxy::KeyMessage(const std::string& key_system, + const std::string& session_id, + scoped_array<uint8> message, + int message_length, + const std::string& default_url) { + render_loop_->PostTask(FROM_HERE, base::Bind( + &WebMediaPlayerProxy::KeyMessageTask, this, key_system, session_id, + base::Passed(&message), message_length, default_url)); +} + +void WebMediaPlayerProxy::NeedKey(const std::string& key_system, + const std::string& session_id, + scoped_array<uint8> init_data, + int init_data_size) { + render_loop_->PostTask(FROM_HERE, base::Bind( + &WebMediaPlayerProxy::NeedKeyTask, this, key_system, session_id, + base::Passed(&init_data), init_data_size)); +} + +void WebMediaPlayerProxy::KeyAddedTask(const std::string& key_system, + const std::string& session_id) { + DCHECK(render_loop_->BelongsToCurrentThread()); + if (webmediaplayer_) + webmediaplayer_->OnKeyAdded(key_system, session_id); +} + +void WebMediaPlayerProxy::KeyErrorTask(const std::string& key_system, + const std::string& session_id, + media::AesDecryptor::KeyError error_code, + int system_code) { + DCHECK(render_loop_->BelongsToCurrentThread()); + if (webmediaplayer_) + webmediaplayer_->OnKeyError(key_system, session_id, + error_code, system_code); +} + +void WebMediaPlayerProxy::KeyMessageTask(const std::string& key_system, + const std::string& session_id, + scoped_array<uint8> message, + int message_length, + const std::string& default_url) { + DCHECK(render_loop_->BelongsToCurrentThread()); + if (webmediaplayer_) + webmediaplayer_->OnKeyMessage(key_system, session_id, + message.Pass(), message_length, default_url); +} + +void WebMediaPlayerProxy::NeedKeyTask(const std::string& key_system, + const std::string& session_id, + scoped_array<uint8> init_data, + int init_data_size) { DCHECK(render_loop_->BelongsToCurrentThread()); if (webmediaplayer_) - webmediaplayer_->OnKeyNeeded(init_data.Pass(), init_data_size); + webmediaplayer_->OnNeedKey(key_system, session_id, + init_data.Pass(), init_data_size); } } // namespace webkit_media diff --git a/webkit/media/webmediaplayer_proxy.h b/webkit/media/webmediaplayer_proxy.h index f958f5d..2416388 100644 --- a/webkit/media/webmediaplayer_proxy.h +++ b/webkit/media/webmediaplayer_proxy.h @@ -12,6 +12,7 @@ #include "base/memory/ref_counted.h" #include "base/synchronization/lock.h" #include "media/base/pipeline.h" +#include "media/crypto/decryptor_client.h" #include "media/filters/chunk_demuxer.h" #include "media/filters/chunk_demuxer_client.h" #include "media/filters/ffmpeg_video_decoder.h" @@ -41,7 +42,8 @@ class WebMediaPlayerImpl; // the render thread that WebMediaPlayerImpl is running on. class WebMediaPlayerProxy : public base::RefCountedThreadSafe<WebMediaPlayerProxy>, - public media::ChunkDemuxerClient { + public media::ChunkDemuxerClient, + public media::DecryptorClient { public: WebMediaPlayerProxy(const scoped_refptr<base::MessageLoopProxy>& render_loop, WebMediaPlayerImpl* webmediaplayer); @@ -89,8 +91,8 @@ class WebMediaPlayerProxy // ChunkDemuxerClient implementation. virtual void DemuxerOpened(media::ChunkDemuxer* demuxer) OVERRIDE; virtual void DemuxerClosed() OVERRIDE; - virtual void KeyNeeded(scoped_array<uint8> init_data, - int init_data_size) OVERRIDE; + virtual void DemuxerNeedKey(scoped_array<uint8> init_data, + int init_data_size) OVERRIDE; // Methods for Demuxer communication. void DemuxerStartWaitingForSeek(); @@ -105,9 +107,22 @@ class WebMediaPlayerProxy void DemuxerEndOfStream(media::PipelineStatus status); void DemuxerShutdown(); - void DemuxerOpenedTask(const scoped_refptr<media::ChunkDemuxer>& demuxer); - void DemuxerClosedTask(); - void KeyNeededTask(scoped_array<uint8> init_data, int init_data_size); + // DecryptorClient implementation. + virtual void KeyAdded(const std::string& key_system, + const std::string& session_id) OVERRIDE; + virtual void KeyError(const std::string& key_system, + const std::string& session_id, + media::AesDecryptor::KeyError error_code, + int system_code) OVERRIDE; + virtual void KeyMessage(const std::string& key_system, + const std::string& session_id, + scoped_array<uint8> message, + int message_length, + const std::string& default_url) OVERRIDE; + virtual void NeedKey(const std::string& key_system, + const std::string& session_id, + scoped_array<uint8> init_data, + int init_data_size) OVERRIDE; private: friend class base::RefCountedThreadSafe<WebMediaPlayerProxy>; @@ -132,6 +147,32 @@ class WebMediaPlayerProxy // Inform |webmediaplayer_| whether the video content is opaque. void SetOpaqueTask(bool opaque); + void DemuxerOpenedTask(const scoped_refptr<media::ChunkDemuxer>& demuxer); + void DemuxerClosedTask(); + + // Notify |webmediaplayer_| that a key has been added. + void KeyAddedTask(const std::string& key_system, + const std::string& session_id); + + // Notify |webmediaplayer_| that a key error occurred. + void KeyErrorTask(const std::string& key_system, + const std::string& session_id, + media::AesDecryptor::KeyError error_code, + int system_code); + + // Notify |webmediaplayer_| that a key message has been generated. + void KeyMessageTask(const std::string& key_system, + const std::string& session_id, + scoped_array<uint8> message, + int message_length, + const std::string& default_url); + + // Notify |webmediaplayer_| that a key is needed for decryption. + void NeedKeyTask(const std::string& key_system, + const std::string& session_id, + scoped_array<uint8> init_data, + int init_data_size); + // The render message loop where WebKit lives. scoped_refptr<base::MessageLoopProxy> render_loop_; WebMediaPlayerImpl* webmediaplayer_; |