diff --git a/packager/media/formats/mp4/box.cc b/packager/media/formats/mp4/box.cc index 4d4f344462..4b42e0c054 100644 --- a/packager/media/formats/mp4/box.cc +++ b/packager/media/formats/mp4/box.cc @@ -13,7 +13,7 @@ namespace edash_packager { namespace media { namespace mp4 { -Box::Box() : atom_size(0) {} +Box::Box() : box_size_(0) {} Box::~Box() {} bool Box::Parse(BoxReader* reader) { @@ -24,21 +24,21 @@ bool Box::Parse(BoxReader* reader) { void Box::Write(BufferWriter* writer) { DCHECK(writer); - // Compute and update atom_size. + // Compute and update box size. uint32_t size = ComputeSize(); - DCHECK_EQ(size, this->atom_size); + DCHECK_EQ(size, box_size_); size_t buffer_size_before_write = writer->Size(); BoxBuffer buffer(writer); CHECK(ReadWriteInternal(&buffer)); - DCHECK_EQ(this->atom_size, writer->Size() - buffer_size_before_write); + DCHECK_EQ(box_size_, writer->Size() - buffer_size_before_write); } void Box::WriteHeader(BufferWriter* writer) { DCHECK(writer); - // Compute and update atom_size. + // Compute and update box size. uint32_t size = ComputeSize(); - DCHECK_EQ(size, this->atom_size); + DCHECK_EQ(size, box_size_); size_t buffer_size_before_write = writer->Size(); BoxBuffer buffer(writer); @@ -47,8 +47,8 @@ void Box::WriteHeader(BufferWriter* writer) { } uint32_t Box::ComputeSize() { - this->atom_size = ComputeSizeInternal(); - return this->atom_size; + box_size_ = ComputeSizeInternal(); + return box_size_; } uint32_t Box::HeaderSize() const { @@ -61,7 +61,7 @@ bool Box::ReadWriteHeaderInternal(BoxBuffer* buffer) { if (buffer->Reading()) { // Skip for read mode, which is handled already in BoxReader. } else { - CHECK(buffer->ReadWriteUInt32(&this->atom_size)); + CHECK(buffer->ReadWriteUInt32(&box_size_)); FourCC fourcc = BoxType(); CHECK(buffer->ReadWriteFourCC(&fourcc)); } diff --git a/packager/media/formats/mp4/box.h b/packager/media/formats/mp4/box.h index 7b1e72f234..ed9d65ed73 100644 --- a/packager/media/formats/mp4/box.h +++ b/packager/media/formats/mp4/box.h @@ -50,24 +50,28 @@ struct Box { /// @return box type. virtual FourCC BoxType() const = 0; + /// @return The size of result box including child boxes. Note that this + // function expects that ComputeSize has been invoked already. + uint32_t box_size() { return box_size_; } + protected: - /// Read/write mp4 box header. Note that this function expects box size - /// updated already. + /// Read/write mp4 box header. Note that this function expects that + /// ComputeSize has been invoked already. /// @return true on success, false otherwise. virtual bool ReadWriteHeaderInternal(BoxBuffer* buffer); private: friend class BoxBuffer; // Read/write the mp4 box from/to BoxBuffer. Note that this function expects - // box size updated already. + // that ComputeSize has been invoked already. virtual bool ReadWriteInternal(BoxBuffer* buffer) = 0; // Compute the size of this box. A value of 0 should be returned if the box // should not be written. Note that this function won't update box size. virtual uint32_t ComputeSizeInternal() = 0; - // We don't support 64-bit atom sizes. 32-bit should be large enough for our + // We don't support 64-bit box sizes. 32-bit should be large enough for our // current needs. - uint32_t atom_size; + uint32_t box_size_; // Not using DISALLOW_COPY_AND_ASSIGN here intentionally to allow the compiler // generated copy constructor and assignment operator. diff --git a/packager/media/formats/mp4/box_buffer.h b/packager/media/formats/mp4/box_buffer.h index 349b1b592e..9959ab4489 100644 --- a/packager/media/formats/mp4/box_buffer.h +++ b/packager/media/formats/mp4/box_buffer.h @@ -167,7 +167,7 @@ class BoxBuffer { if (reader_) return reader_->ReadChild(box); // The box is mandatory, i.e. the box size should not be 0. - DCHECK_NE(0u, box->atom_size); + DCHECK_NE(0u, box->box_size()); CHECK(box->ReadWriteInternal(this)); return true; } @@ -178,7 +178,7 @@ class BoxBuffer { if (reader_) return reader_->TryReadChild(box); // The box is optional, i.e. it can be skipped if the box size is 0. - if (box->atom_size != 0) + if (box->box_size() != 0) CHECK(box->ReadWriteInternal(this)); return true; } diff --git a/packager/media/formats/mp4/box_definitions.cc b/packager/media/formats/mp4/box_definitions.cc index d13294c023..03ae6ed93f 100644 --- a/packager/media/formats/mp4/box_definitions.cc +++ b/packager/media/formats/mp4/box_definitions.cc @@ -41,6 +41,12 @@ const char kVpcCompressorName[] = "\012VPC Coding"; // at once. const int kCueSourceIdNotSet = -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; +} + // Utility functions to check if the 64bit integers can fit in 32bit integer. bool IsFitIn32Bits(uint64_t a) { return a <= std::numeric_limits::max(); @@ -183,6 +189,143 @@ uint32_t SampleAuxiliaryInformationSize::ComputeSizeInternal() { (default_sample_info_size == 0 ? sample_info_sizes.size() : 0); } +SampleEncryptionEntry::SampleEncryptionEntry() {} +SampleEncryptionEntry::~SampleEncryptionEntry() {} + +bool SampleEncryptionEntry::ReadWrite(uint8_t iv_size, + bool has_subsamples, + BoxBuffer* buffer) { + DCHECK(IsIvSizeValid(iv_size)); + DCHECK(buffer); + + RCHECK(buffer->ReadWriteVector(&initialization_vector, iv_size)); + + if (!has_subsamples) { + subsamples.clear(); + return true; + } + + uint16_t subsample_count = subsamples.size(); + RCHECK(buffer->ReadWriteUInt16(&subsample_count)); + RCHECK(subsample_count > 0); + subsamples.resize(subsample_count); + for (auto& subsample : subsamples) { + RCHECK(buffer->ReadWriteUInt16(&subsample.clear_bytes) && + buffer->ReadWriteUInt32(&subsample.cipher_bytes)); + } + return true; +} + +bool SampleEncryptionEntry::ParseFromBuffer(uint8_t iv_size, + bool has_subsamples, + BufferReader* reader) { + DCHECK(IsIvSizeValid(iv_size)); + DCHECK(reader); + + initialization_vector.resize(iv_size); + RCHECK(reader->ReadToVector(&initialization_vector, iv_size)); + + if (!has_subsamples) { + subsamples.clear(); + return true; + } + + uint16_t subsample_count; + RCHECK(reader->Read2(&subsample_count)); + RCHECK(subsample_count > 0); + subsamples.resize(subsample_count); + for (auto& subsample : subsamples) { + RCHECK(reader->Read2(&subsample.clear_bytes) && + reader->Read4(&subsample.cipher_bytes)); + } + return true; +} + +uint32_t SampleEncryptionEntry::ComputeSize() const { + const uint32_t subsample_entry_size = sizeof(uint16_t) + sizeof(uint32_t); + const uint16_t subsample_count = subsamples.size(); + return initialization_vector.size() + + (subsample_count > 0 ? (sizeof(subsample_count) + + subsample_entry_size * subsample_count) + : 0); +} + +uint32_t SampleEncryptionEntry::GetTotalSizeOfSubsamples() const { + uint32_t size = 0; + for (uint32_t i = 0; i < subsamples.size(); ++i) + size += subsamples[i].clear_bytes + subsamples[i].cipher_bytes; + return size; +} + +SampleEncryption::SampleEncryption() : iv_size(0) {} +SampleEncryption::~SampleEncryption() {} +FourCC SampleEncryption::BoxType() const { return FOURCC_SENC; } + +bool SampleEncryption::ReadWriteInternal(BoxBuffer* buffer) { + RCHECK(ReadWriteHeaderInternal(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) { + RCHECK(buffer->ReadWriteVector(&sample_encryption_data, + buffer->Size() - buffer->Pos())); + return true; + } + + if (!IsIvSizeValid(iv_size)) { + LOG(ERROR) << "IV_size can only be 8 or 16, but seeing " << iv_size; + return false; + } + + uint32_t sample_count = sample_encryption_entries.size(); + RCHECK(buffer->ReadWriteUInt32(&sample_count)); + + sample_encryption_entries.resize(sample_count); + for (auto& sample_encryption_entry : sample_encryption_entries) { + RCHECK(sample_encryption_entry.ReadWrite( + iv_size, flags & kUseSubsampleEncryption, buffer)); + } + return true; +} + +uint32_t SampleEncryption::ComputeSizeInternal() { + const uint32_t sample_count = sample_encryption_entries.size(); + if (sample_count == 0) { + // Sample encryption box is optional. Skip it if it is empty. + return 0; + } + + DCHECK(IsIvSizeValid(iv_size)); + uint32_t box_size = HeaderSize() + sizeof(sample_count); + if (flags & kUseSubsampleEncryption) { + for (const SampleEncryptionEntry& sample_encryption_entry : + sample_encryption_entries) { + box_size += sample_encryption_entry.ComputeSize(); + } + } else { + box_size += sample_count * iv_size; + } + return box_size; +} + +bool SampleEncryption::ParseFromSampleEncryptionData( + size_t iv_size, + std::vector* sample_encryption_entries) const { + DCHECK(IsIvSizeValid(iv_size)); + + BufferReader reader(vector_as_array(&sample_encryption_data), + sample_encryption_data.size()); + uint32_t sample_count = 0; + RCHECK(reader.Read4(&sample_count)); + + sample_encryption_entries->resize(sample_count); + for (auto& sample_encryption_entry : *sample_encryption_entries) { + RCHECK(sample_encryption_entry.ParseFromBuffer( + iv_size, flags & kUseSubsampleEncryption, &reader)); + } + return true; +} + OriginalFormat::OriginalFormat() : format(FOURCC_NULL) {} OriginalFormat::~OriginalFormat() {} FourCC OriginalFormat::BoxType() const { return FOURCC_FRMA; } @@ -1440,7 +1583,8 @@ bool Track::ReadWriteInternal(BoxBuffer* buffer) { buffer->PrepareChildren() && buffer->ReadWriteChild(&header) && buffer->ReadWriteChild(&media) && - buffer->TryReadWriteChild(&edit)); + buffer->TryReadWriteChild(&edit) && + buffer->TryReadWriteChild(&sample_encryption)); return true; } @@ -1921,14 +2065,16 @@ bool TrackFragment::ReadWriteInternal(BoxBuffer* buffer) { buffer->TryReadWriteChild(&sample_group_description)); } return buffer->TryReadWriteChild(&auxiliary_size) && - buffer->TryReadWriteChild(&auxiliary_offset); + buffer->TryReadWriteChild(&auxiliary_offset) && + buffer->TryReadWriteChild(&sample_encryption); } uint32_t TrackFragment::ComputeSizeInternal() { uint32_t box_size = HeaderSize() + header.ComputeSize() + decode_time.ComputeSize() + sample_to_group.ComputeSize() + sample_group_description.ComputeSize() + - auxiliary_size.ComputeSize() + auxiliary_offset.ComputeSize(); + auxiliary_size.ComputeSize() + auxiliary_offset.ComputeSize() + + sample_encryption.ComputeSize(); for (uint32_t i = 0; i < runs.size(); ++i) box_size += runs[i].ComputeSize(); return box_size; diff --git a/packager/media/formats/mp4/box_definitions.h b/packager/media/formats/mp4/box_definitions.h index cb4f58be24..3e9f7ae86f 100644 --- a/packager/media/formats/mp4/box_definitions.h +++ b/packager/media/formats/mp4/box_definitions.h @@ -7,6 +7,7 @@ #include +#include "packager/media/base/decrypt_config.h" #include "packager/media/formats/mp4/aac_audio_specific_config.h" #include "packager/media/formats/mp4/box.h" #include "packager/media/formats/mp4/es_descriptor.h" @@ -75,6 +76,60 @@ struct SampleAuxiliaryInformationSize : FullBox { std::vector sample_info_sizes; }; +struct SampleEncryptionEntry { + SampleEncryptionEntry(); + ~SampleEncryptionEntry(); + /// Read/Write SampleEncryptionEntry. + /// @param iv_size specifies the size of initialization vector. + /// @param has_subsamples indicates whether this sample encryption entry + /// constains subsamples. + /// @param buffer points to the box buffer for reading or writing. + /// @return true on success, false otherwise. + bool ReadWrite(uint8_t iv_size, + bool has_subsamples, + BoxBuffer* buffer); + /// Parse SampleEncryptionEntry from buffer. + /// @param iv_size specifies the size of initialization vector. + /// @param has_subsamples indicates whether this sample encryption entry + /// constains subsamples. + /// @param reader points to the buffer reader. Cannot be NULL. + /// @return true on success, false otherwise. + bool ParseFromBuffer(uint8_t iv_size, + bool has_subsamples, + BufferReader* reader); + /// @return The size of the structure in bytes when it is stored. + uint32_t ComputeSize() const; + /// @return The accumulated size of subsamples. Returns 0 if there is no + /// subsamples. + uint32_t GetTotalSizeOfSubsamples() const; + + std::vector initialization_vector; + std::vector subsamples; +}; + +struct SampleEncryption : FullBox { + enum SampleEncryptionFlags { + kUseSubsampleEncryption = 2, + }; + + DECLARE_BOX_METHODS(SampleEncryption); + /// Parse from @a sample_encryption_data. + /// @param iv_size specifies the size of initialization vector. + /// @param[out] sample_encryption_entries receives parsed sample encryption + /// entries. + /// @return true on success, false otherwise. + bool ParseFromSampleEncryptionData( + size_t iv_size, + std::vector* sample_encryption_entries) const; + + /// We may not know @a iv_size before reading this box. In this case, we will + /// store sample encryption data for parsing later when @a iv_size is known. + std::vector sample_encryption_data; + + size_t iv_size; + std::vector sample_encryption_entries; +}; + struct OriginalFormat : Box { DECLARE_BOX_METHODS(OriginalFormat); @@ -258,6 +313,8 @@ struct SampleDescription : FullBox { DECLARE_BOX_METHODS(SampleDescription); TrackType type; + // TODO(kqyang): Clean up the code to have one single member, e.g. by creating + // SampleEntry struct, std::vector sample_entries. std::vector video_entries; std::vector audio_entries; std::vector wvtt_entries; @@ -428,6 +485,7 @@ struct Track : Box { TrackHeader header; Media media; Edit edit; + SampleEncryption sample_encryption; }; struct MovieExtendsHeader : FullBox { @@ -569,6 +627,7 @@ struct TrackFragment : Box { SampleGroupDescription sample_group_description; SampleAuxiliaryInformationSize auxiliary_size; SampleAuxiliaryInformationOffset auxiliary_offset; + SampleEncryption sample_encryption; }; struct MovieFragment : Box { diff --git a/packager/media/formats/mp4/box_definitions_comparison.h b/packager/media/formats/mp4/box_definitions_comparison.h index 4949b38061..0ea1acddb0 100644 --- a/packager/media/formats/mp4/box_definitions_comparison.h +++ b/packager/media/formats/mp4/box_definitions_comparison.h @@ -13,6 +13,12 @@ namespace edash_packager { namespace media { + +inline bool operator==(const SubsampleEntry& lhs, const SubsampleEntry& rhs) { + return lhs.clear_bytes == rhs.clear_bytes && + lhs.cipher_bytes == rhs.cipher_bytes; +} + namespace mp4 { inline bool operator==(const FileType& lhs, const FileType& rhs) { @@ -38,6 +44,18 @@ inline bool operator==(const SampleAuxiliaryInformationSize& lhs, lhs.sample_info_sizes == rhs.sample_info_sizes; } +inline bool operator==(const SampleEncryptionEntry& lhs, + const SampleEncryptionEntry& rhs) { + return lhs.initialization_vector == rhs.initialization_vector && + lhs.subsamples == rhs.subsamples; +} + +inline bool operator==(const SampleEncryption& lhs, + const SampleEncryption& rhs) { + return lhs.iv_size == rhs.iv_size && + lhs.sample_encryption_entries == rhs.sample_encryption_entries; +} + inline bool operator==(const OriginalFormat& lhs, const OriginalFormat& rhs) { return lhs.format == rhs.format; } @@ -274,7 +292,7 @@ inline bool operator==(const Media& lhs, const Media& rhs) { inline bool operator==(const Track& lhs, const Track& rhs) { return lhs.header == rhs.header && lhs.media == rhs.media && - lhs.edit == rhs.edit; + lhs.edit == rhs.edit && lhs.sample_encryption == rhs.sample_encryption; } inline bool operator==(const MovieExtendsHeader& lhs, @@ -360,7 +378,8 @@ inline bool operator==(const TrackFragment& lhs, const TrackFragment& rhs) { return lhs.header == rhs.header && lhs.runs == rhs.runs && lhs.decode_time == rhs.decode_time && lhs.auxiliary_offset == rhs.auxiliary_offset && - lhs.auxiliary_size == rhs.auxiliary_size; + lhs.auxiliary_size == rhs.auxiliary_size && + lhs.sample_encryption == rhs.sample_encryption; } inline bool operator==(const MovieFragment& lhs, const MovieFragment& rhs) { diff --git a/packager/media/formats/mp4/box_definitions_unittest.cc b/packager/media/formats/mp4/box_definitions_unittest.cc index dc0fdc036a..4a196ba4fb 100644 --- a/packager/media/formats/mp4/box_definitions_unittest.cc +++ b/packager/media/formats/mp4/box_definitions_unittest.cc @@ -18,6 +18,7 @@ namespace edash_packager { namespace media { namespace mp4 { namespace { +const uint8_t kData8Bytes[] = {3, 4, 5, 6, 7, 8, 9, 0}; const uint8_t kData16Bytes[] = {8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8}; const uint8_t kData4[] = {1, 5, 4, 3, 15}; const uint8_t kData8[] = {1, 8, 42, 98, 156}; @@ -161,6 +162,30 @@ class BoxDefinitionsTestGeneral : public testing::Test { saiz->sample_info_sizes.clear(); } + void Fill(SampleEncryption* senc) { + senc->iv_size = 8; + senc->flags = SampleEncryption::kUseSubsampleEncryption; + senc->sample_encryption_entries.resize(2); + senc->sample_encryption_entries[0].initialization_vector.assign( + kData8Bytes, kData8Bytes + arraysize(kData8Bytes)); + senc->sample_encryption_entries[0].subsamples.resize(2); + senc->sample_encryption_entries[0].subsamples[0].clear_bytes = 17; + senc->sample_encryption_entries[0].subsamples[0].cipher_bytes = 3456; + senc->sample_encryption_entries[0].subsamples[1].clear_bytes = 1543; + senc->sample_encryption_entries[0].subsamples[1].cipher_bytes = 0; + senc->sample_encryption_entries[1] = senc->sample_encryption_entries[0]; + senc->sample_encryption_entries[1].subsamples[0].clear_bytes = 0; + senc->sample_encryption_entries[1].subsamples[0].cipher_bytes = 15; + senc->sample_encryption_entries[1].subsamples[1].clear_bytes = 1988; + senc->sample_encryption_entries[1].subsamples[1].cipher_bytes = 8765; + } + + void Modify(SampleEncryption* senc) { + senc->flags = 0; + senc->sample_encryption_entries.resize(1); + senc->sample_encryption_entries[0].subsamples.clear(); + } + void Fill(OriginalFormat* frma) { frma->format = FOURCC_AVC1; } void Modify(OriginalFormat* frma) { frma->format = FOURCC_MP4A; } @@ -817,6 +842,7 @@ class BoxDefinitionsTestGeneral : public testing::Test { bool IsOptional(const SampleAuxiliaryInformationOffset* box) { return true; } bool IsOptional(const SampleAuxiliaryInformationSize* box) { return true; } + bool IsOptional(const SampleEncryption* box) { return true; } bool IsOptional(const ProtectionSchemeInfo* box) { return true; } bool IsOptional(const EditList* box) { return true; } bool IsOptional(const Edit* box) { return true; } @@ -1066,6 +1092,48 @@ TEST_F(BoxDefinitionsTest, TrackFragmentRun_NoSampleSize) { ASSERT_EQ(trun, trun_readback); } +TEST_F(BoxDefinitionsTest, SampleEncryptionIsOptional) { + SampleEncryption senc; + EXPECT_EQ(0u, senc.ComputeSize()); +} + +TEST_F(BoxDefinitionsTest, SampleEncryptionWithIvKnownWhenReading) { + SampleEncryption senc; + Fill(&senc); + senc.Write(buffer_.get()); + + SampleEncryption senc_readback; + senc_readback.iv_size = senc.iv_size; + + ASSERT_TRUE(ReadBack(&senc_readback)); + EXPECT_EQ(0u, senc_readback.sample_encryption_data.size()); + EXPECT_NE(0u, senc_readback.sample_encryption_entries.size()); + ASSERT_EQ(senc, senc_readback); + + Modify(&senc); + senc.Write(buffer_.get()); + ASSERT_TRUE(ReadBack(&senc_readback)); + ASSERT_EQ(senc, senc_readback); +} + +TEST_F(BoxDefinitionsTest, SampleEncryptionWithIvUnknownWhenReading) { + SampleEncryption senc; + Fill(&senc); + senc.Write(buffer_.get()); + + SampleEncryption senc_readback; + senc_readback.iv_size = 0; + + ASSERT_TRUE(ReadBack(&senc_readback)); + EXPECT_NE(0u, senc_readback.sample_encryption_data.size()); + EXPECT_EQ(0u, senc_readback.sample_encryption_entries.size()); + + std::vector sample_encryption_entries; + ASSERT_TRUE(senc_readback.ParseFromSampleEncryptionData( + senc.iv_size, &sample_encryption_entries)); + ASSERT_EQ(senc.sample_encryption_entries, sample_encryption_entries); +} + } // namespace mp4 } // namespace media } // namespace edash_packager diff --git a/packager/media/formats/mp4/fourccs.h b/packager/media/formats/mp4/fourccs.h index 8467003419..02a1c9285c 100644 --- a/packager/media/formats/mp4/fourccs.h +++ b/packager/media/formats/mp4/fourccs.h @@ -73,6 +73,7 @@ enum FourCC { FOURCC_SCHM = 0x7363686d, FOURCC_SDTP = 0x73647470, FOURCC_SEIG = 0x73656967, + FOURCC_SENC = 0x73656e63, FOURCC_SGPD = 0x73677064, FOURCC_SIDX = 0x73696478, FOURCC_SINF = 0x73696e66, diff --git a/packager/media/formats/mp4/mp4_media_parser.h b/packager/media/formats/mp4/mp4_media_parser.h index 08f5a59787..59824e73df 100644 --- a/packager/media/formats/mp4/mp4_media_parser.h +++ b/packager/media/formats/mp4/mp4_media_parser.h @@ -74,8 +74,8 @@ class MP4MediaParser : public MediaParser { uint8_t* buffer, size_t buffer_size); - // To retain proper framing, each 'mdat' atom must be read; to limit memory - // usage, the atom's data needs to be discarded incrementally as frames are + // To retain proper framing, each 'mdat' box must be read; to limit memory + // usage, the box's data needs to be discarded incrementally as frames are // extracted from the stream. This function discards data from the stream up // to |offset|, updating the |mdat_tail_| value so that framing can be // retained after all 'mdat' information has been read. diff --git a/packager/media/formats/mp4/mp4_muxer.h b/packager/media/formats/mp4/mp4_muxer.h index 5e45848274..f9ce6e4895 100644 --- a/packager/media/formats/mp4/mp4_muxer.h +++ b/packager/media/formats/mp4/mp4_muxer.h @@ -42,7 +42,7 @@ class MP4Muxer : public Muxer { Status DoAddSample(const MediaStream* stream, scoped_refptr sample) override; - // Generate Audio/Video Track atom. + // Generate Audio/Video Track box. void InitializeTrak(const StreamInfo* info, Track* trak); void GenerateAudioTrak(const AudioStreamInfo* audio_info, Track* trak,