diff options
author | strobe@google.com <strobe@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-07-26 00:49:59 +0000 |
---|---|---|
committer | strobe@google.com <strobe@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-07-26 00:49:59 +0000 |
commit | 9746f9132e55a91d2ec3d866711277b874574743 (patch) | |
tree | b0f6d236afc3515855403363b6e9fe455a6c801d | |
parent | 7db8893ab741949612cebfed89e11d267daacbf9 (diff) | |
download | chromium_src-9746f9132e55a91d2ec3d866711277b874574743.zip chromium_src-9746f9132e55a91d2ec3d866711277b874574743.tar.gz chromium_src-9746f9132e55a91d2ec3d866711277b874574743.tar.bz2 |
Add Common Encryption support to BMFF, including subsample decryption.
BUG=132351
TEST=AesDecryptorTest, plus manual playback in browser
Review URL: https://chromiumcodereview.appspot.com/10651006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@148453 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | media/base/decrypt_config.cc | 32 | ||||
-rw-r--r-- | media/base/decrypt_config.h | 80 | ||||
-rw-r--r-- | media/crypto/aes_decryptor.cc | 180 | ||||
-rw-r--r-- | media/crypto/aes_decryptor.h | 11 | ||||
-rw-r--r-- | media/crypto/aes_decryptor_unittest.cc | 270 | ||||
-rw-r--r-- | media/filters/ffmpeg_video_decoder_unittest.cc | 13 | ||||
-rw-r--r-- | media/mp4/avc.cc | 27 | ||||
-rw-r--r-- | media/mp4/avc.h | 4 | ||||
-rw-r--r-- | media/mp4/avc_unittest.cc | 15 | ||||
-rw-r--r-- | media/mp4/box_definitions.cc | 36 | ||||
-rw-r--r-- | media/mp4/box_definitions.h | 4 | ||||
-rw-r--r-- | media/mp4/cenc.cc | 27 | ||||
-rw-r--r-- | media/mp4/cenc.h | 12 | ||||
-rw-r--r-- | media/mp4/mp4_stream_parser.cc | 153 | ||||
-rw-r--r-- | media/mp4/mp4_stream_parser.h | 7 | ||||
-rw-r--r-- | media/mp4/mp4_stream_parser_unittest.cc | 29 | ||||
-rw-r--r-- | media/mp4/track_run_iterator.cc | 221 | ||||
-rw-r--r-- | media/mp4/track_run_iterator.h | 32 | ||||
-rw-r--r-- | media/mp4/track_run_iterator_unittest.cc | 240 | ||||
-rw-r--r-- | media/webm/webm_cluster_parser.cc | 13 |
20 files changed, 1023 insertions, 383 deletions
diff --git a/media/base/decrypt_config.cc b/media/base/decrypt_config.cc index fb502df..c499c92 100644 --- a/media/base/decrypt_config.cc +++ b/media/base/decrypt_config.cc @@ -8,25 +8,19 @@ namespace media { -DecryptConfig::DecryptConfig(const uint8* key_id, int key_id_size, - const uint8* iv, int iv_size, - const uint8* checksum, int checksum_size, - int encrypted_frame_offset) - : key_id_(new uint8[key_id_size]), - key_id_size_(key_id_size), - iv_(new uint8[iv_size]), - iv_size_(iv_size), - checksum_(checksum_size > 0 ? new uint8[checksum_size] : NULL), - checksum_size_(checksum_size), - encrypted_frame_offset_(encrypted_frame_offset) { - CHECK_GT(key_id_size, 0); - CHECK_EQ(iv_size, DecryptConfig::kDecryptionKeySize); - CHECK_GE(checksum_size, 0); - CHECK_GE(encrypted_frame_offset, 0); - memcpy(key_id_.get(), key_id, key_id_size); - memcpy(iv_.get(), iv, iv_size); - if (checksum_size > 0) - memcpy(checksum_.get(), checksum, checksum_size); +DecryptConfig::DecryptConfig(const std::string& key_id, + const std::string& iv, + const std::string& checksum, + const int data_offset, + const std::vector<SubsampleEntry>& subsamples) + : key_id_(key_id), + iv_(iv), + checksum_(checksum), + data_offset_(data_offset), + subsamples_(subsamples) { + CHECK_GT(key_id.size(), 0u); + CHECK_EQ(iv.size(), static_cast<size_t>(DecryptConfig::kDecryptionKeySize)); + CHECK_GE(data_offset, 0); } DecryptConfig::~DecryptConfig() {} diff --git a/media/base/decrypt_config.h b/media/base/decrypt_config.h index 68a3fbd..d8ad224 100644 --- a/media/base/decrypt_config.h +++ b/media/base/decrypt_config.h @@ -5,55 +5,77 @@ #ifndef MEDIA_BASE_DECRYPT_CONFIG_H_ #define MEDIA_BASE_DECRYPT_CONFIG_H_ +#include <string> +#include <vector> + #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 a frame. +// The Common Encryption spec provides for subsample encryption, where portions +// of a sample are set in cleartext. A SubsampleEntry specifies the number of +// clear and encrypted bytes in each subsample. For decryption, all of the +// encrypted bytes in a sample should be considered a single logical stream, +// regardless of how they are divided into subsamples, and the clear bytes +// should not be considered as part of decryption. This is logically equivalent +// to concatenating all 'cypher_bytes' portions of subsamples, decrypting that +// result, and then copying each byte from the decrypted block over the +// position of the corresponding encrypted byte. +struct SubsampleEntry { + uint32 clear_bytes; + uint32 cypher_bytes; +}; + +// Contains all information that a decryptor needs to decrypt a media sample. class MEDIA_EXPORT DecryptConfig { public: // Keys are always 128 bits. static const int kDecryptionKeySize = 16; - // |key_id| is the ID that references the decryption key for this frame. |iv| - // is the initialization vector defined by the encrypted format. Currently - // |iv_size| must be 16 bytes as defined by WebM and ISO. |checksum| is the - // hash value of the encrypted buffer. |checksum| is defined by the - // encrypted format and may be NULL. |encrypted_frame_offset| is the offset - // into the encrypted buffer that the encrypted frame starts. The class - // will copy the data from |key_id|, |iv|, and |checksum|. - DecryptConfig(const uint8* key_id, int key_id_size, - const uint8* iv, int iv_size, - const uint8* checksum, int checksum_size, - int encrypted_frame_offset); + // |key_id| is the ID that references the decryption key for this sample. + // |iv| is the initialization vector defined by the encrypted format. + // Currently |iv_size| must be 16 bytes as defined by WebM and ISO. + // |checksum| is the hash value of the encrypted buffer. |checksum| is + // defined by the encrypted format and may be NULL. + // |data_offset| is the amount of data that should be discarded from the + // head of the sample buffer before applying subsample information. A + // decrypted buffer will be shorter than an encrypted buffer by this amount. + // |subsamples| defines the clear and encrypted portions of the sample as + // described above. A decrypted buffer will be equal in size to the sum + // of the subsample sizes. + // + // |data_offset| is applied after |checksum|, but before |subsamples|. + DecryptConfig(const std::string& key_id, + const std::string& iv, + const std::string& checksum, + const int data_offset, + const std::vector<SubsampleEntry>& subsamples); ~DecryptConfig(); - const uint8* key_id() const { return key_id_.get(); } - int key_id_size() const { return key_id_size_; } - const uint8* iv() const { return iv_.get(); } - int iv_size() const { return iv_size_; } - const uint8* checksum() const { return checksum_.get(); } - int checksum_size() const { return checksum_size_; } - int encrypted_frame_offset() const { return encrypted_frame_offset_; } + const std::string& key_id() const { return key_id_; } + const std::string& iv() const { return iv_; } + const std::string& checksum() const { return checksum_; } + int data_offset() const { return data_offset_; } + const std::vector<SubsampleEntry>& subsamples() const { return subsamples_; } private: - const scoped_array<uint8> key_id_; - const int key_id_size_; + const std::string key_id_; // Initialization vector. - const scoped_array<uint8> iv_; - const int iv_size_; + const std::string iv_; // Checksum of the data to be verified before decrypting the data. This may - // be NULL for some formats. - const scoped_array<uint8> checksum_; - const int checksum_size_; + // be empty for some formats. + const std::string checksum_; + + // Amount of data to be discarded before applying subsample information. + const int data_offset_; - // This is the offset in bytes to where the encrypted data starts within - // the input buffer. - const int encrypted_frame_offset_; + // Subsample information. May be empty for some formats, meaning entire frame + // (less data ignored by data_offset_) is encrypted. + const std::vector<SubsampleEntry> subsamples_; DISALLOW_COPY_AND_ASSIGN(DecryptConfig); }; diff --git a/media/crypto/aes_decryptor.cc b/media/crypto/aes_decryptor.cc index 730a9b8..552197b 100644 --- a/media/crypto/aes_decryptor.cc +++ b/media/crypto/aes_decryptor.cc @@ -59,7 +59,7 @@ static bool CheckData(const DecoderBuffer& input, const base::StringPiece& hmac_key) { CHECK(input.GetDataSize()); CHECK(input.GetDecryptConfig()); - CHECK_GT(input.GetDecryptConfig()->checksum_size(), 0); + CHECK_GT(input.GetDecryptConfig()->checksum().size(), 0u); CHECK(!hmac_key.empty()); crypto::HMAC hmac(crypto::HMAC::SHA1); @@ -72,65 +72,117 @@ static bool CheckData(const DecoderBuffer& input, // Here, check that checksum size is not greater than the hash // algorithm's digest length. - DCHECK_LE(input.GetDecryptConfig()->checksum_size(), - static_cast<int>(hmac.DigestLength())); + DCHECK_LE(input.GetDecryptConfig()->checksum().size(), + hmac.DigestLength()); base::StringPiece data_to_check( reinterpret_cast<const char*>(input.GetData()), input.GetDataSize()); - base::StringPiece digest( - reinterpret_cast<const char*>(input.GetDecryptConfig()->checksum()), - input.GetDecryptConfig()->checksum_size()); - return hmac.VerifyTruncated(data_to_check, digest); + return hmac.VerifyTruncated(data_to_check, + input.GetDecryptConfig()->checksum()); } -// Decrypts |input| using |key|. |encrypted_data_offset| is the number of bytes -// into |input| that the encrypted data starts. -// Returns a DecoderBuffer with the decrypted data if decryption succeeded or -// NULL if decryption failed. +enum ClearBytesBufferSel { + kSrcContainsClearBytes, + kDstContainsClearBytes, +}; + +static void CopySubsamples(const std::vector<SubsampleEntry>& subsamples, + const ClearBytesBufferSel sel, + const uint8* src, + uint8* dst) { + for (size_t i = 0; i < subsamples.size(); i++) { + const SubsampleEntry& subsample = subsamples[i]; + if (sel == kSrcContainsClearBytes) { + src += subsample.clear_bytes; + } else { + dst += subsample.clear_bytes; + } + memcpy(dst, src, subsample.cypher_bytes); + src += subsample.cypher_bytes; + dst += subsample.cypher_bytes; + } +} + +// Decrypts |input| using |key|. Returns a DecoderBuffer with the decrypted +// data if decryption succeeded or NULL if decryption failed. static scoped_refptr<DecoderBuffer> DecryptData(const DecoderBuffer& input, - crypto::SymmetricKey* key, - int encrypted_data_offset) { + crypto::SymmetricKey* key) { CHECK(input.GetDataSize()); CHECK(input.GetDecryptConfig()); CHECK(key); - // Initialize decryptor. crypto::Encryptor encryptor; if (!encryptor.Init(key, crypto::Encryptor::CTR, "")) { DVLOG(1) << "Could not initialize decryptor."; return NULL; } - DCHECK_EQ(input.GetDecryptConfig()->iv_size(), - DecryptConfig::kDecryptionKeySize); - // Set the counter block. - base::StringPiece counter_block( - reinterpret_cast<const char*>(input.GetDecryptConfig()->iv()), - input.GetDecryptConfig()->iv_size()); - if (counter_block.empty()) { - DVLOG(1) << "Could not generate counter block."; + DCHECK_EQ(input.GetDecryptConfig()->iv().size(), + static_cast<size_t>(DecryptConfig::kDecryptionKeySize)); + if (!encryptor.SetCounter(input.GetDecryptConfig()->iv())) { + DVLOG(1) << "Could not set counter block."; return NULL; } - if (!encryptor.SetCounter(counter_block)) { - DVLOG(1) << "Could not set counter block."; + + const int data_offset = input.GetDecryptConfig()->data_offset(); + const char* sample = + reinterpret_cast<const char*>(input.GetData() + data_offset); + int sample_size = input.GetDataSize() - data_offset; + + if (input.GetDecryptConfig()->subsamples().empty()) { + std::string decrypted_text; + base::StringPiece encrypted_text(sample, sample_size); + if (!encryptor.Decrypt(encrypted_text, &decrypted_text)) { + DVLOG(1) << "Could not decrypt data."; + return NULL; + } + + // TODO(xhwang): Find a way to avoid this data copy. + return DecoderBuffer::CopyFrom( + reinterpret_cast<const uint8*>(decrypted_text.data()), + decrypted_text.size()); + } + + const std::vector<SubsampleEntry>& subsamples = + input.GetDecryptConfig()->subsamples(); + + int total_clear_size = 0; + int total_encrypted_size = 0; + for (size_t i = 0; i < subsamples.size(); i++) { + total_clear_size += subsamples[i].clear_bytes; + total_encrypted_size += subsamples[i].cypher_bytes; + } + if (total_clear_size + total_encrypted_size != sample_size) { + DVLOG(1) << "Subsample sizes do not equal input size"; return NULL; } + // The encrypted portions of all subsamples must form a contiguous block, + // such that an encrypted subsample that ends away from a block boundary is + // immediately followed by the start of the next encrypted subsample. We + // copy all encrypted subsamples to a contiguous buffer, decrypt them, then + // copy the decrypted bytes over the encrypted bytes in the output. + // TODO(strobe): attempt to reduce number of memory copies + scoped_array<uint8> encrypted_bytes(new uint8[total_encrypted_size]); + CopySubsamples(subsamples, kSrcContainsClearBytes, + reinterpret_cast<const uint8*>(sample), encrypted_bytes.get()); + + base::StringPiece encrypted_text( + reinterpret_cast<const char*>(encrypted_bytes.get()), + total_encrypted_size); std::string decrypted_text; - const char* frame = - reinterpret_cast<const char*>(input.GetData() + encrypted_data_offset); - int frame_size = input.GetDataSize() - encrypted_data_offset; - base::StringPiece encrypted_text(frame, frame_size); if (!encryptor.Decrypt(encrypted_text, &decrypted_text)) { DVLOG(1) << "Could not decrypt data."; return NULL; } - // TODO(xhwang): Find a way to avoid this data copy. - return DecoderBuffer::CopyFrom( - reinterpret_cast<const uint8*>(decrypted_text.data()), - decrypted_text.size()); + scoped_refptr<DecoderBuffer> output = DecoderBuffer::CopyFrom( + reinterpret_cast<const uint8*>(sample), sample_size); + CopySubsamples(subsamples, kDstContainsClearBytes, + reinterpret_cast<const uint8*>(decrypted_text.data()), + output->GetWritableData()); + return output; } AesDecryptor::AesDecryptor(DecryptorClient* client) @@ -192,9 +244,7 @@ void AesDecryptor::AddKey(const std::string& key_system, return; } - // TODO(fgalligan): When ISO is added we will need to figure out how to - // detect if the encrypted data will contain an HMAC. - if (!decryption_key->Init(true)) { + if (!decryption_key->Init()) { DVLOG(1) << "Could not initialize decryption key."; client_->KeyError(key_system, session_id, Decryptor::kUnknownError, 0); return; @@ -220,16 +270,12 @@ void AesDecryptor::CancelKeyRequest(const std::string& key_system, void AesDecryptor::Decrypt(const scoped_refptr<DecoderBuffer>& encrypted, const DecryptCB& decrypt_cb) { CHECK(encrypted->GetDecryptConfig()); - const uint8* key_id = encrypted->GetDecryptConfig()->key_id(); - const int key_id_size = encrypted->GetDecryptConfig()->key_id_size(); - - // TODO(xhwang): Avoid always constructing a string with StringPiece? - std::string key_id_string(reinterpret_cast<const char*>(key_id), key_id_size); + const std::string& key_id = encrypted->GetDecryptConfig()->key_id(); DecryptionKey* key = NULL; { base::AutoLock auto_lock(key_map_lock_); - KeyMap::const_iterator found = key_map_.find(key_id_string); + KeyMap::const_iterator found = key_map_.find(key_id); if (found != key_map_.end()) key = found->second; } @@ -241,7 +287,7 @@ void AesDecryptor::Decrypt(const scoped_refptr<DecoderBuffer>& encrypted, return; } - int checksum_size = encrypted->GetDecryptConfig()->checksum_size(); + int checksum_size = encrypted->GetDecryptConfig()->checksum().size(); // According to the WebM encrypted specification, it is an open question // what should happen when a frame fails the integrity check. // http://wiki.webmproject.org/encryption/webm-encryption-rfc @@ -253,10 +299,13 @@ void AesDecryptor::Decrypt(const scoped_refptr<DecoderBuffer>& encrypted, return; } + // TODO(strobe): Currently, presence of checksum is used to indicate the use + // of normal or WebM decryption keys. Consider a more explicit signaling + // mechanism and the removal of the webm_decryption_key member. + crypto::SymmetricKey* decryption_key = (checksum_size > 0) ? + key->webm_decryption_key() : key->decryption_key(); scoped_refptr<DecoderBuffer> decrypted = - DecryptData(*encrypted, - key->decryption_key(), - encrypted->GetDecryptConfig()->encrypted_frame_offset()); + DecryptData(*encrypted, decryption_key); if (!decrypted) { DVLOG(1) << "Decryption failed."; decrypt_cb.Run(kError, NULL); @@ -275,34 +324,31 @@ AesDecryptor::DecryptionKey::DecryptionKey( AesDecryptor::DecryptionKey::~DecryptionKey() {} -bool AesDecryptor::DecryptionKey::Init(bool derive_webm_keys) { +bool AesDecryptor::DecryptionKey::Init() { CHECK(!secret_.empty()); - - if (derive_webm_keys) { - std::string raw_key = DeriveKey(secret_, - kWebmEncryptionSeed, - secret_.length()); - if (raw_key.empty()) { - return false; - } - decryption_key_.reset( - crypto::SymmetricKey::Import(crypto::SymmetricKey::AES, raw_key)); - if (!decryption_key_.get()) { - return false; - } - - hmac_key_ = DeriveKey(secret_, kWebmHmacSeed, kWebmSha1DigestSize); - if (hmac_key_.empty()) { - return false; - } - return true; - } - decryption_key_.reset( crypto::SymmetricKey::Import(crypto::SymmetricKey::AES, secret_)); if (!decryption_key_.get()) { return false; } + + std::string raw_key = DeriveKey(secret_, + kWebmEncryptionSeed, + secret_.length()); + if (raw_key.empty()) { + return false; + } + webm_decryption_key_.reset( + crypto::SymmetricKey::Import(crypto::SymmetricKey::AES, raw_key)); + if (!webm_decryption_key_.get()) { + return false; + } + + hmac_key_ = DeriveKey(secret_, kWebmHmacSeed, kWebmSha1DigestSize); + if (hmac_key_.empty()) { + return false; + } + return true; } diff --git a/media/crypto/aes_decryptor.h b/media/crypto/aes_decryptor.h index 224035c..86b258d 100644 --- a/media/crypto/aes_decryptor.h +++ b/media/crypto/aes_decryptor.h @@ -61,12 +61,12 @@ class MEDIA_EXPORT AesDecryptor : public Decryptor { explicit DecryptionKey(const std::string& secret); ~DecryptionKey(); - // Creates the encryption key and HMAC. If |derive_webm_keys| is true then - // the object will derive the decryption key and the HMAC key from - // |secret_|. - bool Init(bool derive_webm_keys); + // Creates the encryption key, and derives the WebM decryption key and HMAC. + bool Init(); crypto::SymmetricKey* decryption_key() { return decryption_key_.get(); } + crypto::SymmetricKey* webm_decryption_key() + { return webm_decryption_key_.get(); } base::StringPiece hmac_key() { return base::StringPiece(hmac_key_); } private: @@ -77,6 +77,9 @@ class MEDIA_EXPORT AesDecryptor : public Decryptor { // The key used to decrypt the data. scoped_ptr<crypto::SymmetricKey> decryption_key_; + // The key used for decryption of WebM media, derived from the secret. + scoped_ptr<crypto::SymmetricKey> webm_decryption_key_; + // The key used to perform the integrity check. Currently the HMAC key is // defined by the WebM encrypted specification. Current encrypted WebM // request for comments specification is here diff --git a/media/crypto/aes_decryptor_unittest.cc b/media/crypto/aes_decryptor_unittest.cc index 34354a4..7877f6e 100644 --- a/media/crypto/aes_decryptor_unittest.cc +++ b/media/crypto/aes_decryptor_unittest.cc @@ -143,6 +143,47 @@ static const unsigned char kWebmFrame0FrameDataChanged[] = { 0x64, 0xf8 }; +static const uint8 kSubsampleOriginalData[] = "Original subsample data."; +static const int kSubsampleOriginalDataSize = 24; + +static const uint8 kSubsampleKeyId[] = { 0x00, 0x01, 0x02, 0x03 }; + +static const uint8 kSubsampleKey[] = { + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13 +}; + +static const uint8 kSubsampleIv[] = { + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static const uint8 kSubsampleData[] = { + 0x4f, 0x72, 0x09, 0x16, 0x09, 0xe6, 0x79, 0xad, + 0x70, 0x73, 0x75, 0x62, 0x09, 0xbb, 0x83, 0x1d, + 0x4d, 0x08, 0xd7, 0x78, 0xa4, 0xa7, 0xf1, 0x2e +}; + +static const uint8 kPaddedSubsampleData[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x4f, 0x72, 0x09, 0x16, 0x09, 0xe6, 0x79, 0xad, + 0x70, 0x73, 0x75, 0x62, 0x09, 0xbb, 0x83, 0x1d, + 0x4d, 0x08, 0xd7, 0x78, 0xa4, 0xa7, 0xf1, 0x2e +}; + +// Encrypted with kSubsampleKey and kSubsampleIv but without subsamples. +static const uint8 kNoSubsampleData[] = { + 0x2f, 0x03, 0x09, 0xef, 0x71, 0xaf, 0x31, 0x16, + 0xfa, 0x9d, 0x18, 0x43, 0x1e, 0x96, 0x71, 0xb5, + 0xbf, 0xf5, 0x30, 0x53, 0x9a, 0x20, 0xdf, 0x95 +}; + +static const SubsampleEntry kSubsampleEntries[] = { + { 2, 7 }, + { 3, 11 }, + { 1, 0 }, +}; + class AesDecryptorTest : public testing::Test { public: AesDecryptorTest() @@ -176,10 +217,9 @@ class AesDecryptorTest : public testing::Test { // an HMAC and IV prepended to an encrypted frame. Current encrypted WebM // request for comments specification is here // http://wiki.webmproject.org/encryption/webm-encryption-rfc - scoped_refptr<DecoderBuffer> CreateWebMEncryptedBuffer(const uint8* data, - int data_size, - const uint8* key_id, - int key_id_size) { + scoped_refptr<DecoderBuffer> CreateWebMEncryptedBuffer( + const uint8* data, int data_size, + const uint8* key_id, int key_id_size) { scoped_refptr<DecoderBuffer> encrypted_buffer = DecoderBuffer::CopyFrom( data + kWebMHmacSize, data_size - kWebMHmacSize); CHECK(encrypted_buffer); @@ -191,10 +231,30 @@ class AesDecryptorTest : public testing::Test { GenerateCounterBlock(reinterpret_cast<const uint8*>(&iv), sizeof(iv)); encrypted_buffer->SetDecryptConfig( scoped_ptr<DecryptConfig>(new DecryptConfig( - key_id, key_id_size, - reinterpret_cast<const uint8*>(webm_iv.data()), webm_iv.size(), - data, kWebMHmacSize, - sizeof(iv)))); + std::string(reinterpret_cast<const char*>(key_id), key_id_size), + webm_iv, + std::string(reinterpret_cast<const char*>(data), kWebMHmacSize), + sizeof(iv), + std::vector<SubsampleEntry>()))); + return encrypted_buffer; + } + + scoped_refptr<DecoderBuffer> CreateSubsampleEncryptedBuffer( + const uint8* data, int data_size, + const uint8* key_id, int key_id_size, + const uint8* iv, int iv_size, + int data_offset, + const std::vector<SubsampleEntry>& subsample_entries) { + scoped_refptr<DecoderBuffer> encrypted_buffer = + DecoderBuffer::CopyFrom(data, data_size); + CHECK(encrypted_buffer); + encrypted_buffer->SetDecryptConfig( + scoped_ptr<DecryptConfig>(new DecryptConfig( + std::string(reinterpret_cast<const char*>(key_id), key_id_size), + std::string(reinterpret_cast<const char*>(iv), iv_size), + std::string(), + data_offset, + subsample_entries))); return encrypted_buffer; } @@ -223,29 +283,21 @@ class AesDecryptorTest : public testing::Test { MOCK_METHOD2(BufferDecrypted, void(Decryptor::DecryptStatus, const scoped_refptr<DecoderBuffer>&)); - void DecryptAndExpectToSucceed(const uint8* data, int data_size, - const uint8* plain_text, - int plain_text_size, - const uint8* key_id, int key_id_size) { - scoped_refptr<DecoderBuffer> encrypted_data = - CreateWebMEncryptedBuffer(data, data_size, key_id, key_id_size); + void DecryptAndExpectToSucceed(const scoped_refptr<DecoderBuffer>& encrypted, + const uint8* plain_text, int plain_text_size) { scoped_refptr<DecoderBuffer> decrypted; EXPECT_CALL(*this, BufferDecrypted(AesDecryptor::kSuccess, NotNull())) .WillOnce(SaveArg<1>(&decrypted)); - decryptor_.Decrypt(encrypted_data, decrypt_cb_); + decryptor_.Decrypt(encrypted, decrypt_cb_); ASSERT_TRUE(decrypted); ASSERT_EQ(plain_text_size, decrypted->GetDataSize()); EXPECT_EQ(0, memcmp(plain_text, decrypted->GetData(), plain_text_size)); } - void DecryptAndExpectToFail(const uint8* data, int data_size, - const uint8* plain_text, int plain_text_size, - const uint8* key_id, int key_id_size) { - scoped_refptr<DecoderBuffer> encrypted_data = - CreateWebMEncryptedBuffer(data, data_size, key_id, key_id_size); + void DecryptAndExpectToFail(const scoped_refptr<DecoderBuffer>& encrypted) { EXPECT_CALL(*this, BufferDecrypted(AesDecryptor::kError, IsNull())); - decryptor_.Decrypt(encrypted_data, decrypt_cb_); + decryptor_.Decrypt(encrypted, decrypt_cb_); } scoped_refptr<DecoderBuffer> encrypted_data_; @@ -255,17 +307,18 @@ class AesDecryptorTest : public testing::Test { AesDecryptor::DecryptCB decrypt_cb_; }; -TEST_F(AesDecryptorTest, NormalDecryption) { +TEST_F(AesDecryptorTest, NormalWebMDecryption) { const WebmEncryptedData& frame = kWebmEncryptedFrames[0]; GenerateKeyRequest(frame.key_id, frame.key_id_size); AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size, frame.key, frame.key_size); - ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed(frame.encrypted_data, - frame.encrypted_data_size, + scoped_refptr<DecoderBuffer> encrypted_data = + CreateWebMEncryptedBuffer(frame.encrypted_data, + frame.encrypted_data_size, + frame.key_id, frame.key_id_size); + ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed(encrypted_data, frame.plain_text, - frame.plain_text_size, - frame.key_id, - frame.key_id_size)); + frame.plain_text_size)); } TEST_F(AesDecryptorTest, WrongKey) { @@ -273,12 +326,11 @@ TEST_F(AesDecryptorTest, WrongKey) { GenerateKeyRequest(frame.key_id, frame.key_id_size); AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size, kWebmWrongKey, arraysize(kWebmWrongKey)); - ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToFail(frame.encrypted_data, - frame.encrypted_data_size, - frame.plain_text, - frame.plain_text_size, - frame.key_id, - frame.key_id_size)); + scoped_refptr<DecoderBuffer> encrypted_data = + CreateWebMEncryptedBuffer(frame.encrypted_data, + frame.encrypted_data_size, + frame.key_id, frame.key_id_size); + ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToFail(encrypted_data)); } TEST_F(AesDecryptorTest, KeyReplacement) { @@ -286,20 +338,16 @@ TEST_F(AesDecryptorTest, KeyReplacement) { GenerateKeyRequest(frame.key_id, frame.key_id_size); AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size, kWebmWrongKey, arraysize(kWebmWrongKey)); - ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToFail(frame.encrypted_data, - frame.encrypted_data_size, - frame.plain_text, - frame.plain_text_size, - frame.key_id, - frame.key_id_size)); + scoped_refptr<DecoderBuffer> encrypted_data = + CreateWebMEncryptedBuffer(frame.encrypted_data, + frame.encrypted_data_size, + frame.key_id, frame.key_id_size); + ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToFail(encrypted_data)); AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size, frame.key, frame.key_size); - ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed(frame.encrypted_data, - frame.encrypted_data_size, + ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed(encrypted_data, frame.plain_text, - frame.plain_text_size, - frame.key_id, - frame.key_id_size)); + frame.plain_text_size)); } TEST_F(AesDecryptorTest, WrongSizedKey) { @@ -314,12 +362,13 @@ TEST_F(AesDecryptorTest, MultipleKeysAndFrames) { GenerateKeyRequest(frame.key_id, frame.key_id_size); AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size, frame.key, frame.key_size); - ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed(frame.encrypted_data, - frame.encrypted_data_size, + scoped_refptr<DecoderBuffer> encrypted_data = + CreateWebMEncryptedBuffer(frame.encrypted_data, + frame.encrypted_data_size, + frame.key_id, frame.key_id_size); + ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed(encrypted_data, frame.plain_text, - frame.plain_text_size, - frame.key_id, - frame.key_id_size)); + frame.plain_text_size)); const WebmEncryptedData& frame2 = kWebmEncryptedFrames[2]; GenerateKeyRequest(frame2.key_id, frame2.key_id_size); @@ -327,19 +376,21 @@ TEST_F(AesDecryptorTest, MultipleKeysAndFrames) { frame2.key, frame2.key_size); const WebmEncryptedData& frame1 = kWebmEncryptedFrames[1]; - ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed(frame1.encrypted_data, - frame1.encrypted_data_size, + scoped_refptr<DecoderBuffer> encrypted_data1 = + CreateWebMEncryptedBuffer(frame1.encrypted_data, + frame1.encrypted_data_size, + frame1.key_id, frame1.key_id_size); + ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed(encrypted_data1, frame1.plain_text, - frame1.plain_text_size, - frame1.key_id, - frame1.key_id_size)); + frame1.plain_text_size)); - ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed(frame2.encrypted_data, - frame2.encrypted_data_size, + scoped_refptr<DecoderBuffer> encrypted_data2 = + CreateWebMEncryptedBuffer(frame2.encrypted_data, + frame2.encrypted_data_size, + frame2.key_id, frame2.key_id_size); + ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed(encrypted_data2, frame2.plain_text, - frame2.plain_text_size, - frame2.key_id, - frame2.key_id_size)); + frame2.plain_text_size)); } TEST_F(AesDecryptorTest, HmacCheckFailure) { @@ -347,12 +398,11 @@ TEST_F(AesDecryptorTest, HmacCheckFailure) { GenerateKeyRequest(frame.key_id, frame.key_id_size); AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size, frame.key, frame.key_size); - ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToFail(kWebmFrame0HmacDataChanged, - frame.encrypted_data_size, - frame.plain_text, - frame.plain_text_size, - frame.key_id, - frame.key_id_size)); + scoped_refptr<DecoderBuffer> encrypted_data = + CreateWebMEncryptedBuffer(kWebmFrame0HmacDataChanged, + frame.encrypted_data_size, + frame.key_id, frame.key_id_size); + ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToFail(encrypted_data)); } TEST_F(AesDecryptorTest, IvCheckFailure) { @@ -360,12 +410,11 @@ TEST_F(AesDecryptorTest, IvCheckFailure) { GenerateKeyRequest(frame.key_id, frame.key_id_size); AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size, frame.key, frame.key_size); - ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToFail(kWebmFrame0IvDataChanged, - frame.encrypted_data_size, - frame.plain_text, - frame.plain_text_size, - frame.key_id, - frame.key_id_size)); + scoped_refptr<DecoderBuffer> encrypted_data = + CreateWebMEncryptedBuffer(kWebmFrame0IvDataChanged, + frame.encrypted_data_size, + frame.key_id, frame.key_id_size); + ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToFail(encrypted_data)); } TEST_F(AesDecryptorTest, DataCheckFailure) { @@ -373,12 +422,79 @@ TEST_F(AesDecryptorTest, DataCheckFailure) { GenerateKeyRequest(frame.key_id, frame.key_id_size); AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size, frame.key, frame.key_size); - ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToFail(kWebmFrame0FrameDataChanged, - frame.encrypted_data_size, - frame.plain_text, - frame.plain_text_size, - frame.key_id, - frame.key_id_size)); + scoped_refptr<DecoderBuffer> encrypted_data = + CreateWebMEncryptedBuffer(kWebmFrame0FrameDataChanged, + frame.encrypted_data_size, + frame.key_id, frame.key_id_size); + ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToFail(encrypted_data)); +} + +TEST_F(AesDecryptorTest, SubsampleDecryption) { + GenerateKeyRequest(kSubsampleKeyId, arraysize(kSubsampleKeyId)); + AddKeyAndExpectToSucceed(kSubsampleKeyId, arraysize(kSubsampleKeyId), + kSubsampleKey, arraysize(kSubsampleKey)); + scoped_refptr<DecoderBuffer> encrypted_data = CreateSubsampleEncryptedBuffer( + kSubsampleData, arraysize(kSubsampleData), + kSubsampleKeyId, arraysize(kSubsampleKeyId), + kSubsampleIv, arraysize(kSubsampleIv), + 0, + std::vector<SubsampleEntry>( + kSubsampleEntries, + kSubsampleEntries + arraysize(kSubsampleEntries))); + ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed( + encrypted_data, kSubsampleOriginalData, kSubsampleOriginalDataSize)); +} + +// Ensures noninterference of data offset and subsample mechanisms. We never +// expect to encounter this in the wild, but since the DecryptConfig doesn't +// disallow such a configuration, it should be covered. +TEST_F(AesDecryptorTest, SubsampleDecryptionWithOffset) { + GenerateKeyRequest(kSubsampleKeyId, arraysize(kSubsampleKeyId)); + AddKeyAndExpectToSucceed(kSubsampleKeyId, arraysize(kSubsampleKeyId), + kSubsampleKey, arraysize(kSubsampleKey)); + scoped_refptr<DecoderBuffer> encrypted_data = CreateSubsampleEncryptedBuffer( + kPaddedSubsampleData, arraysize(kPaddedSubsampleData), + kSubsampleKeyId, arraysize(kSubsampleKeyId), + kSubsampleIv, arraysize(kSubsampleIv), + arraysize(kPaddedSubsampleData) - arraysize(kSubsampleData), + std::vector<SubsampleEntry>( + kSubsampleEntries, + kSubsampleEntries + arraysize(kSubsampleEntries))); + ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed( + encrypted_data, kSubsampleOriginalData, kSubsampleOriginalDataSize)); +} + +// No subsample or offset. +TEST_F(AesDecryptorTest, NormalDecryption) { + GenerateKeyRequest(kSubsampleKeyId, arraysize(kSubsampleKeyId)); + AddKeyAndExpectToSucceed(kSubsampleKeyId, arraysize(kSubsampleKeyId), + kSubsampleKey, arraysize(kSubsampleKey)); + scoped_refptr<DecoderBuffer> encrypted_data = CreateSubsampleEncryptedBuffer( + kNoSubsampleData, arraysize(kNoSubsampleData), + kSubsampleKeyId, arraysize(kSubsampleKeyId), + kSubsampleIv, arraysize(kSubsampleIv), + 0, + std::vector<SubsampleEntry>()); + ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToSucceed( + encrypted_data, kSubsampleOriginalData, kSubsampleOriginalDataSize)); +} + +TEST_F(AesDecryptorTest, IncorrectSubsampleSize) { + GenerateKeyRequest(kSubsampleKeyId, arraysize(kSubsampleKeyId)); + AddKeyAndExpectToSucceed(kSubsampleKeyId, arraysize(kSubsampleKeyId), + kSubsampleKey, arraysize(kSubsampleKey)); + std::vector<SubsampleEntry> entries( + kSubsampleEntries, + kSubsampleEntries + arraysize(kSubsampleEntries)); + entries[2].cypher_bytes += 1; + + scoped_refptr<DecoderBuffer> encrypted_data = CreateSubsampleEncryptedBuffer( + kSubsampleData, arraysize(kSubsampleData), + kSubsampleKeyId, arraysize(kSubsampleKeyId), + kSubsampleIv, arraysize(kSubsampleIv), + 0, + entries); + ASSERT_NO_FATAL_FAILURE(DecryptAndExpectToFail(encrypted_data)); } } // namespace media diff --git a/media/filters/ffmpeg_video_decoder_unittest.cc b/media/filters/ffmpeg_video_decoder_unittest.cc index 6a7fb4f..f720cf1 100644 --- a/media/filters/ffmpeg_video_decoder_unittest.cc +++ b/media/filters/ffmpeg_video_decoder_unittest.cc @@ -4,6 +4,7 @@ #include <deque> #include <string> +#include <vector> #include "base/bind.h" #include "base/message_loop.h" @@ -47,10 +48,14 @@ static scoped_refptr<DecoderBuffer> CreateFakeEncryptedBuffer() { const int encrypted_frame_offset = 1; // This should be non-zero. scoped_refptr<DecoderBuffer> buffer(new DecoderBuffer(buffer_size)); buffer->SetDecryptConfig(scoped_ptr<DecryptConfig>(new DecryptConfig( - kFakeKeyId, arraysize(kFakeKeyId), - kFakeIv, DecryptConfig::kDecryptionKeySize, - kFakeCheckSum, arraysize(kFakeCheckSum), - encrypted_frame_offset))); + std::string(reinterpret_cast<const char*>(kFakeKeyId), + arraysize(kFakeKeyId)), + std::string(reinterpret_cast<const char*>(kFakeIv), + DecryptConfig::kDecryptionKeySize), + std::string(reinterpret_cast<const char*>(kFakeCheckSum), + arraysize(kFakeCheckSum)), + encrypted_frame_offset, + std::vector<SubsampleEntry>()))); return buffer; } diff --git a/media/mp4/avc.cc b/media/mp4/avc.cc index 2999466..ae28ffd 100644 --- a/media/mp4/avc.cc +++ b/media/mp4/avc.cc @@ -32,7 +32,7 @@ static bool ConvertAVCToAnnexBInPlaceForLengthSize4(std::vector<uint8>* buf) { } // static -bool AVC::ConvertToAnnexB(int length_size, std::vector<uint8>* buffer) { +bool AVC::ConvertFrameToAnnexB(int length_size, std::vector<uint8>* buffer) { RCHECK(length_size == 1 || length_size == 2 || length_size == 4); if (length_size == 4) @@ -59,32 +59,31 @@ bool AVC::ConvertToAnnexB(int length_size, std::vector<uint8>* buffer) { } // static -bool AVC::InsertParameterSets(const AVCDecoderConfigurationRecord& avc_config, - std::vector<uint8>* buffer) { +bool AVC::ConvertConfigToAnnexB( + const AVCDecoderConfigurationRecord& avc_config, + std::vector<uint8>* buffer) { + DCHECK(buffer->empty()); + buffer->clear(); int total_size = 0; for (size_t i = 0; i < avc_config.sps_list.size(); i++) total_size += avc_config.sps_list[i].size() + kAnnexBStartCodeSize; for (size_t i = 0; i < avc_config.pps_list.size(); i++) total_size += avc_config.pps_list[i].size() + kAnnexBStartCodeSize; - - std::vector<uint8> temp; - temp.reserve(total_size); + buffer->reserve(total_size); for (size_t i = 0; i < avc_config.sps_list.size(); i++) { - temp.insert(temp.end(), kAnnexBStartCode, + buffer->insert(buffer->end(), kAnnexBStartCode, kAnnexBStartCode + kAnnexBStartCodeSize); - temp.insert(temp.end(), avc_config.sps_list[i].begin(), + buffer->insert(buffer->end(), avc_config.sps_list[i].begin(), avc_config.sps_list[i].end()); } for (size_t i = 0; i < avc_config.pps_list.size(); i++) { - temp.insert(temp.end(), kAnnexBStartCode, - kAnnexBStartCode + kAnnexBStartCodeSize); - temp.insert(temp.end(), avc_config.pps_list[i].begin(), - avc_config.pps_list[i].end()); + buffer->insert(buffer->end(), kAnnexBStartCode, + kAnnexBStartCode + kAnnexBStartCodeSize); + buffer->insert(buffer->end(), avc_config.pps_list[i].begin(), + avc_config.pps_list[i].end()); } - - buffer->insert(buffer->begin(), temp.begin(), temp.end()); return true; } diff --git a/media/mp4/avc.h b/media/mp4/avc.h index 8e826b0..3d815a1 100644 --- a/media/mp4/avc.h +++ b/media/mp4/avc.h @@ -17,9 +17,9 @@ struct AVCDecoderConfigurationRecord; class MEDIA_EXPORT AVC { public: - static bool ConvertToAnnexB(int length_size, std::vector<uint8>* buffer); + static bool ConvertFrameToAnnexB(int length_size, std::vector<uint8>* buffer); - static bool InsertParameterSets( + static bool ConvertConfigToAnnexB( const AVCDecoderConfigurationRecord& avc_config, std::vector<uint8>* buffer); }; diff --git a/media/mp4/avc_unittest.cc b/media/mp4/avc_unittest.cc index cdcc413..766a979 100644 --- a/media/mp4/avc_unittest.cc +++ b/media/mp4/avc_unittest.cc @@ -23,7 +23,7 @@ static const uint8 kExpected[] = { static const uint8 kExpectedParamSets[] = { 0x00, 0x00, 0x00, 0x01, 0x67, 0x12, 0x00, 0x00, 0x00, 0x01, 0x67, 0x34, - 0x00, 0x00, 0x00, 0x01, 0x68, 0x56, 0x78, 0x9a}; + 0x00, 0x00, 0x00, 0x01, 0x68, 0x56, 0x78}; class AVCConversionTest : public testing::TestWithParam<int> { protected: @@ -44,7 +44,7 @@ class AVCConversionTest : public testing::TestWithParam<int> { TEST_P(AVCConversionTest, ParseCorrectly) { std::vector<uint8> buf; MakeInputForLength(GetParam(), &buf); - EXPECT_TRUE(AVC::ConvertToAnnexB(GetParam(), &buf)); + EXPECT_TRUE(AVC::ConvertFrameToAnnexB(GetParam(), &buf)); EXPECT_EQ(buf.size(), sizeof(kExpected)); EXPECT_EQ(0, memcmp(kExpected, &buf[0], sizeof(kExpected))); } @@ -53,19 +53,19 @@ TEST_P(AVCConversionTest, ParsePartial) { std::vector<uint8> buf; MakeInputForLength(GetParam(), &buf); buf.pop_back(); - EXPECT_FALSE(AVC::ConvertToAnnexB(GetParam(), &buf)); + EXPECT_FALSE(AVC::ConvertFrameToAnnexB(GetParam(), &buf)); // This tests a buffer ending in the middle of a NAL length. For length size // of one, this can't happen, so we skip that case. if (GetParam() != 1) { MakeInputForLength(GetParam(), &buf); buf.erase(buf.end() - (sizeof(kNALU2) + 1), buf.end()); - EXPECT_FALSE(AVC::ConvertToAnnexB(GetParam(), &buf)); + EXPECT_FALSE(AVC::ConvertFrameToAnnexB(GetParam(), &buf)); } } TEST_P(AVCConversionTest, ParseEmpty) { std::vector<uint8> buf; - EXPECT_TRUE(AVC::ConvertToAnnexB(GetParam(), &buf)); + EXPECT_TRUE(AVC::ConvertFrameToAnnexB(GetParam(), &buf)); EXPECT_EQ(0u, buf.size()); } @@ -73,7 +73,7 @@ INSTANTIATE_TEST_CASE_P(AVCConversionTestValues, AVCConversionTest, ::testing::Values(1, 2, 4)); -TEST(AVC, InsertParameterSetsTest) { +TEST_F(AVCConversionTest, ConvertConfigToAnnexB) { AVCDecoderConfigurationRecord avc_config; avc_config.sps_list.resize(2); avc_config.sps_list[0].push_back(0x67); @@ -86,8 +86,7 @@ TEST(AVC, InsertParameterSetsTest) { avc_config.pps_list[0].push_back(0x78); std::vector<uint8> buf; - buf.push_back(0x9a); - EXPECT_TRUE(AVC::InsertParameterSets(avc_config, &buf)); + EXPECT_TRUE(AVC::ConvertConfigToAnnexB(avc_config, &buf)); EXPECT_EQ(0, memcmp(kExpectedParamSets, &buf[0], sizeof(kExpectedParamSets))); } diff --git a/media/mp4/box_definitions.cc b/media/mp4/box_definitions.cc index c9245d8..d6ab0fc 100644 --- a/media/mp4/box_definitions.cc +++ b/media/mp4/box_definitions.cc @@ -27,7 +27,7 @@ FourCC ProtectionSystemSpecificHeader::BoxType() const { return FOURCC_PSSH; } bool ProtectionSystemSpecificHeader::Parse(BoxReader* reader) { uint32 size; - return reader->SkipBytes(4) && + return reader->ReadFullBoxHeader() && reader->ReadVec(&system_id, 16) && reader->Read4(&size) && reader->ReadVec(&data, size); @@ -88,7 +88,7 @@ SchemeType::~SchemeType() {} FourCC SchemeType::BoxType() const { return FOURCC_SCHM; } bool SchemeType::Parse(BoxReader* reader) { - RCHECK(reader->SkipBytes(4) && + RCHECK(reader->ReadFullBoxHeader() && reader->ReadFourCC(&type) && reader->Read4(&version)); RCHECK(type == FOURCC_CENC); @@ -103,7 +103,8 @@ FourCC TrackEncryption::BoxType() const { return FOURCC_TENC; } bool TrackEncryption::Parse(BoxReader* reader) { uint8 flag; - RCHECK(reader->SkipBytes(2) && + RCHECK(reader->ReadFullBoxHeader() && + reader->SkipBytes(2) && reader->Read1(&flag) && reader->Read1(&default_iv_size) && reader->ReadVec(&default_kid, 16)); @@ -129,9 +130,11 @@ ProtectionSchemeInfo::~ProtectionSchemeInfo() {} FourCC ProtectionSchemeInfo::BoxType() const { return FOURCC_SINF; } bool ProtectionSchemeInfo::Parse(BoxReader* reader) { - return reader->ScanChildren() && + RCHECK(reader->ScanChildren() && + reader->ReadChild(&format) && reader->ReadChild(&type) && - reader->ReadChild(&info); + reader->ReadChild(&info)); + return true; } MovieHeader::MovieHeader() @@ -375,20 +378,15 @@ bool VideoSampleEntry::Parse(BoxReader* reader) { reader->Read2(&height) && reader->SkipBytes(50)); - RCHECK(reader->ScanChildren()); - RCHECK(reader->MaybeReadChild(&pixel_aspect)); - if (format == FOURCC_ENCV) { - RCHECK(reader->ReadChild(&sinf)); - } + RCHECK(reader->ScanChildren() && + reader->MaybeReadChild(&pixel_aspect)); - // TODO(strobe): finalize format signaling for encrypted media - // (http://crbug.com/132351) - // - // if (format == FOURCC_AVC1 || - // (format == FOURCC_ENCV && - // sinf.format.format == FOURCC_AVC1)) { + if (format == FOURCC_ENCV) + RCHECK(reader->ReadChild(&sinf)); + if (format == FOURCC_AVC1 || + (format == FOURCC_ENCV && sinf.format.format == FOURCC_AVC1)) { RCHECK(reader->ReadChild(&avcc)); - // } + } return true; } @@ -444,9 +442,8 @@ bool AudioSampleEntry::Parse(BoxReader* reader) { samplerate >>= 16; RCHECK(reader->ScanChildren()); - if (format == FOURCC_ENCA) { + if (format == FOURCC_ENCA) RCHECK(reader->ReadChild(&sinf)); - } RCHECK(reader->ReadChild(&esds)); return true; } @@ -597,6 +594,7 @@ bool MovieFragmentHeader::Parse(BoxReader* reader) { TrackFragmentHeader::TrackFragmentHeader() : track_id(0), + sample_description_index(0), default_sample_duration(0), default_sample_size(0), default_sample_flags(0), diff --git a/media/mp4/box_definitions.h b/media/mp4/box_definitions.h index 0b19a5c..b78ebdd 100644 --- a/media/mp4/box_definitions.h +++ b/media/mp4/box_definitions.h @@ -217,7 +217,9 @@ struct MEDIA_EXPORT SampleTable : Box { DECLARE_BOX_METHODS(SampleTable); // Media Source specific: we ignore many of the sub-boxes in this box, - // including some that are required to be present in the BMFF spec. + // including some that are required to be present in the BMFF spec. This + // includes the 'stts', 'stsc', and 'stco' boxes, which must contain no + // samples in order to be compliant files. SampleDescription description; }; diff --git a/media/mp4/cenc.cc b/media/mp4/cenc.cc index 996ebb1..104948d 100644 --- a/media/mp4/cenc.cc +++ b/media/mp4/cenc.cc @@ -4,6 +4,8 @@ #include "media/mp4/cenc.h" +#include <cstring> + #include "media/mp4/box_reader.h" #include "media/mp4/rcheck.h" @@ -15,28 +17,35 @@ FrameCENCInfo::~FrameCENCInfo() {} bool FrameCENCInfo::Parse(int iv_size, BufferReader* reader) { const int kEntrySize = 6; - // Mandated by CENC spec RCHECK(iv_size == 8 || iv_size == 16); - iv.resize(iv_size); + + memset(iv, 0, sizeof(iv)); + for (int i = 0; i < iv_size; i++) + RCHECK(reader->Read1(&iv[i])); + + if (!reader->HasBytes(1)) return true; uint16 subsample_count; - RCHECK(reader->ReadVec(&iv, iv_size) && - reader->Read2(&subsample_count) && + RCHECK(reader->Read2(&subsample_count) && reader->HasBytes(subsample_count * kEntrySize)); - subsamples.resize(subsample_count); + subsamples.resize(subsample_count); for (int i = 0; i < subsample_count; i++) { - RCHECK(reader->Read2(&subsamples[i].clear_size) && - reader->Read4(&subsamples[i].encrypted_size)); + uint16 clear_bytes; + uint32 cypher_bytes; + RCHECK(reader->Read2(&clear_bytes) && + reader->Read4(&cypher_bytes)); + subsamples[i].clear_bytes = clear_bytes; + subsamples[i].cypher_bytes = cypher_bytes; } return true; } -size_t FrameCENCInfo::GetTotalSize() const { +size_t FrameCENCInfo::GetTotalSizeOfSubsamples() const { size_t size = 0; for (size_t i = 0; i < subsamples.size(); i++) { - size += subsamples[i].clear_size + subsamples[i].encrypted_size; + size += subsamples[i].clear_bytes + subsamples[i].cypher_bytes; } return size; } diff --git a/media/mp4/cenc.h b/media/mp4/cenc.h index ee23743..e558559 100644 --- a/media/mp4/cenc.h +++ b/media/mp4/cenc.h @@ -8,25 +8,21 @@ #include <vector> #include "base/basictypes.h" +#include "media/base/decrypt_config.h" namespace media { namespace mp4 { class BufferReader; -struct SubsampleSizes { - uint16 clear_size; - uint32 encrypted_size; -}; - struct FrameCENCInfo { - std::vector<uint8> iv; - std::vector<SubsampleSizes> subsamples; + uint8 iv[16]; + std::vector<SubsampleEntry> subsamples; FrameCENCInfo(); ~FrameCENCInfo(); bool Parse(int iv_size, BufferReader* r); - size_t GetTotalSize() const; + size_t GetTotalSizeOfSubsamples() const; }; diff --git a/media/mp4/mp4_stream_parser.cc b/media/mp4/mp4_stream_parser.cc index 05b18c1..757ea503 100644 --- a/media/mp4/mp4_stream_parser.cc +++ b/media/mp4/mp4_stream_parser.cc @@ -174,19 +174,24 @@ bool MP4StreamParser::ParseMoov(BoxReader* reader) { // It is not uncommon to find otherwise-valid files with incorrect sample // description indices, so we fail gracefully in that case. - if (static_cast<uint32>(desc_idx) >= samp_descr.audio_entries.size()) + if (desc_idx >= samp_descr.audio_entries.size()) desc_idx = 0; const AudioSampleEntry& entry = samp_descr.audio_entries[desc_idx]; const AAC& aac = entry.esds.aac; - // TODO(strobe): We accept all format values, pending clarification on - // the formats used for encrypted media (http://crbug.com/132351). - // RCHECK(entry.format == FOURCC_MP4A || - // (entry.format == FOURCC_ENCA && - // entry.sinf.format.format == FOURCC_MP4A)); - + if (!(entry.format == FOURCC_MP4A || + (entry.format == FOURCC_ENCA && + entry.sinf.format.format == FOURCC_MP4A))) { + LOG(ERROR) << "Unsupported audio format."; + return false; + } // Check if it is MPEG4 AAC defined in ISO 14496 Part 3. - RCHECK(entry.esds.object_type == kISO_14496_3); + if (entry.esds.object_type != kISO_14496_3) { + LOG(ERROR) << "Unsupported audio object type."; + return false; + } + RCHECK(EmitKeyNeeded(entry.sinf.info.track_encryption)); + audio_config.Initialize(kCodecAAC, entry.samplesize, aac.channel_layout(), aac.GetOutputSamplesPerSecond(has_sbr_), @@ -196,13 +201,17 @@ bool MP4StreamParser::ParseMoov(BoxReader* reader) { } if (track->media.handler.type == kVideo && !video_config.IsValidConfig()) { RCHECK(!samp_descr.video_entries.empty()); - if (static_cast<uint32>(desc_idx) >= samp_descr.video_entries.size()) + if (desc_idx >= samp_descr.video_entries.size()) desc_idx = 0; const VideoSampleEntry& entry = samp_descr.video_entries[desc_idx]; - // RCHECK(entry.format == FOURCC_AVC1 || - // (entry.format == FOURCC_ENCV && - // entry.sinf.format.format == FOURCC_AVC1)); + if (!(entry.format == FOURCC_AVC1 || + (entry.format == FOURCC_ENCV && + entry.sinf.format.format == FOURCC_AVC1))) { + LOG(ERROR) << "Unsupported video format."; + return false; + } + RCHECK(EmitKeyNeeded(entry.sinf.info.track_encryption)); // TODO(strobe): Recover correct crop box video_config.Initialize(kCodecH264, H264PROFILE_MAIN, VideoFrame::YV12, @@ -223,18 +232,20 @@ bool MP4StreamParser::ParseMoov(BoxReader* reader) { // TODO(strobe): For now, we avoid sending new configs on a new // reinitialization segment, and instead simply embed the updated parameter - // sets into the video stream. The conditional should be removed when - // http://crbug.com/122913 is fixed. + // sets into the video stream. The conditional should be removed when + // http://crbug.com/122913 is fixed. (We detect whether we've already sent + // configs by looking at init_cb_ instead of config_cb_, because init_cb_ + // should only be fired once even after that bug is fixed.) if (!init_cb_.is_null()) RCHECK(config_cb_.Run(audio_config, video_config)); base::TimeDelta duration; if (moov_->extends.header.fragment_duration > 0) { - duration = TimeDeltaFromFrac(moov_->extends.header.fragment_duration, - moov_->header.timescale); + duration = TimeDeltaFromRational(moov_->extends.header.fragment_duration, + moov_->header.timescale); } else if (moov_->header.duration > 0) { - duration = TimeDeltaFromFrac(moov_->header.duration, - moov_->header.timescale); + duration = TimeDeltaFromRational(moov_->header.duration, + moov_->header.timescale); } else { duration = kInfiniteDuration(); } @@ -254,10 +265,54 @@ bool MP4StreamParser::ParseMoof(BoxReader* reader) { return true; } +bool MP4StreamParser::EmitKeyNeeded(const TrackEncryption& track_encryption) { + // TODO(strobe): Send the correct value for initData. The format of initData + // has not yet been defined; see + // https://www.w3.org/Bugs/Public/show_bug.cgi?id=17673. + if (!track_encryption.is_encrypted) return true; + scoped_array<uint8> kid(new uint8[track_encryption.default_kid.size()]); + memcpy(kid.get(), &track_encryption.default_kid[0], + track_encryption.default_kid.size()); + return need_key_cb_.Run(kid.Pass(), track_encryption.default_kid.size()); +} + +bool MP4StreamParser::PrepareAVCBuffer( + const AVCDecoderConfigurationRecord& avc_config, + std::vector<uint8>* frame_buf, + std::vector<SubsampleEntry>* subsamples) const { + // Convert the AVC NALU length fields to Annex B headers, as expected by + // decoding libraries. Since this may enlarge the size of the buffer, we also + // update the clear byte count for each subsample if encryption is used to + // account for the difference in size between the length prefix and Annex B + // start code. + RCHECK(AVC::ConvertFrameToAnnexB(avc_config.length_size, frame_buf)); + if (!subsamples->empty()) { + const int nalu_size_diff = 4 - avc_config.length_size; + size_t expected_size = runs_->sample_size() + + subsamples->size() * nalu_size_diff; + RCHECK(frame_buf->size() == expected_size); + for (size_t i = 0; i < subsamples->size(); i++) + (*subsamples)[i].clear_bytes += nalu_size_diff; + } + + if (runs_->is_keyframe()) { + // If this is a keyframe, we (re-)inject SPS and PPS headers at the start of + // a frame. If subsample info is present, we also update the clear byte + // count for that first subsample. + std::vector<uint8> param_sets; + RCHECK(AVC::ConvertConfigToAnnexB(avc_config, ¶m_sets)); + frame_buf->insert(frame_buf->begin(), + param_sets.begin(), param_sets.end()); + if (!subsamples->empty()) + (*subsamples)[0].clear_bytes += param_sets.size(); + } + return true; +} + bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers, BufferQueue* video_buffers, bool* err) { - if (!runs_->RunIsValid()) { + if (!runs_->IsRunValid()) { // Flush any buffers we've gotten in this chunk so that buffers don't // cross NewSegment() calls *err = !SendAndFlushSamples(audio_buffers, video_buffers); @@ -266,7 +321,7 @@ bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers, return true; } - if (!runs_->SampleIsValid()) { + if (!runs_->IsSampleValid()) { runs_->AdvanceRun(); return true; } @@ -274,9 +329,9 @@ bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers, DCHECK(!(*err)); const uint8* buf; - int size; - queue_.Peek(&buf, &size); - if (!size) return false; + int buf_size; + queue_.Peek(&buf, &buf_size); + if (!buf_size) return false; bool audio = has_audio_ && audio_track_id_ == runs_->track_id(); bool video = has_video_ && video_track_id_ == runs_->track_id(); @@ -284,16 +339,42 @@ bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers, // Skip this entire track if it's not one we're interested in if (!audio && !video) runs_->AdvanceRun(); - queue_.PeekAt(runs_->sample_offset() + moof_head_, &buf, &size); - if (size < runs_->sample_size()) return false; + // Attempt to cache the auxiliary information first. Aux info is usually + // placed in a contiguous block before the sample data, rather than being + // interleaved. If we didn't cache it, this would require that we retain the + // start of the segment buffer while reading samples. Aux info is typically + // quite small compared to sample data, so this pattern is useful on + // memory-constrained devices where the source buffer consumes a substantial + // portion of the total system memory. + if (runs_->AuxInfoNeedsToBeCached()) { + queue_.PeekAt(runs_->aux_info_offset() + moof_head_, &buf, &buf_size); + if (buf_size < runs_->aux_info_size()) return false; + *err = !runs_->CacheAuxInfo(buf, buf_size); + return !*err; + } + + queue_.PeekAt(runs_->sample_offset() + moof_head_, &buf, &buf_size); + if (buf_size < runs_->sample_size()) return false; + + scoped_ptr<DecryptConfig> decrypt_config; + if (runs_->is_encrypted()) + decrypt_config = runs_->GetDecryptConfig(); std::vector<uint8> frame_buf(buf, buf + runs_->sample_size()); if (video) { - const AVCDecoderConfigurationRecord& avc_config = - runs_->video_description().avcc; - RCHECK(AVC::ConvertToAnnexB(avc_config.length_size, &frame_buf)); - if (runs_->is_keyframe()) - RCHECK(AVC::InsertParameterSets(avc_config, &frame_buf)); + std::vector<SubsampleEntry> subsamples; + if (decrypt_config.get()) + subsamples = decrypt_config->subsamples(); + RCHECK(PrepareAVCBuffer(runs_->video_description().avcc, + &frame_buf, &subsamples)); + if (!subsamples.empty()) { + decrypt_config.reset(new DecryptConfig( + decrypt_config->key_id(), + decrypt_config->iv(), + decrypt_config->checksum(), + decrypt_config->data_offset(), + subsamples)); + } } if (audio) { @@ -305,6 +386,9 @@ bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers, StreamParserBuffer::CopyFrom(&frame_buf[0], frame_buf.size(), runs_->is_keyframe()); + if (runs_->is_encrypted()) + stream_buf->SetDecryptConfig(decrypt_config.Pass()); + stream_buf->SetDuration(runs_->duration()); stream_buf->SetTimestamp(runs_->cts()); stream_buf->SetDecodeTimestamp(runs_->dts()); @@ -328,17 +412,16 @@ bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers, bool MP4StreamParser::SendAndFlushSamples(BufferQueue* audio_buffers, BufferQueue* video_buffers) { + bool err = false; if (!audio_buffers->empty()) { - if (audio_cb_.is_null() || !audio_cb_.Run(*audio_buffers)) - return false; + err |= (audio_cb_.is_null() || !audio_cb_.Run(*audio_buffers)); audio_buffers->clear(); } if (!video_buffers->empty()) { - if (video_cb_.is_null() || !video_cb_.Run(*video_buffers)) - return false; + err |= (video_cb_.is_null() || !video_cb_.Run(*video_buffers)); video_buffers->clear(); } - return true; + return !err; } bool MP4StreamParser::ReadMDATsUntil(const int64 tgt_offset) { diff --git a/media/mp4/mp4_stream_parser.h b/media/mp4/mp4_stream_parser.h index a19bbcc..53100a95 100644 --- a/media/mp4/mp4_stream_parser.h +++ b/media/mp4/mp4_stream_parser.h @@ -5,6 +5,8 @@ #ifndef MEDIA_MP4_MP4_STREAM_PARSER_H_ #define MEDIA_MP4_MP4_STREAM_PARSER_H_ +#include <vector> + #include "base/basictypes.h" #include "base/callback.h" #include "base/compiler_specific.h" @@ -45,11 +47,16 @@ class MEDIA_EXPORT MP4StreamParser : public StreamParser { bool ParseMoov(mp4::BoxReader* reader); bool ParseMoof(mp4::BoxReader* reader); + bool EmitKeyNeeded(const TrackEncryption& track_encryption); + bool ReadMDATsUntil(const int64 tgt_tail); void ChangeState(State new_state); bool EmitConfigs(); + bool PrepareAVCBuffer(const AVCDecoderConfigurationRecord& avc_config, + std::vector<uint8>* frame_buf, + std::vector<SubsampleEntry>* subsamples) const; bool EnqueueSample(BufferQueue* audio_buffers, BufferQueue* video_buffers, bool* err); diff --git a/media/mp4/mp4_stream_parser_unittest.cc b/media/mp4/mp4_stream_parser_unittest.cc index db2555f..63e1382 100644 --- a/media/mp4/mp4_stream_parser_unittest.cc +++ b/media/mp4/mp4_stream_parser_unittest.cc @@ -28,21 +28,16 @@ class MP4StreamParserTest : public testing::Test { public: MP4StreamParserTest() : parser_(new MP4StreamParser(false)), - got_configs_(false) { + configs_received_(false) { } protected: scoped_ptr<MP4StreamParser> parser_; base::TimeDelta segment_start_; - bool got_configs_; + bool configs_received_; bool AppendData(const uint8* data, size_t length) { - parser_->Parse(data, length); - return true; - } - - bool AppendDataInPieces(const uint8* data, size_t length) { - return AppendDataInPieces(data, length, 7); + return parser_->Parse(data, length); } bool AppendDataInPieces(const uint8* data, size_t length, size_t piece_size) { @@ -71,8 +66,8 @@ class MP4StreamParserTest : public testing::Test { // TODO(strobe): Until http://crbug.com/122913 is fixed, we want to make // sure that this callback isn't called more than once per stream. Remove // when that bug is fixed. - EXPECT_FALSE(got_configs_); - got_configs_ = true; + EXPECT_FALSE(configs_received_); + configs_received_ = true; return true; } @@ -83,7 +78,7 @@ class MP4StreamParserTest : public testing::Test { DVLOG(3) << " n=" << buf - bufs.begin() << ", size=" << (*buf)->GetDataSize() << ", dur=" << (*buf)->GetDuration().InMilliseconds(); - EXPECT_LE(segment_start_, (*buf)->GetTimestamp()); + EXPECT_GE((*buf)->GetTimestamp(), segment_start_); } return true; } @@ -108,22 +103,28 @@ class MP4StreamParserTest : public testing::Test { base::Bind(&MP4StreamParserTest::NewSegmentF, base::Unretained(this))); } - bool ParseMP4File(const std::string& filename, int append_size) { + bool ParseMP4File(const std::string& filename, int append_bytes) { InitializeParser(); scoped_refptr<DecoderBuffer> buffer = ReadTestDataFile(filename); EXPECT_TRUE(AppendDataInPieces(buffer->GetData(), buffer->GetDataSize(), - append_size)); + append_bytes)); return true; } }; -TEST_F(MP4StreamParserTest, TestParseBearDASH) { + + +TEST_F(MP4StreamParserTest, TestUnalignedAppend) { + // Test small, non-segment-aligned appends (small enough to exercise + // incremental append system) ParseMP4File("bear.1280x720_dash.mp4", 512); } TEST_F(MP4StreamParserTest, TestMultiFragmentAppend) { + // Large size ensures multiple fragments are appended in one call (size is + // larger than this particular test file) ParseMP4File("bear.1280x720_dash.mp4", 768432); } diff --git a/media/mp4/track_run_iterator.cc b/media/mp4/track_run_iterator.cc index 0bef86e..59bc229 100644 --- a/media/mp4/track_run_iterator.cc +++ b/media/mp4/track_run_iterator.cc @@ -9,6 +9,10 @@ #include "media/base/stream_parser_buffer.h" #include "media/mp4/rcheck.h" +namespace { +static const uint32 kSampleIsDifferenceSampleFlagMask = 0x10000; +} + namespace media { namespace mp4 { @@ -30,6 +34,11 @@ struct TrackRunInfo { const AudioSampleEntry* audio_description; const VideoSampleEntry* video_description; + int64 aux_info_start_offset; // Only valid if aux_info_total_size > 0. + int aux_info_default_size; + std::vector<uint8> aux_info_sizes; // Populated if default_size == 0. + int aux_info_total_size; + TrackRunInfo(); ~TrackRunInfo(); }; @@ -39,20 +48,20 @@ TrackRunInfo::TrackRunInfo() timescale(-1), start_dts(-1), sample_start_offset(-1), - is_audio(false) { + is_audio(false), + aux_info_start_offset(-1), + aux_info_default_size(-1), + aux_info_total_size(-1) { } TrackRunInfo::~TrackRunInfo() {} -TimeDelta TimeDeltaFromFrac(int64 numer, int64 denom) { +TimeDelta TimeDeltaFromRational(int64 numer, int64 denom) { DCHECK_LT((numer > 0 ? numer : -numer), kint64max / base::Time::kMicrosecondsPerSecond); return TimeDelta::FromMicroseconds( base::Time::kMicrosecondsPerSecond * numer / denom); } -static const uint32 kSampleIsDifferenceSampleFlagMask = 0x10000; - - TrackRunIterator::TrackRunIterator(const Movie* moov) : moov_(moov), sample_offset_(0) { CHECK(moov); @@ -97,10 +106,30 @@ static void PopulateSampleInfo(const TrackExtends& trex, sample_info->is_keyframe = !(flags & kSampleIsDifferenceSampleFlagMask); } +// In well-structured encrypted media, each track run will be immediately +// preceded by its auxiliary information; this is the only optimal storage +// pattern in terms of minimum number of bytes from a serial stream needed to +// begin playback. It also allows us to optimize caching on memory-constrained +// architectures, because we can cache the relatively small auxiliary +// information for an entire run and then discard data from the input stream, +// instead of retaining the entire 'mdat' box. +// +// We optimize for this situation (with no loss of generality) by sorting track +// runs during iteration in order of their first data offset (either sample data +// or auxiliary data). class CompareMinTrackRunDataOffset { public: bool operator()(const TrackRunInfo& a, const TrackRunInfo& b) { - return a.sample_start_offset < b.sample_start_offset; + int64 a_aux = a.aux_info_total_size ? a.aux_info_start_offset : kint64max; + int64 b_aux = b.aux_info_total_size ? b.aux_info_start_offset : kint64max; + + int64 a_lesser = std::min(a_aux, a.sample_start_offset); + int64 a_greater = std::max(a_aux, a.sample_start_offset); + int64 b_lesser = std::min(b_aux, b.sample_start_offset); + int64 b_greater = std::max(b_aux, b.sample_start_offset); + + if (a_lesser == b_lesser) return a_greater < b_greater; + return a_lesser < b_lesser; } }; @@ -130,11 +159,13 @@ bool TrackRunIterator::Init(const MovieFragment& moof) { DVLOG(1) << "Skipping unhandled track type"; continue; } - int desc_idx = traf.header.sample_description_index; + size_t desc_idx = traf.header.sample_description_index; if (!desc_idx) desc_idx = trex->default_sample_description_index; - desc_idx--; // Descriptions are one-indexed in the file + RCHECK(desc_idx > 0); // Descriptions are one-indexed in the file + desc_idx -= 1; int64 run_start_dts = traf.decode_time.decode_time; + int sample_count_sum = 0; for (size_t j = 0; j < traf.runs.size(); j++) { const TrackFragmentRun& trun = traf.runs[j]; @@ -146,17 +177,60 @@ bool TrackRunIterator::Init(const MovieFragment& moof) { tri.is_audio = (stsd.type == kAudio); if (tri.is_audio) { + RCHECK(!stsd.audio_entries.empty()); + if (desc_idx > stsd.audio_entries.size()) + desc_idx = 0; tri.audio_description = &stsd.audio_entries[desc_idx]; } else { + RCHECK(!stsd.video_entries.empty()); + if (desc_idx > stsd.video_entries.size()) + desc_idx = 0; tri.video_description = &stsd.video_entries[desc_idx]; } + // Collect information from the auxiliary_offset entry with the same index + // in the 'saiz' container as the current run's index in the 'trun' + // container, if it is present. + if (traf.auxiliary_offset.offsets.size() > j) { + // There should be an auxiliary info entry corresponding to each sample + // in the auxiliary offset entry's corresponding track run. + RCHECK(traf.auxiliary_size.sample_count >= + sample_count_sum + trun.sample_count); + tri.aux_info_start_offset = traf.auxiliary_offset.offsets[j]; + tri.aux_info_default_size = + traf.auxiliary_size.default_sample_info_size; + if (tri.aux_info_default_size == 0) { + const std::vector<uint8>& sizes = + traf.auxiliary_size.sample_info_sizes; + tri.aux_info_sizes.insert(tri.aux_info_sizes.begin(), + sizes.begin() + sample_count_sum, + sizes.begin() + sample_count_sum + trun.sample_count); + } + + // If the default info size is positive, find the total size of the aux + // info block from it, otherwise sum over the individual sizes of each + // aux info entry in the aux_offset entry. + if (tri.aux_info_default_size) { + tri.aux_info_total_size = + tri.aux_info_default_size * trun.sample_count; + } else { + tri.aux_info_total_size = 0; + for (size_t k = 0; k < trun.sample_count; k++) { + tri.aux_info_total_size += tri.aux_info_sizes[k]; + } + } + } else { + tri.aux_info_start_offset = -1; + tri.aux_info_total_size = 0; + } + tri.samples.resize(trun.sample_count); for (size_t k = 0; k < trun.sample_count; k++) { PopulateSampleInfo(*trex, traf.header, trun, k, &tri.samples[k]); run_start_dts += tri.samples[k].duration; } runs_.push_back(tri); + sample_count_sum += trun.sample_count; } } @@ -172,61 +246,109 @@ void TrackRunIterator::AdvanceRun() { } void TrackRunIterator::ResetRun() { - if (!RunIsValid()) return; + if (!IsRunValid()) return; sample_dts_ = run_itr_->start_dts; sample_offset_ = run_itr_->sample_start_offset; sample_itr_ = run_itr_->samples.begin(); + cenc_info_.clear(); } void TrackRunIterator::AdvanceSample() { - DCHECK(SampleIsValid()); + DCHECK(IsSampleValid()); sample_dts_ += sample_itr_->duration; sample_offset_ += sample_itr_->size; ++sample_itr_; } -bool TrackRunIterator::RunIsValid() const { +// This implementation only indicates a need for caching if CENC auxiliary +// info is available in the stream. +bool TrackRunIterator::AuxInfoNeedsToBeCached() { + DCHECK(IsRunValid()); + return is_encrypted() && aux_info_size() > 0 && cenc_info_.size() == 0; +} + +// This implementation currently only caches CENC auxiliary info. +bool TrackRunIterator::CacheAuxInfo(const uint8* buf, int buf_size) { + RCHECK(AuxInfoNeedsToBeCached() && buf_size >= aux_info_size()); + + cenc_info_.resize(run_itr_->samples.size()); + int64 pos = 0; + for (size_t i = 0; i < run_itr_->samples.size(); i++) { + int info_size = run_itr_->aux_info_default_size; + if (!info_size) + info_size = run_itr_->aux_info_sizes[i]; + + BufferReader reader(buf + pos, info_size); + RCHECK(cenc_info_[i].Parse(track_encryption().default_iv_size, &reader)); + pos += info_size; + } + + return true; +} + +bool TrackRunIterator::IsRunValid() const { return run_itr_ != runs_.end(); } -bool TrackRunIterator::SampleIsValid() const { - return RunIsValid() && (sample_itr_ != run_itr_->samples.end()); +bool TrackRunIterator::IsSampleValid() const { + return IsRunValid() && (sample_itr_ != run_itr_->samples.end()); } +// Because tracks are in sorted order and auxiliary information is cached when +// returning samples, it is guaranteed that no data will be required before the +// lesser of the minimum data offset of this track and the next in sequence. +// (The stronger condition - that no data is required before the minimum data +// offset of this track alone - is not guaranteed, because the BMFF spec does +// not have any inter-run ordering restrictions.) int64 TrackRunIterator::GetMaxClearOffset() { int64 offset = kint64max; - if (SampleIsValid()) + if (IsSampleValid()) { offset = std::min(offset, sample_offset_); - if (run_itr_ == runs_.end()) - return offset; - std::vector<TrackRunInfo>::const_iterator next_run = run_itr_ + 1; - if (next_run != runs_.end()) - offset = std::min(offset, next_run->sample_start_offset); + if (AuxInfoNeedsToBeCached()) + offset = std::min(offset, aux_info_offset()); + } + if (run_itr_ != runs_.end()) { + std::vector<TrackRunInfo>::const_iterator next_run = run_itr_ + 1; + if (next_run != runs_.end()) { + offset = std::min(offset, next_run->sample_start_offset); + if (next_run->aux_info_total_size) + offset = std::min(offset, next_run->aux_info_start_offset); + } + } + if (offset == kint64max) return 0; return offset; } TimeDelta TrackRunIterator::GetMinDecodeTimestamp() { TimeDelta dts = kInfiniteDuration(); for (size_t i = 0; i < runs_.size(); i++) { - dts = std::min(dts, TimeDeltaFromFrac(runs_[i].start_dts, - runs_[i].timescale)); + dts = std::min(dts, TimeDeltaFromRational(runs_[i].start_dts, + runs_[i].timescale)); } return dts; } uint32 TrackRunIterator::track_id() const { - DCHECK(RunIsValid()); + DCHECK(IsRunValid()); return run_itr_->track_id; } bool TrackRunIterator::is_encrypted() const { - DCHECK(RunIsValid()); - return false; + DCHECK(IsRunValid()); + return track_encryption().is_encrypted; +} + +int64 TrackRunIterator::aux_info_offset() const { + return run_itr_->aux_info_start_offset; +} + +int TrackRunIterator::aux_info_size() const { + return run_itr_->aux_info_total_size; } bool TrackRunIterator::is_audio() const { - DCHECK(RunIsValid()); + DCHECK(IsRunValid()); return run_itr_->is_audio; } @@ -243,35 +365,64 @@ const VideoSampleEntry& TrackRunIterator::video_description() const { } int64 TrackRunIterator::sample_offset() const { - DCHECK(SampleIsValid()); + DCHECK(IsSampleValid()); return sample_offset_; } int TrackRunIterator::sample_size() const { - DCHECK(SampleIsValid()); + DCHECK(IsSampleValid()); return sample_itr_->size; } TimeDelta TrackRunIterator::dts() const { - DCHECK(SampleIsValid()); - return TimeDeltaFromFrac(sample_dts_, run_itr_->timescale); + DCHECK(IsSampleValid()); + return TimeDeltaFromRational(sample_dts_, run_itr_->timescale); } TimeDelta TrackRunIterator::cts() const { - DCHECK(SampleIsValid()); - return TimeDeltaFromFrac(sample_dts_ + sample_itr_->cts_offset, - run_itr_->timescale); + DCHECK(IsSampleValid()); + return TimeDeltaFromRational(sample_dts_ + sample_itr_->cts_offset, + run_itr_->timescale); } TimeDelta TrackRunIterator::duration() const { - DCHECK(SampleIsValid()); - return TimeDeltaFromFrac(sample_itr_->duration, run_itr_->timescale); + DCHECK(IsSampleValid()); + return TimeDeltaFromRational(sample_itr_->duration, run_itr_->timescale); } bool TrackRunIterator::is_keyframe() const { - DCHECK(SampleIsValid()); + DCHECK(IsSampleValid()); return sample_itr_->is_keyframe; } +const TrackEncryption& TrackRunIterator::track_encryption() const { + if (is_audio()) + return audio_description().sinf.info.track_encryption; + return video_description().sinf.info.track_encryption; +} + +scoped_ptr<DecryptConfig> TrackRunIterator::GetDecryptConfig() { + size_t sample_idx = sample_itr_ - run_itr_->samples.begin(); + DCHECK(sample_idx < cenc_info_.size()); + const FrameCENCInfo& cenc_info = cenc_info_[sample_idx]; + DCHECK(is_encrypted() && !AuxInfoNeedsToBeCached()); + + if (!cenc_info.subsamples.empty() && + (cenc_info.GetTotalSizeOfSubsamples() != + static_cast<size_t>(sample_size()))) { + DVLOG(1) << "Incorrect CENC subsample size."; + return scoped_ptr<DecryptConfig>(); + } + + const std::vector<uint8>& kid = track_encryption().default_kid; + return scoped_ptr<DecryptConfig>(new DecryptConfig( + std::string(reinterpret_cast<const char*>(&kid[0]), kid.size()), + std::string(reinterpret_cast<const char*>(cenc_info.iv), + arraysize(cenc_info.iv)), + std::string(), // No checksum in MP4 using CENC. + 0, // No offset to start of media data in MP4 using CENC. + cenc_info.subsamples)); +} + } // namespace mp4 } // namespace media diff --git a/media/mp4/track_run_iterator.h b/media/mp4/track_run_iterator.h index ddea39c..85cadce 100644 --- a/media/mp4/track_run_iterator.h +++ b/media/mp4/track_run_iterator.h @@ -11,12 +11,16 @@ #include "base/time.h" #include "media/base/media_export.h" #include "media/mp4/box_definitions.h" +#include "media/mp4/cenc.h" namespace media { + +class DecryptConfig; + namespace mp4 { using base::TimeDelta; -base::TimeDelta MEDIA_EXPORT TimeDeltaFromFrac(int64 numer, int64 denom); +base::TimeDelta MEDIA_EXPORT TimeDeltaFromRational(int64 numer, int64 denom); struct SampleInfo; struct TrackRunInfo; @@ -34,14 +38,23 @@ class MEDIA_EXPORT TrackRunIterator { bool Init(const MovieFragment& moof); // Returns true if the properties of the current run or sample are valid. - bool RunIsValid() const; - bool SampleIsValid() const; + bool IsRunValid() const; + bool IsSampleValid() const; // Advance the properties to refer to the next run or sample. Requires that // the current sample be valid. void AdvanceRun(); void AdvanceSample(); + // Returns true if this track run has auxiliary information and has not yet + // been cached. Only valid if IsRunValid(). + bool AuxInfoNeedsToBeCached(); + + // Caches the CENC data from the given buffer. |buf| must be a buffer starting + // at the offset given by cenc_offset(), with a |size| of at least + // cenc_size(). Returns true on success, false on error. + bool CacheAuxInfo(const uint8* buf, int size); + // Returns the maximum buffer location at which no data earlier in the stream // will be required in order to read the current or any subsequent sample. You // may clear all data up to this offset before reading the current sample @@ -52,15 +65,17 @@ class MEDIA_EXPORT TrackRunIterator { // Returns the minimum timestamp (or kInfiniteDuration if no runs present). TimeDelta GetMinDecodeTimestamp(); - // Property of the current run. Only valid if RunIsValid(). + // Property of the current run. Only valid if IsRunValid(). uint32 track_id() const; + int64 aux_info_offset() const; + int aux_info_size() const; bool is_encrypted() const; bool is_audio() const; // Only one is valid, based on the value of is_audio(). const AudioSampleEntry& audio_description() const; const VideoSampleEntry& video_description() const; - // Properties of the current sample. Only valid if SampleIsValid(). + // Properties of the current sample. Only valid if IsSampleValid(). int64 sample_offset() const; int sample_size() const; TimeDelta dts() const; @@ -68,8 +83,13 @@ class MEDIA_EXPORT TrackRunIterator { TimeDelta duration() const; bool is_keyframe() const; + // Only call when is_encrypted() is true and AuxInfoNeedsToBeCached() is + // false. Result is owned by caller. + scoped_ptr<DecryptConfig> GetDecryptConfig(); + private: void ResetRun(); + const TrackEncryption& track_encryption() const; const Movie* moov_; @@ -77,6 +97,8 @@ class MEDIA_EXPORT TrackRunIterator { std::vector<TrackRunInfo>::const_iterator run_itr_; std::vector<SampleInfo>::const_iterator sample_itr_; + std::vector<FrameCENCInfo> cenc_info_; + int64 sample_dts_; int64 sample_offset_; diff --git a/media/mp4/track_run_iterator_unittest.cc b/media/mp4/track_run_iterator_unittest.cc index 0b5a967..e1c1a03 100644 --- a/media/mp4/track_run_iterator_unittest.cc +++ b/media/mp4/track_run_iterator_unittest.cc @@ -19,6 +19,24 @@ static const int kVideoScale = 25; static const uint32 kSampleIsDifferenceSampleFlagMask = 0x10000; +static const uint8 kAuxInfo[] = { + 0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x31, + 0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x32, + 0x00, 0x02, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x04 +}; + +static const char kIv1[] = { + 0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x31, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static const uint8 kKeyId[] = { + 0x41, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x54, + 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x44 +}; + namespace media { namespace mp4 { @@ -40,19 +58,25 @@ class TrackRunIteratorTest : public testing::Test { moov_.tracks[0].media.header.timescale = kAudioScale; SampleDescription& desc1 = moov_.tracks[0].media.information.sample_table.description; + AudioSampleEntry aud_desc; + aud_desc.format = FOURCC_MP4A; + aud_desc.sinf.info.track_encryption.is_encrypted = false; desc1.type = kAudio; - desc1.audio_entries.resize(1); - desc1.audio_entries[0].format = FOURCC_MP4A; + desc1.audio_entries.push_back(aud_desc); moov_.extends.tracks[0].track_id = 1; + moov_.extends.tracks[0].default_sample_description_index = 1; moov_.tracks[1].header.track_id = 2; moov_.tracks[1].media.header.timescale = kVideoScale; SampleDescription& desc2 = moov_.tracks[1].media.information.sample_table.description; + VideoSampleEntry vid_desc; + vid_desc.format = FOURCC_AVC1; + vid_desc.sinf.info.track_encryption.is_encrypted = false; desc2.type = kVideo; - desc2.video_entries.resize(1); - desc2.video_entries[0].sinf.info.track_encryption.is_encrypted = false; + desc2.video_entries.push_back(vid_desc); moov_.extends.tracks[1].track_id = 2; + moov_.extends.tracks[1].default_sample_description_index = 1; moov_.tracks[2].header.track_id = 3; moov_.tracks[2].media.information.sample_table.description.type = kHint; @@ -66,7 +90,6 @@ class TrackRunIteratorTest : public testing::Test { moof.tracks[0].header.has_default_sample_flags = true; moof.tracks[0].header.default_sample_duration = 1024; moof.tracks[0].header.default_sample_size = 4; - moof.tracks[0].header.sample_description_index = 0; moof.tracks[0].runs.resize(2); moof.tracks[0].runs[0].sample_count = 10; moof.tracks[0].runs[0].data_offset = 100; @@ -77,7 +100,6 @@ class TrackRunIteratorTest : public testing::Test { moof.tracks[1].header.track_id = 2; moof.tracks[1].header.has_default_sample_flags = false; - moof.tracks[1].header.sample_description_index = 0; moof.tracks[1].decode_time.decode_time = 10; moof.tracks[1].runs.resize(1); moof.tracks[1].runs[0].sample_count = 10; @@ -93,6 +115,36 @@ class TrackRunIteratorTest : public testing::Test { return moof; } + // Update the first sample description of a Track to indicate encryption + void AddEncryption(Track* track) { + SampleDescription* stsd = + &track->media.information.sample_table.description; + ProtectionSchemeInfo* sinf; + if (!stsd->video_entries.empty()) { + sinf = &stsd->video_entries[0].sinf; + } else { + sinf = &stsd->audio_entries[0].sinf; + } + + sinf->type.type = FOURCC_CENC; + sinf->info.track_encryption.is_encrypted = true; + sinf->info.track_encryption.default_iv_size = 8; + sinf->info.track_encryption.default_kid.insert( + sinf->info.track_encryption.default_kid.begin(), + kKeyId, kKeyId + arraysize(kKeyId)); + } + + // Add aux info covering the first track run to a TrackFragment, and update + // the run to ensure it matches length and subsample information. + void AddAuxInfoHeaders(int offset, TrackFragment* frag) { + frag->auxiliary_offset.offsets.push_back(offset); + frag->auxiliary_size.sample_count = 2; + frag->auxiliary_size.sample_info_sizes.push_back(8); + frag->auxiliary_size.sample_info_sizes.push_back(22); + frag->runs[0].sample_count = 2; + frag->runs[0].sample_sizes[1] = 10; + } + void SetAscending(std::vector<uint32>* vec) { vec->resize(10); for (size_t i = 0; i < vec->size(); i++) @@ -103,8 +155,8 @@ class TrackRunIteratorTest : public testing::Test { TEST_F(TrackRunIteratorTest, NoRunsTest) { iter_.reset(new TrackRunIterator(&moov_)); ASSERT_TRUE(iter_->Init(MovieFragment())); - EXPECT_FALSE(iter_->RunIsValid()); - EXPECT_FALSE(iter_->SampleIsValid()); + EXPECT_FALSE(iter_->IsRunValid()); + EXPECT_FALSE(iter_->IsSampleValid()); } TEST_F(TrackRunIteratorTest, BasicOperationTest) { @@ -114,14 +166,14 @@ TEST_F(TrackRunIteratorTest, BasicOperationTest) { // Test that runs are sorted correctly, and that properties of the initial // sample of the first run are correct ASSERT_TRUE(iter_->Init(moof)); - EXPECT_TRUE(iter_->RunIsValid()); + EXPECT_TRUE(iter_->IsRunValid()); EXPECT_FALSE(iter_->is_encrypted()); EXPECT_EQ(iter_->track_id(), 1u); EXPECT_EQ(iter_->sample_offset(), 100); EXPECT_EQ(iter_->sample_size(), 1); - EXPECT_EQ(iter_->dts(), TimeDeltaFromFrac(0, kAudioScale)); - EXPECT_EQ(iter_->cts(), TimeDeltaFromFrac(0, kAudioScale)); - EXPECT_EQ(iter_->duration(), TimeDeltaFromFrac(1024, kAudioScale)); + EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(0, kAudioScale)); + EXPECT_EQ(iter_->cts(), TimeDeltaFromRational(0, kAudioScale)); + EXPECT_EQ(iter_->duration(), TimeDeltaFromRational(1024, kAudioScale)); EXPECT_TRUE(iter_->is_keyframe()); // Advance to the last sample in the current run, and test its properties @@ -129,13 +181,13 @@ TEST_F(TrackRunIteratorTest, BasicOperationTest) { EXPECT_EQ(iter_->track_id(), 1u); EXPECT_EQ(iter_->sample_offset(), 100 + kSumAscending1); EXPECT_EQ(iter_->sample_size(), 10); - EXPECT_EQ(iter_->dts(), TimeDeltaFromFrac(1024 * 9, kAudioScale)); - EXPECT_EQ(iter_->duration(), TimeDeltaFromFrac(1024, kAudioScale)); + EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(1024 * 9, kAudioScale)); + EXPECT_EQ(iter_->duration(), TimeDeltaFromRational(1024, kAudioScale)); EXPECT_TRUE(iter_->is_keyframe()); // Test end-of-run iter_->AdvanceSample(); - EXPECT_FALSE(iter_->SampleIsValid()); + EXPECT_FALSE(iter_->IsSampleValid()); // Test last sample of next run iter_->AdvanceRun(); @@ -145,20 +197,20 @@ TEST_F(TrackRunIteratorTest, BasicOperationTest) { EXPECT_EQ(iter_->sample_offset(), 200 + kSumAscending1); EXPECT_EQ(iter_->sample_size(), 10); int64 base_dts = kSumAscending1 + moof.tracks[1].decode_time.decode_time; - EXPECT_EQ(iter_->dts(), TimeDeltaFromFrac(base_dts, kVideoScale)); - EXPECT_EQ(iter_->duration(), TimeDeltaFromFrac(10, kVideoScale)); + EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(base_dts, kVideoScale)); + EXPECT_EQ(iter_->duration(), TimeDeltaFromRational(10, kVideoScale)); EXPECT_FALSE(iter_->is_keyframe()); // Test final run iter_->AdvanceRun(); EXPECT_EQ(iter_->track_id(), 1u); - EXPECT_EQ(iter_->dts(), TimeDeltaFromFrac(1024 * 10, kAudioScale)); + EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(1024 * 10, kAudioScale)); iter_->AdvanceSample(); EXPECT_EQ(moof.tracks[0].runs[1].data_offset + moof.tracks[0].header.default_sample_size, iter_->sample_offset()); iter_->AdvanceRun(); - EXPECT_FALSE(iter_->RunIsValid()); + EXPECT_FALSE(iter_->IsRunValid()); } TEST_F(TrackRunIteratorTest, TrackExtendsDefaultsTest) { @@ -177,8 +229,8 @@ TEST_F(TrackRunIteratorTest, TrackExtendsDefaultsTest) { EXPECT_FALSE(iter_->is_keyframe()); EXPECT_EQ(iter_->sample_size(), 3); EXPECT_EQ(iter_->sample_offset(), moof.tracks[0].runs[0].data_offset + 3); - EXPECT_EQ(iter_->duration(), TimeDeltaFromFrac(50, kAudioScale)); - EXPECT_EQ(iter_->dts(), TimeDeltaFromFrac(50, kAudioScale)); + EXPECT_EQ(iter_->duration(), TimeDeltaFromRational(50, kAudioScale)); + EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(50, kAudioScale)); } TEST_F(TrackRunIteratorTest, FirstSampleFlagTest) { @@ -203,8 +255,8 @@ TEST_F(TrackRunIteratorTest, MinDecodeTest) { MovieFragment moof = CreateFragment(); moof.tracks[0].decode_time.decode_time = kAudioScale; ASSERT_TRUE(iter_->Init(moof)); - EXPECT_EQ(TimeDeltaFromFrac(moof.tracks[1].decode_time.decode_time, - kVideoScale), + EXPECT_EQ(TimeDeltaFromRational(moof.tracks[1].decode_time.decode_time, + kVideoScale), iter_->GetMinDecodeTimestamp()); } @@ -219,13 +271,143 @@ TEST_F(TrackRunIteratorTest, ReorderingTest) { moof.tracks[1].decode_time.decode_time = 0; ASSERT_TRUE(iter_->Init(moof)); iter_->AdvanceRun(); - EXPECT_EQ(iter_->dts(), TimeDeltaFromFrac(0, kVideoScale)); - EXPECT_EQ(iter_->cts(), TimeDeltaFromFrac(2, kVideoScale)); - EXPECT_EQ(iter_->duration(), TimeDeltaFromFrac(1, kVideoScale)); + EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(0, kVideoScale)); + EXPECT_EQ(iter_->cts(), TimeDeltaFromRational(2, kVideoScale)); + EXPECT_EQ(iter_->duration(), TimeDeltaFromRational(1, kVideoScale)); iter_->AdvanceSample(); - EXPECT_EQ(iter_->dts(), TimeDeltaFromFrac(1, kVideoScale)); - EXPECT_EQ(iter_->cts(), TimeDeltaFromFrac(0, kVideoScale)); - EXPECT_EQ(iter_->duration(), TimeDeltaFromFrac(2, kVideoScale)); + EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(1, kVideoScale)); + EXPECT_EQ(iter_->cts(), TimeDeltaFromRational(0, kVideoScale)); + EXPECT_EQ(iter_->duration(), TimeDeltaFromRational(2, kVideoScale)); +} + +TEST_F(TrackRunIteratorTest, IgnoreUnknownAuxInfoTest) { + iter_.reset(new TrackRunIterator(&moov_)); + MovieFragment moof = CreateFragment(); + moof.tracks[1].auxiliary_offset.offsets.push_back(50); + moof.tracks[1].auxiliary_size.default_sample_info_size = 2; + moof.tracks[1].auxiliary_size.sample_count = 2; + moof.tracks[1].runs[0].sample_count = 2; + ASSERT_TRUE(iter_->Init(moof)); + iter_->AdvanceRun(); + EXPECT_FALSE(iter_->AuxInfoNeedsToBeCached()); +} + +TEST_F(TrackRunIteratorTest, DecryptConfigTest) { + AddEncryption(&moov_.tracks[1]); + iter_.reset(new TrackRunIterator(&moov_)); + + MovieFragment moof = CreateFragment(); + AddAuxInfoHeaders(50, &moof.tracks[1]); + + ASSERT_TRUE(iter_->Init(moof)); + + // The run for track 2 will be first, since its aux info offset is the first + // element in the file. + EXPECT_EQ(iter_->track_id(), 2u); + EXPECT_TRUE(iter_->is_encrypted()); + EXPECT_TRUE(iter_->AuxInfoNeedsToBeCached()); + EXPECT_EQ(static_cast<uint32>(iter_->aux_info_size()), arraysize(kAuxInfo)); + EXPECT_EQ(iter_->aux_info_offset(), 50); + EXPECT_EQ(iter_->GetMaxClearOffset(), 50); + EXPECT_FALSE(iter_->CacheAuxInfo(NULL, 0)); + EXPECT_FALSE(iter_->CacheAuxInfo(kAuxInfo, 3)); + EXPECT_TRUE(iter_->AuxInfoNeedsToBeCached()); + EXPECT_TRUE(iter_->CacheAuxInfo(kAuxInfo, arraysize(kAuxInfo))); + EXPECT_FALSE(iter_->AuxInfoNeedsToBeCached()); + EXPECT_EQ(iter_->sample_offset(), 200); + EXPECT_EQ(iter_->GetMaxClearOffset(), moof.tracks[0].runs[0].data_offset); + scoped_ptr<DecryptConfig> config = iter_->GetDecryptConfig(); + ASSERT_EQ(arraysize(kKeyId), config->key_id().size()); + EXPECT_TRUE(!memcmp(kKeyId, config->key_id().data(), + config->key_id().size())); + ASSERT_EQ(arraysize(kIv1), config->iv().size()); + EXPECT_TRUE(!memcmp(kIv1, config->iv().data(), config->iv().size())); + EXPECT_TRUE(config->subsamples().empty()); + iter_->AdvanceSample(); + config = iter_->GetDecryptConfig(); + EXPECT_EQ(config->subsamples().size(), 2u); + EXPECT_EQ(config->subsamples()[0].clear_bytes, 1u); + EXPECT_EQ(config->subsamples()[1].cypher_bytes, 4u); +} + +// It is legal for aux info blocks to be shared among multiple formats. +TEST_F(TrackRunIteratorTest, SharedAuxInfoTest) { + AddEncryption(&moov_.tracks[0]); + AddEncryption(&moov_.tracks[1]); + iter_.reset(new TrackRunIterator(&moov_)); + + MovieFragment moof = CreateFragment(); + moof.tracks[0].runs.resize(1); + AddAuxInfoHeaders(50, &moof.tracks[0]); + AddAuxInfoHeaders(50, &moof.tracks[1]); + moof.tracks[0].auxiliary_size.default_sample_info_size = 8; + + ASSERT_TRUE(iter_->Init(moof)); + EXPECT_EQ(iter_->track_id(), 1u); + EXPECT_EQ(iter_->aux_info_offset(), 50); + EXPECT_TRUE(iter_->CacheAuxInfo(kAuxInfo, arraysize(kAuxInfo))); + scoped_ptr<DecryptConfig> config = iter_->GetDecryptConfig(); + ASSERT_EQ(arraysize(kIv1), config->iv().size()); + EXPECT_TRUE(!memcmp(kIv1, config->iv().data(), config->iv().size())); + iter_->AdvanceSample(); + EXPECT_EQ(iter_->GetMaxClearOffset(), 50); + iter_->AdvanceRun(); + EXPECT_EQ(iter_->GetMaxClearOffset(), 50); + EXPECT_EQ(iter_->aux_info_offset(), 50); + EXPECT_TRUE(iter_->CacheAuxInfo(kAuxInfo, arraysize(kAuxInfo))); + EXPECT_EQ(iter_->GetMaxClearOffset(), 200); + ASSERT_EQ(arraysize(kIv1), config->iv().size()); + EXPECT_TRUE(!memcmp(kIv1, config->iv().data(), config->iv().size())); + iter_->AdvanceSample(); + EXPECT_EQ(iter_->GetMaxClearOffset(), 201); +} + +// Sensible files are expected to place auxiliary information for a run +// immediately before the main data for that run. Alternative schemes are +// possible, however, including the somewhat reasonable behavior of placing all +// aux info at the head of the 'mdat' box together, and the completely +// unreasonable behavior demonstrated here: +// byte 50: track 2, run 1 aux info +// byte 100: track 1, run 1 data +// byte 200: track 2, run 1 data +// byte 201: track 1, run 2 aux info (*inside* track 2, run 1 data) +// byte 10000: track 1, run 2 data +// byte 20000: track 1, run 1 aux info +TEST_F(TrackRunIteratorTest, UnexpectedOrderingTest) { + AddEncryption(&moov_.tracks[0]); + AddEncryption(&moov_.tracks[1]); + iter_.reset(new TrackRunIterator(&moov_)); + + MovieFragment moof = CreateFragment(); + AddAuxInfoHeaders(20000, &moof.tracks[0]); + moof.tracks[0].auxiliary_offset.offsets.push_back(201); + moof.tracks[0].auxiliary_size.sample_count += 2; + moof.tracks[0].auxiliary_size.default_sample_info_size = 8; + moof.tracks[0].runs[1].sample_count = 2; + AddAuxInfoHeaders(50, &moof.tracks[1]); + moof.tracks[1].runs[0].sample_sizes[0] = 5; + + ASSERT_TRUE(iter_->Init(moof)); + EXPECT_EQ(iter_->track_id(), 2u); + EXPECT_EQ(iter_->aux_info_offset(), 50); + EXPECT_EQ(iter_->sample_offset(), 200); + EXPECT_TRUE(iter_->CacheAuxInfo(kAuxInfo, arraysize(kAuxInfo))); + EXPECT_EQ(iter_->GetMaxClearOffset(), 100); + iter_->AdvanceRun(); + EXPECT_EQ(iter_->track_id(), 1u); + EXPECT_EQ(iter_->aux_info_offset(), 20000); + EXPECT_EQ(iter_->sample_offset(), 100); + EXPECT_TRUE(iter_->CacheAuxInfo(kAuxInfo, arraysize(kAuxInfo))); + EXPECT_EQ(iter_->GetMaxClearOffset(), 100); + iter_->AdvanceSample(); + EXPECT_EQ(iter_->GetMaxClearOffset(), 101); + iter_->AdvanceRun(); + EXPECT_EQ(iter_->track_id(), 1u); + EXPECT_EQ(iter_->aux_info_offset(), 201); + EXPECT_EQ(iter_->sample_offset(), 10000); + EXPECT_EQ(iter_->GetMaxClearOffset(), 201); + EXPECT_TRUE(iter_->CacheAuxInfo(kAuxInfo, arraysize(kAuxInfo))); + EXPECT_EQ(iter_->GetMaxClearOffset(), 10000); } } // namespace mp4 diff --git a/media/webm/webm_cluster_parser.cc b/media/webm/webm_cluster_parser.cc index 20d131e..14a360d 100644 --- a/media/webm/webm_cluster_parser.cc +++ b/media/webm/webm_cluster_parser.cc @@ -235,10 +235,15 @@ bool WebMClusterParser::OnBlock(int track_num, int timecode, scoped_array<uint8> counter_block(GenerateCounterBlock(iv)); buffer->SetDecryptConfig(scoped_ptr<DecryptConfig>(new DecryptConfig( - video_encryption_key_id_.get(), video_encryption_key_id_size_, - counter_block.get(), DecryptConfig::kDecryptionKeySize, - data, kWebMHmacSize, - sizeof(iv)))); + std::string( + reinterpret_cast<const char*>(video_encryption_key_id_.get()), + video_encryption_key_id_size_), + std::string( + reinterpret_cast<const char*>(counter_block.get()), + DecryptConfig::kDecryptionKeySize), + std::string(reinterpret_cast<const char*>(data), kWebMHmacSize), + sizeof(iv), + std::vector<SubsampleEntry>()))); } buffer->SetTimestamp(timestamp); |