summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorstrobe@google.com <strobe@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-26 00:49:59 +0000
committerstrobe@google.com <strobe@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-26 00:49:59 +0000
commit9746f9132e55a91d2ec3d866711277b874574743 (patch)
treeb0f6d236afc3515855403363b6e9fe455a6c801d
parent7db8893ab741949612cebfed89e11d267daacbf9 (diff)
downloadchromium_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.cc32
-rw-r--r--media/base/decrypt_config.h80
-rw-r--r--media/crypto/aes_decryptor.cc180
-rw-r--r--media/crypto/aes_decryptor.h11
-rw-r--r--media/crypto/aes_decryptor_unittest.cc270
-rw-r--r--media/filters/ffmpeg_video_decoder_unittest.cc13
-rw-r--r--media/mp4/avc.cc27
-rw-r--r--media/mp4/avc.h4
-rw-r--r--media/mp4/avc_unittest.cc15
-rw-r--r--media/mp4/box_definitions.cc36
-rw-r--r--media/mp4/box_definitions.h4
-rw-r--r--media/mp4/cenc.cc27
-rw-r--r--media/mp4/cenc.h12
-rw-r--r--media/mp4/mp4_stream_parser.cc153
-rw-r--r--media/mp4/mp4_stream_parser.h7
-rw-r--r--media/mp4/mp4_stream_parser_unittest.cc29
-rw-r--r--media/mp4/track_run_iterator.cc221
-rw-r--r--media/mp4/track_run_iterator.h32
-rw-r--r--media/mp4/track_run_iterator_unittest.cc240
-rw-r--r--media/webm/webm_cluster_parser.cc13
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, &param_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);