Add mp4 box fields for pattern encryption

- crypt_byte_block and skip_byte_block for pattern-based encryption.
- constant_iv for constant iv (used by 'cbcs' protection scheme).
- Also renamed iv_size to per_sample_iv_size.
- Also changed "bool is_encrypted" to "uint8_t is_protected" to align
  with CENC spec.

Issue #78

Change-Id: I2878a91e0ebe536a08a3e3109daf157fe4440e27
This commit is contained in:
KongQun Yang 2016-04-12 15:39:10 -07:00
parent 4391f247ac
commit c8819cb257
9 changed files with 180 additions and 72 deletions

View File

@ -42,10 +42,13 @@ const char kVpcCompressorName[] = "\012VPC Coding";
// at once.
const int kCueSourceIdNotSet = -1;
const size_t kInvalidIvSize = 1;
// According to ISO/IEC FDIS 23001-7: CENC spec, IV should be either
// 64-bit (8-byte) or 128-bit (16-byte).
bool IsIvSizeValid(size_t iv_size) {
return iv_size == 8 || iv_size == 16;
// |per_sample_iv_size| of 0 means constant_iv is used.
bool IsIvSizeValid(size_t per_sample_iv_size) {
return per_sample_iv_size == 0 || per_sample_iv_size == 8 ||
per_sample_iv_size == 16;
}
// Default values to construct the following fields in ddts box. Values are set
@ -295,7 +298,7 @@ uint32_t SampleEncryptionEntry::GetTotalSizeOfSubsamples() const {
return size;
}
SampleEncryption::SampleEncryption() : iv_size(0) {}
SampleEncryption::SampleEncryption() : iv_size(kInvalidIvSize) {}
SampleEncryption::~SampleEncryption() {}
FourCC SampleEncryption::BoxType() const { return FOURCC_senc; }
@ -304,14 +307,16 @@ bool SampleEncryption::ReadWriteInternal(BoxBuffer* buffer) {
// If we don't know |iv_size|, store sample encryption data to parse later
// after we know iv_size.
if (buffer->Reading() && iv_size == 0) {
if (buffer->Reading() && iv_size == kInvalidIvSize) {
RCHECK(
buffer->ReadWriteVector(&sample_encryption_data, buffer->BytesLeft()));
return true;
}
if (!IsIvSizeValid(iv_size)) {
LOG(ERROR) << "IV_size can only be 8 or 16, but seeing " << iv_size;
LOG(ERROR)
<< "IV_size can only be 8 or 16 or 0 for constant iv, but seeing "
<< iv_size;
return false;
}
@ -392,7 +397,11 @@ uint32_t SchemeType::ComputeSizeInternal() {
}
TrackEncryption::TrackEncryption()
: is_encrypted(false), default_iv_size(0), default_kid(16, 0) {}
: default_is_protected(0),
default_per_sample_iv_size(0),
default_kid(16, 0),
default_crypt_byte_block(0),
default_skip_byte_block(0) {}
TrackEncryption::~TrackEncryption() {}
FourCC TrackEncryption::BoxType() const { return FOURCC_tenc; }
@ -404,27 +413,50 @@ bool TrackEncryption::ReadWriteInternal(BoxBuffer* buffer) {
<< ". Resized accordingly.";
default_kid.resize(kCencKeyIdSize);
}
RCHECK(default_crypt_byte_block < 16 && default_skip_byte_block < 16);
if (default_crypt_byte_block != 0 && default_skip_byte_block != 0) {
// Version 1 box is needed for pattern-based encryption.
version = 1;
}
}
uint8_t flag = is_encrypted ? 1 : 0;
RCHECK(ReadWriteHeaderInternal(buffer) &&
buffer->IgnoreBytes(2) && // reserved.
buffer->ReadWriteUInt8(&flag) &&
buffer->ReadWriteUInt8(&default_iv_size) &&
buffer->IgnoreBytes(1)); // reserved.
uint8_t pattern = default_crypt_byte_block << 4 | default_skip_byte_block;
RCHECK(buffer->ReadWriteUInt8(&pattern));
default_crypt_byte_block = pattern >> 4;
default_skip_byte_block = pattern & 0x0F;
RCHECK(buffer->ReadWriteUInt8(&default_is_protected) &&
buffer->ReadWriteUInt8(&default_per_sample_iv_size) &&
buffer->ReadWriteVector(&default_kid, kCencKeyIdSize));
if (buffer->Reading()) {
is_encrypted = (flag != 0);
if (is_encrypted) {
RCHECK(default_iv_size == 8 || default_iv_size == 16);
if (default_is_protected == 1) {
if (default_per_sample_iv_size == 0) { // For constant iv.
uint8_t default_constant_iv_size = default_constant_iv.size();
RCHECK(buffer->ReadWriteUInt8(&default_constant_iv_size));
RCHECK(default_constant_iv_size == 8 || default_constant_iv_size == 16);
RCHECK(buffer->ReadWriteVector(&default_constant_iv,
default_constant_iv_size));
} else {
RCHECK(default_iv_size == 0);
RCHECK(default_per_sample_iv_size == 8 ||
default_per_sample_iv_size == 16);
RCHECK(default_constant_iv.empty());
}
} else {
// Expect |default_is_protected| to be 0, i.e. not protected. Other values
// of |default_is_protected| is not supported.
RCHECK(default_is_protected == 0);
RCHECK(default_per_sample_iv_size == 0);
}
return true;
}
uint32_t TrackEncryption::ComputeSizeInternal() {
return HeaderSize() + sizeof(uint32_t) + kCencKeyIdSize;
return HeaderSize() + sizeof(uint32_t) + kCencKeyIdSize +
(default_constant_iv.empty() ? 0 : (sizeof(uint8_t) +
default_constant_iv.size()));
}
SchemeInfo::SchemeInfo() {}
@ -2171,8 +2203,10 @@ uint32_t SampleToGroup::ComputeSizeInternal() {
}
CencSampleEncryptionInfoEntry::CencSampleEncryptionInfoEntry()
: is_encrypted(false), iv_size(0) {
}
: is_protected(0),
per_sample_iv_size(0),
crypt_byte_block(0),
skip_byte_block(0) {}
CencSampleEncryptionInfoEntry::~CencSampleEncryptionInfoEntry() {};
SampleGroupDescription::SampleGroupDescription() : grouping_type(0) {}
@ -2220,22 +2254,41 @@ bool SampleGroupDescription::ReadWriteInternal(BoxBuffer* buffer) {
<< ". Resized accordingly.";
entries[i].key_id.resize(kCencKeyIdSize);
}
RCHECK(entries[i].crypt_byte_block < 16 &&
entries[i].skip_byte_block < 16);
}
uint8_t flag = entries[i].is_encrypted ? 1 : 0;
RCHECK(buffer->IgnoreBytes(2) && // reserved.
buffer->ReadWriteUInt8(&flag) &&
buffer->ReadWriteUInt8(&entries[i].iv_size) &&
RCHECK(buffer->IgnoreBytes(1)); // reserved.
uint8_t pattern =
entries[i].crypt_byte_block << 4 | entries[i].skip_byte_block;
RCHECK(buffer->ReadWriteUInt8(&pattern));
entries[i].crypt_byte_block = pattern >> 4;
entries[i].skip_byte_block = pattern & 0x0F;
RCHECK(buffer->ReadWriteUInt8(&entries[i].is_protected) &&
buffer->ReadWriteUInt8(&entries[i].per_sample_iv_size) &&
buffer->ReadWriteVector(&entries[i].key_id, kCencKeyIdSize));
if (buffer->Reading()) {
entries[i].is_encrypted = (flag != 0);
if (entries[i].is_encrypted) {
RCHECK(entries[i].iv_size == 8 || entries[i].iv_size == 16);
if (entries[i].is_protected == 1) {
if (entries[i].per_sample_iv_size == 0) { // For constant iv.
uint8_t constant_iv_size = entries[i].constant_iv.size();
RCHECK(buffer->ReadWriteUInt8(&constant_iv_size));
RCHECK(constant_iv_size == 8 || constant_iv_size == 16);
RCHECK(
buffer->ReadWriteVector(&entries[i].constant_iv, constant_iv_size));
} else {
RCHECK(entries[i].iv_size == 0);
RCHECK(entries[i].per_sample_iv_size == 8 ||
entries[i].per_sample_iv_size == 16);
RCHECK(entries[i].constant_iv.empty());
}
} else {
// Expect |is_protected| to be 0, i.e. not protected. Other values of
// |is_protected| is not supported.
RCHECK(entries[i].is_protected == 0);
RCHECK(entries[i].per_sample_iv_size == 0);
}
}
return true;
}
@ -2246,10 +2299,16 @@ uint32_t SampleGroupDescription::ComputeSizeInternal() {
// This box is optional. Skip it if it is not used.
if (entries.empty())
return 0;
const size_t kEntrySize = sizeof(uint32_t) + kCencKeyIdSize;
size_t entries_size = 0;
for (const auto& entry : entries) {
entries_size += sizeof(uint32_t) + kCencKeyIdSize +
(entry.constant_iv.empty()
? 0
: (sizeof(uint8_t) + entry.constant_iv.size()));
}
return HeaderSize() + sizeof(grouping_type) +
(version == 1 ? sizeof(uint32_t) : 0) + sizeof(uint32_t) +
entries.size() * kEntrySize;
entries_size;
}
TrackFragment::TrackFragment() : decode_time_absent(false) {}

View File

@ -144,10 +144,17 @@ struct SchemeType : FullBox {
struct TrackEncryption : FullBox {
DECLARE_BOX_METHODS(TrackEncryption);
// Note: this definition is specific to the CENC protection type.
bool is_encrypted;
uint8_t default_iv_size;
uint8_t default_is_protected;
uint8_t default_per_sample_iv_size;
std::vector<uint8_t> default_kid;
// For pattern-based encryption.
uint8_t default_crypt_byte_block;
uint8_t default_skip_byte_block;
// Present only if
// |default_is_protected == 1 && default_per_sample_iv_size == 0|.
std::vector<uint8_t> default_constant_iv;
};
struct SchemeInfo : Box {
@ -660,9 +667,16 @@ struct CencSampleEncryptionInfoEntry {
CencSampleEncryptionInfoEntry();
~CencSampleEncryptionInfoEntry();
bool is_encrypted;
uint8_t iv_size;
uint8_t is_protected;
uint8_t per_sample_iv_size;
std::vector<uint8_t> key_id;
// For pattern-based encryption.
uint8_t crypt_byte_block;
uint8_t skip_byte_block;
// Present only if |is_protected == 1 && per_sample_iv_size == 0|.
std::vector<uint8_t> constant_iv;
};
struct SampleGroupDescription : FullBox {

View File

@ -65,9 +65,12 @@ inline bool operator==(const SchemeType& lhs, const SchemeType& rhs) {
}
inline bool operator==(const TrackEncryption& lhs, const TrackEncryption& rhs) {
return lhs.is_encrypted == rhs.is_encrypted &&
lhs.default_iv_size == rhs.default_iv_size &&
lhs.default_kid == rhs.default_kid;
return lhs.default_is_protected == rhs.default_is_protected &&
lhs.default_per_sample_iv_size == rhs.default_per_sample_iv_size &&
lhs.default_kid == rhs.default_kid &&
lhs.default_crypt_byte_block == rhs.default_crypt_byte_block &&
lhs.default_skip_byte_block == rhs.default_skip_byte_block &&
lhs.default_constant_iv == rhs.default_constant_iv;
}
inline bool operator==(const SchemeInfo& lhs, const SchemeInfo& rhs) {
@ -395,9 +398,12 @@ inline bool operator==(const SampleToGroup& lhs,
inline bool operator==(const CencSampleEncryptionInfoEntry& lhs,
const CencSampleEncryptionInfoEntry& rhs) {
return lhs.is_encrypted == rhs.is_encrypted &&
lhs.iv_size == rhs.iv_size &&
lhs.key_id == rhs.key_id;
return lhs.is_protected == rhs.is_protected &&
lhs.per_sample_iv_size == rhs.per_sample_iv_size &&
lhs.key_id == rhs.key_id &&
lhs.crypt_byte_block == rhs.crypt_byte_block &&
lhs.skip_byte_block == rhs.skip_byte_block &&
lhs.constant_iv == rhs.constant_iv;
}
inline bool operator==(const SampleGroupDescription& lhs,

View File

@ -209,15 +209,21 @@ class BoxDefinitionsTestGeneral : public testing::Test {
void Modify(SchemeType* schm) { schm->version = 123; }
void Fill(TrackEncryption* tenc) {
tenc->is_encrypted = true;
tenc->default_iv_size = 8;
tenc->default_is_protected = 1;
tenc->default_per_sample_iv_size = 8;
tenc->default_kid.assign(kData16Bytes,
kData16Bytes + arraysize(kData16Bytes));
tenc->default_skip_byte_block = 2;
tenc->default_crypt_byte_block = 8;
tenc->version = 1;
}
void Modify(TrackEncryption* tenc) {
tenc->is_encrypted = false;
tenc->default_iv_size = 0;
tenc->default_is_protected = 0;
tenc->default_per_sample_iv_size = 0;
tenc->default_skip_byte_block = 0;
tenc->default_crypt_byte_block = 0;
tenc->version = 0;
}
void Fill(SchemeInfo* schi) { Fill(&schi->track_encryption); }
@ -764,14 +770,21 @@ class BoxDefinitionsTestGeneral : public testing::Test {
void Fill(SampleGroupDescription* sgpd) {
sgpd->grouping_type = FOURCC_seig;
sgpd->entries.resize(2);
sgpd->entries[0].is_encrypted = true;
sgpd->entries[0].iv_size = 8;
sgpd->entries.resize(3);
sgpd->entries[0].is_protected = 1;
sgpd->entries[0].per_sample_iv_size = 8;
sgpd->entries[0].key_id.assign(kData16Bytes,
kData16Bytes + arraysize(kData16Bytes));
sgpd->entries[1].is_encrypted = false;
sgpd->entries[1].iv_size = 0;
sgpd->entries[0].crypt_byte_block = 3;
sgpd->entries[0].skip_byte_block = 7;
sgpd->entries[1].is_protected = 0;
sgpd->entries[1].per_sample_iv_size = 0;
sgpd->entries[1].key_id.resize(16);
sgpd->entries[2].is_protected = 1;
sgpd->entries[2].per_sample_iv_size = 0;
sgpd->entries[2].constant_iv.assign(kData16Bytes,
kData16Bytes + arraysize(kData16Bytes));
sgpd->entries[2].key_id.resize(16);
sgpd->version = 1;
}
@ -1186,9 +1199,18 @@ TEST_F(BoxDefinitionsTest, TrackFragmentRun_NoSampleSize) {
ASSERT_EQ(trun, trun_readback);
}
TEST_F(BoxDefinitionsTest, SampleEncryptionIsOptional) {
SampleEncryption senc;
EXPECT_EQ(0u, senc.ComputeSize());
TEST_F(BoxDefinitionsTest, TrackEncryptionConstantIv) {
TrackEncryption tenc;
tenc.default_is_protected = 1;
tenc.default_per_sample_iv_size = 0;
tenc.default_kid.assign(kData16Bytes, kData16Bytes + arraysize(kData16Bytes));
tenc.default_constant_iv.assign(kData16Bytes,
kData16Bytes + arraysize(kData16Bytes));
tenc.Write(buffer_.get());
TrackEncryption tenc_readback;
ASSERT_TRUE(ReadBack(&tenc_readback));
ASSERT_EQ(tenc, tenc_readback);
}
TEST_F(BoxDefinitionsTest, SampleEncryptionWithIvKnownWhenReading) {
@ -1216,7 +1238,8 @@ TEST_F(BoxDefinitionsTest, SampleEncryptionWithIvUnknownWhenReading) {
senc.Write(buffer_.get());
SampleEncryption senc_readback;
senc_readback.iv_size = 0;
const size_t kInvalidIvSize = 1;
senc_readback.iv_size = kInvalidIvSize;
ASSERT_TRUE(ReadBack(&senc_readback));
EXPECT_NE(0u, senc_readback.sample_encryption_data.size());

View File

@ -105,8 +105,8 @@ Status KeyRotationFragmenter::PrepareFragmentForEncryption(
// Fill in SampleGroupDescription box information.
traf()->sample_group_description.grouping_type = FOURCC_seig;
traf()->sample_group_description.entries.resize(1);
traf()->sample_group_description.entries[0].is_encrypted = true;
traf()->sample_group_description.entries[0].iv_size =
traf()->sample_group_description.entries[0].is_protected = 1;
traf()->sample_group_description.entries[0].per_sample_iv_size =
encryptor()->iv().size();
traf()->sample_group_description.entries[0].key_id = encryption_key()->key_id;

View File

@ -430,7 +430,8 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
return false;
}
bool is_encrypted = entry.sinf.info.track_encryption.is_encrypted;
const bool is_encrypted =
entry.sinf.info.track_encryption.default_is_protected == 1;
DVLOG(1) << "is_audio_track_encrypted_: " << is_encrypted;
streams.push_back(new AudioStreamInfo(
track->header.track_id,
@ -533,7 +534,8 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
return false;
}
bool is_encrypted = entry.sinf.info.track_encryption.is_encrypted;
const bool is_encrypted =
entry.sinf.info.track_encryption.default_is_protected == 1;
DVLOG(1) << "is_video_track_encrypted_: " << is_encrypted;
streams.push_back(new VideoStreamInfo(
track->header.track_id, timescale, duration, video_codec,

View File

@ -58,9 +58,9 @@ void GenerateSinf(const EncryptionKey& encryption_key,
sinf->type.version = kCencSchemeVersion;
auto& track_encryption = sinf->info.track_encryption;
track_encryption.is_encrypted = true;
track_encryption.default_is_protected = 1;
DCHECK(!encryption_key.iv.empty());
track_encryption.default_iv_size = encryption_key.iv.size();
track_encryption.default_per_sample_iv_size = encryption_key.iv.size();
track_encryption.default_kid = encryption_key.key_id;
}

View File

@ -224,14 +224,16 @@ bool TrackRunIterator::Init() {
desc_idx = 0;
tri.audio_description = &stsd.audio_entries[desc_idx];
// We don't support encrypted non-fragmented mp4 for now.
RCHECK(!tri.audio_description->sinf.info.track_encryption.is_encrypted);
RCHECK(tri.audio_description->sinf.info.track_encryption
.default_is_protected == 0);
} else if (tri.track_type == kVideo) {
RCHECK(!stsd.video_entries.empty());
if (desc_idx > stsd.video_entries.size())
desc_idx = 0;
tri.video_description = &stsd.video_entries[desc_idx];
// We don't support encrypted non-fragmented mp4 for now.
RCHECK(!tri.video_description->sinf.info.track_encryption.is_encrypted);
RCHECK(tri.video_description->sinf.info.track_encryption
.default_is_protected == 0);
}
uint32_t samples_per_chunk = chunk_info.samples_per_chunk();
@ -333,12 +335,14 @@ bool TrackRunIterator::Init(const MovieFragment& moof) {
std::vector<SampleEncryptionEntry> sample_encryption_entries;
if (!traf.sample_encryption.sample_encryption_data.empty()) {
RCHECK(audio_sample_entry || video_sample_entry);
const uint8_t default_iv_size =
const uint8_t default_per_sample_iv_size =
audio_sample_entry
? audio_sample_entry->sinf.info.track_encryption.default_iv_size
: video_sample_entry->sinf.info.track_encryption.default_iv_size;
? audio_sample_entry->sinf.info.track_encryption
.default_per_sample_iv_size
: video_sample_entry->sinf.info.track_encryption
.default_per_sample_iv_size;
RCHECK(traf.sample_encryption.ParseFromSampleEncryptionData(
default_iv_size, &sample_encryption_entries));
default_per_sample_iv_size, &sample_encryption_entries));
}
int64_t run_start_dts = traf.decode_time_absent
@ -463,9 +467,11 @@ bool TrackRunIterator::CacheAuxInfo(const uint8_t* buf, int buf_size) {
info_size = run_itr_->aux_info_sizes[i];
BufferReader reader(buf + pos, info_size);
const bool has_subsamples = info_size > track_encryption().default_iv_size;
const bool has_subsamples =
info_size > track_encryption().default_per_sample_iv_size;
RCHECK(sample_encryption_entries[i].ParseFromBuffer(
track_encryption().default_iv_size, has_subsamples, &reader));
track_encryption().default_per_sample_iv_size, has_subsamples,
&reader));
pos += info_size;
}
@ -512,7 +518,7 @@ uint32_t TrackRunIterator::track_id() const {
bool TrackRunIterator::is_encrypted() const {
DCHECK(IsRunValid());
return track_encryption().is_encrypted;
return track_encryption().default_is_protected == 1;
}
int64_t TrackRunIterator::aux_info_offset() const {

View File

@ -87,7 +87,6 @@ class TrackRunIteratorTest : public testing::Test {
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.push_back(aud_desc);
moov_.extends.tracks[0].track_id = 1;
@ -99,7 +98,6 @@ class TrackRunIteratorTest : public testing::Test {
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.push_back(vid_desc);
moov_.extends.tracks[1].track_id = 2;
@ -155,8 +153,8 @@ class TrackRunIteratorTest : public testing::Test {
}
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_is_protected = 1;
sinf->info.track_encryption.default_per_sample_iv_size = 8;
sinf->info.track_encryption.default_kid.assign(kKeyId,
kKeyId + arraysize(kKeyId));
}