diff --git a/packager/media/formats/mp4/box_definitions.cc b/packager/media/formats/mp4/box_definitions.cc index 581d46e2ad..4900dbfb75 100644 --- a/packager/media/formats/mp4/box_definitions.cc +++ b/packager/media/formats/mp4/box_definitions.cc @@ -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) {} diff --git a/packager/media/formats/mp4/box_definitions.h b/packager/media/formats/mp4/box_definitions.h index 344ec76dad..e4a5821978 100644 --- a/packager/media/formats/mp4/box_definitions.h +++ b/packager/media/formats/mp4/box_definitions.h @@ -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 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 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 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 constant_iv; }; struct SampleGroupDescription : FullBox { diff --git a/packager/media/formats/mp4/box_definitions_comparison.h b/packager/media/formats/mp4/box_definitions_comparison.h index 7afb92315f..21b8cc79f2 100644 --- a/packager/media/formats/mp4/box_definitions_comparison.h +++ b/packager/media/formats/mp4/box_definitions_comparison.h @@ -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, diff --git a/packager/media/formats/mp4/box_definitions_unittest.cc b/packager/media/formats/mp4/box_definitions_unittest.cc index f81515af84..b609940c12 100644 --- a/packager/media/formats/mp4/box_definitions_unittest.cc +++ b/packager/media/formats/mp4/box_definitions_unittest.cc @@ -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()); diff --git a/packager/media/formats/mp4/key_rotation_fragmenter.cc b/packager/media/formats/mp4/key_rotation_fragmenter.cc index 2d25d677f3..80ff991b58 100644 --- a/packager/media/formats/mp4/key_rotation_fragmenter.cc +++ b/packager/media/formats/mp4/key_rotation_fragmenter.cc @@ -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; diff --git a/packager/media/formats/mp4/mp4_media_parser.cc b/packager/media/formats/mp4/mp4_media_parser.cc index 0a78985d1c..ff61d7219f 100644 --- a/packager/media/formats/mp4/mp4_media_parser.cc +++ b/packager/media/formats/mp4/mp4_media_parser.cc @@ -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, diff --git a/packager/media/formats/mp4/segmenter.cc b/packager/media/formats/mp4/segmenter.cc index 761801b206..484deed050 100644 --- a/packager/media/formats/mp4/segmenter.cc +++ b/packager/media/formats/mp4/segmenter.cc @@ -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; } diff --git a/packager/media/formats/mp4/track_run_iterator.cc b/packager/media/formats/mp4/track_run_iterator.cc index 240d8b7b60..f57e55357a 100644 --- a/packager/media/formats/mp4/track_run_iterator.cc +++ b/packager/media/formats/mp4/track_run_iterator.cc @@ -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 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 { diff --git a/packager/media/formats/mp4/track_run_iterator_unittest.cc b/packager/media/formats/mp4/track_run_iterator_unittest.cc index 2db536bec4..5169dbf7f4 100644 --- a/packager/media/formats/mp4/track_run_iterator_unittest.cc +++ b/packager/media/formats/mp4/track_run_iterator_unittest.cc @@ -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)); }