diff --git a/media/mp4/box_definitions.cc b/media/mp4/box_definitions.cc index d69d6ae9fa..460fe9f49b 100644 --- a/media/mp4/box_definitions.cc +++ b/media/mp4/box_definitions.cc @@ -1595,6 +1595,121 @@ uint32 TrackFragmentRun::ComputeSize() { return atom_size; } +SampleToGroup::SampleToGroup() : grouping_type(0), grouping_type_parameter(0) {} +SampleToGroup::~SampleToGroup() {} +FourCC SampleToGroup::BoxType() const { return FOURCC_SBGP; } + +bool SampleToGroup::ReadWrite(BoxBuffer* buffer) { + RCHECK(FullBox::ReadWrite(buffer) && + buffer->ReadWriteUInt32(&grouping_type)); + if (version == 1) + RCHECK(buffer->ReadWriteUInt32(&grouping_type_parameter)); + + if (grouping_type != FOURCC_SEIG) { + DCHECK(buffer->Reading()); + DLOG(WARNING) << "Sample group '" << grouping_type << "' is not supported."; + return true; + } + + uint32 count = entries.size(); + RCHECK(buffer->ReadWriteUInt32(&count)); + entries.resize(count); + for (uint32 i = 0; i < count; ++i) { + RCHECK(buffer->ReadWriteUInt32(&entries[i].sample_count) && + buffer->ReadWriteUInt32(&entries[i].group_description_index)); + } + return true; +} + +uint32 SampleToGroup::ComputeSize() { + // This box is optional. Skip it if it is not used. + atom_size = 0; + if (!entries.empty()) { + atom_size = kFullBoxSize + sizeof(grouping_type) + + (version == 1 ? sizeof(grouping_type_parameter) : 0) + + sizeof(uint32) + entries.size() * sizeof(entries[0]); + } + return atom_size; +} + +CencSampleEncryptionInfoEntry::CencSampleEncryptionInfoEntry() + : is_encrypted(false), iv_size(0) { +} +CencSampleEncryptionInfoEntry::~CencSampleEncryptionInfoEntry() {}; + +SampleGroupDescription::SampleGroupDescription() : grouping_type(0) {} +SampleGroupDescription::~SampleGroupDescription() {} +FourCC SampleGroupDescription::BoxType() const { return FOURCC_SGPD; } + +bool SampleGroupDescription::ReadWrite(BoxBuffer* buffer) { + RCHECK(FullBox::ReadWrite(buffer) && + buffer->ReadWriteUInt32(&grouping_type)); + + if (grouping_type != FOURCC_SEIG) { + DCHECK(buffer->Reading()); + DLOG(WARNING) << "Sample group '" << grouping_type << "' is not supported."; + return true; + } + + const size_t kKeyIdSize = 16; + const size_t kEntrySize = sizeof(uint32) + kKeyIdSize; + uint32 default_length = 0; + if (version == 1) { + if (buffer->Reading()) { + RCHECK(buffer->ReadWriteUInt32(&default_length)); + RCHECK(default_length == 0 || default_length == kEntrySize); + } else { + default_length = kEntrySize; + RCHECK(buffer->ReadWriteUInt32(&default_length)); + } + } + + uint32 count = entries.size(); + RCHECK(buffer->ReadWriteUInt32(&count)); + entries.resize(count); + for (uint32 i = 0; i < count; ++i) { + if (version == 1) { + if (buffer->Reading() && default_length == 0) { + uint32 description_length = 0; + RCHECK(buffer->ReadWriteUInt32(&description_length)); + RCHECK(description_length == kEntrySize); + } + } + + if (!buffer->Reading()) + RCHECK(entries[i].key_id.size() == kKeyIdSize); + + uint8 flag = entries[i].is_encrypted ? 1 : 0; + RCHECK(buffer->IgnoreBytes(2) && // reserved. + buffer->ReadWriteUInt8(&flag) && + buffer->ReadWriteUInt8(&entries[i].iv_size) && + buffer->ReadWriteVector(&entries[i].key_id, kKeyIdSize)); + + 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); + } else { + RCHECK(entries[i].iv_size == 0); + } + } + } + return true; +} + +uint32 SampleGroupDescription::ComputeSize() { + // This box is optional. Skip it if it is not used. + atom_size = 0; + if (!entries.empty()) { + const size_t kKeyIdSize = 16; + const size_t kEntrySize = sizeof(uint32) + kKeyIdSize; + atom_size = kFullBoxSize + sizeof(grouping_type) + + (version == 1 ? sizeof(uint32) : 0) + + sizeof(uint32) + entries.size() * kEntrySize; + } + return atom_size; +} + TrackFragment::TrackFragment() {} TrackFragment::~TrackFragment() {} FourCC TrackFragment::BoxType() const { return FOURCC_TRAF; } @@ -1608,9 +1723,46 @@ bool TrackFragment::ReadWrite(BoxBuffer* buffer) { if (buffer->Reading()) { DCHECK(buffer->reader()); RCHECK(buffer->reader()->TryReadChildren(&runs)); + + while (sample_to_group.grouping_type != FOURCC_SEIG && + buffer->reader()->ChildExist(&sample_to_group)) { + RCHECK(buffer->reader()->ReadChild(&sample_to_group)); + } + while (sample_group_description.grouping_type != FOURCC_SEIG && + buffer->reader()->ChildExist(&sample_group_description)) { + RCHECK(buffer->reader()->ReadChild(&sample_group_description)); + } + if (sample_to_group.grouping_type == FOURCC_SEIG) { + // SampleGroupDescription box can appear in either 'moov...stbl' or + // 'moov.traf'. The first case is not supported for now, so we require + // a companion SampleGroupDescription box to coexist with the + // SampleToGroup box. + if (sample_group_description.grouping_type != FOURCC_SEIG) { + NOTIMPLEMENTED() + << "SampleGroupDescription box in 'moov' is not supported."; + return false; + } + for (std::vector::iterator it = + sample_to_group.entries.begin(); + it != sample_to_group.entries.end(); + ++it) { + if ((it->group_description_index & 0x10000) == 0) { + NOTIMPLEMENTED() + << "SampleGroupDescription box in 'moov' is not supported."; + return false; + } + it->group_description_index &= 0x0FFFF; + RCHECK(it->group_description_index <= + sample_group_description.entries.size()); + } + } else { + RCHECK(sample_group_description.grouping_type != FOURCC_SEIG); + } } else { for (uint32 i = 0; i < runs.size(); ++i) RCHECK(runs[i].ReadWrite(buffer)); + RCHECK(buffer->TryReadWriteChild(&sample_to_group) && + buffer->TryReadWriteChild(&sample_group_description)); } return buffer->TryReadWriteChild(&auxiliary_size) && buffer->TryReadWriteChild(&auxiliary_offset); @@ -1618,6 +1770,8 @@ bool TrackFragment::ReadWrite(BoxBuffer* buffer) { uint32 TrackFragment::ComputeSize() { atom_size = kBoxSize + header.ComputeSize() + decode_time.ComputeSize() + + sample_to_group.ComputeSize() + + sample_group_description.ComputeSize() + auxiliary_size.ComputeSize() + auxiliary_offset.ComputeSize(); for (uint32 i = 0; i < runs.size(); ++i) atom_size += runs[i].ComputeSize(); diff --git a/media/mp4/box_definitions.h b/media/mp4/box_definitions.h index a56388e2cb..e11baea633 100644 --- a/media/mp4/box_definitions.h +++ b/media/mp4/box_definitions.h @@ -492,14 +492,50 @@ struct TrackFragmentRun : FullBox { std::vector sample_composition_time_offsets; }; +struct SampleToGroupEntry { + enum GroupDescriptionIndexBase { + kTrackGroupDescriptionIndexBase = 0, + kTrackFragmentGroupDescriptionIndexBase = 0x10000, + }; + + uint32 sample_count; + uint32 group_description_index; +}; + +struct SampleToGroup : FullBox { + DECLARE_BOX_METHODS(SampleToGroup); + + uint32 grouping_type; + uint32 grouping_type_parameter; // Version 1 only. + std::vector entries; +}; + +struct CencSampleEncryptionInfoEntry { + CencSampleEncryptionInfoEntry(); + ~CencSampleEncryptionInfoEntry(); + + bool is_encrypted; + uint8 iv_size; + std::vector key_id; +}; + +struct SampleGroupDescription : FullBox { + DECLARE_BOX_METHODS(SampleGroupDescription); + + uint32 grouping_type; + std::vector entries; +}; + struct TrackFragment : Box { DECLARE_BOX_METHODS(TrackFragment); TrackFragmentHeader header; std::vector runs; TrackFragmentDecodeTime decode_time; - SampleAuxiliaryInformationOffset auxiliary_offset; + SampleToGroup sample_to_group; + SampleGroupDescription sample_group_description; SampleAuxiliaryInformationSize auxiliary_size; + SampleAuxiliaryInformationOffset auxiliary_offset; }; struct MovieFragment : Box { diff --git a/media/mp4/box_definitions_comparison.h b/media/mp4/box_definitions_comparison.h index 5441ae3718..df71db91d1 100644 --- a/media/mp4/box_definitions_comparison.h +++ b/media/mp4/box_definitions_comparison.h @@ -303,6 +303,32 @@ inline bool operator==(const TrackFragmentRun& lhs, rhs.sample_composition_time_offsets; } +inline bool operator==(const SampleToGroupEntry& lhs, + const SampleToGroupEntry& rhs) { + return lhs.sample_count == rhs.sample_count && + lhs.group_description_index == rhs.group_description_index; +} + +inline bool operator==(const SampleToGroup& lhs, + const SampleToGroup& rhs) { + return lhs.grouping_type == rhs.grouping_type && + lhs.grouping_type_parameter == rhs.grouping_type_parameter && + lhs.entries == rhs.entries; +} + +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; +} + +inline bool operator==(const SampleGroupDescription& lhs, + const SampleGroupDescription& rhs) { + return lhs.grouping_type == rhs.grouping_type && + lhs.entries == rhs.entries; +} + inline bool operator==(const TrackFragment& lhs, const TrackFragment& rhs) { return lhs.header == rhs.header && lhs.runs == rhs.runs && lhs.decode_time == rhs.decode_time && diff --git a/media/mp4/box_definitions_unittest.cc b/media/mp4/box_definitions_unittest.cc index cf29e02d62..ddf6d0002b 100644 --- a/media/mp4/box_definitions_unittest.cc +++ b/media/mp4/box_definitions_unittest.cc @@ -103,7 +103,7 @@ class BoxDefinitionsTestGeneral : public testing::Test { void Modify(Box* box) {} // Is this box optional? - bool IsOptional(Box* box) { return false; } + bool IsOptional(const Box* box) { return false; } // Non-full box does not have version field. uint8 GetAndClearVersion(Box* box) { return 0; } @@ -625,6 +625,37 @@ class BoxDefinitionsTestGeneral : public testing::Test { trun->version = 1; } + void Fill(SampleToGroup* sbgp) { + sbgp->grouping_type = FOURCC_SEIG; + sbgp->entries.resize(2); + sbgp->entries[0].sample_count = 3; + sbgp->entries[0].group_description_index = 0x10002; + sbgp->entries[0].sample_count = 1212; + sbgp->entries[0].group_description_index = 0x10001; + } + + void Modify(SampleToGroup* sbgp) { + sbgp->entries.resize(1); + sbgp->entries[0].group_description_index = 0x10001; + } + + 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[0].key_id.assign(kData16Bytes, + kData16Bytes + arraysize(kData16Bytes)); + sgpd->entries[1].is_encrypted = false; + sgpd->entries[1].iv_size = 0; + sgpd->entries[1].key_id.resize(16); + } + + void Modify(SampleGroupDescription* sbgp) { + sbgp->entries.resize(1); + sbgp->entries[0].key_id[4] = 88; + } + void Fill(TrackFragment* traf) { Fill(&traf->header); traf->runs.resize(1); @@ -680,18 +711,20 @@ class BoxDefinitionsTestGeneral : public testing::Test { sidx->version = 1; } - bool IsOptional(SampleAuxiliaryInformationOffset* box) { return true; } - bool IsOptional(SampleAuxiliaryInformationSize* box) { return true; } - bool IsOptional(ProtectionSchemeInfo* box) { return true; } - bool IsOptional(EditList* box) { return true; } - bool IsOptional(Edit* box) { return true; } - bool IsOptional(AVCDecoderConfigurationRecord* box) { return true; } - bool IsOptional(PixelAspectRatioBox* box) { return true; } - bool IsOptional(ElementaryStreamDescriptor* box) { return true; } - bool IsOptional(CompositionTimeToSample* box) { return true; } - bool IsOptional(SyncSample* box) { return true; } - bool IsOptional(MovieExtendsHeader* box) { return true; } - bool IsOptional(MovieExtends* box) { return true; } + bool IsOptional(const SampleAuxiliaryInformationOffset* box) { return true; } + bool IsOptional(const SampleAuxiliaryInformationSize* box) { return true; } + bool IsOptional(const ProtectionSchemeInfo* box) { return true; } + bool IsOptional(const EditList* box) { return true; } + bool IsOptional(const Edit* box) { return true; } + bool IsOptional(const AVCDecoderConfigurationRecord* box) { return true; } + bool IsOptional(const PixelAspectRatioBox* box) { return true; } + bool IsOptional(const ElementaryStreamDescriptor* box) { return true; } + bool IsOptional(const CompositionTimeToSample* box) { return true; } + bool IsOptional(const SyncSample* box) { return true; } + bool IsOptional(const MovieExtendsHeader* box) { return true; } + bool IsOptional(const MovieExtends* box) { return true; } + bool IsOptional(const SampleToGroup* box) { return true; } + bool IsOptional(const SampleGroupDescription* box) { return true; } protected: scoped_ptr buffer_; @@ -749,9 +782,15 @@ typedef testing::Types< MovieFragment, SegmentIndex> Boxes; -TYPED_TEST_CASE(BoxDefinitionsTestGeneral, Boxes); +// GTEST support a maximum of 50 types in the template list, so we have to +// break it into two groups. +typedef testing::Types< + SampleToGroup, + SampleGroupDescription> Boxes2; -TYPED_TEST(BoxDefinitionsTestGeneral, WriteReadbackCompare) { +TYPED_TEST_CASE_P(BoxDefinitionsTestGeneral); + +TYPED_TEST_P(BoxDefinitionsTestGeneral, WriteReadbackCompare) { TypeParam box; LOG(INFO) << "Processing " << FourCCToString(box.BoxType()); this->Fill(&box); @@ -762,7 +801,7 @@ TYPED_TEST(BoxDefinitionsTestGeneral, WriteReadbackCompare) { ASSERT_EQ(box, box_readback); } -TYPED_TEST(BoxDefinitionsTestGeneral, WriteModifyWrite) { +TYPED_TEST_P(BoxDefinitionsTestGeneral, WriteModifyWrite) { TypeParam box; LOG(INFO) << "Processing " << FourCCToString(box.BoxType()); this->Fill(&box); @@ -783,7 +822,7 @@ TYPED_TEST(BoxDefinitionsTestGeneral, WriteModifyWrite) { ASSERT_EQ(box, box_readback); } -TYPED_TEST(BoxDefinitionsTestGeneral, Empty) { +TYPED_TEST_P(BoxDefinitionsTestGeneral, Empty) { TypeParam box; LOG(INFO) << "Processing " << FourCCToString(box.BoxType()); if (this->IsOptional(&box)) { @@ -793,6 +832,18 @@ TYPED_TEST(BoxDefinitionsTestGeneral, Empty) { } } +REGISTER_TYPED_TEST_CASE_P(BoxDefinitionsTestGeneral, + WriteReadbackCompare, + WriteModifyWrite, + Empty); + +INSTANTIATE_TYPED_TEST_CASE_P(BoxDefinitionTypedTests, + BoxDefinitionsTestGeneral, + Boxes); +INSTANTIATE_TYPED_TEST_CASE_P(BoxDefinitionTypedTests2, + BoxDefinitionsTestGeneral, + Boxes2); + // Test other cases of box input. class BoxDefinitionsTest : public BoxDefinitionsTestGeneral {}; diff --git a/media/mp4/fourccs.h b/media/mp4/fourccs.h index 9c3e2bd664..ee86dd7a3b 100644 --- a/media/mp4/fourccs.h +++ b/media/mp4/fourccs.h @@ -56,9 +56,12 @@ enum FourCC { FOURCC_PSSH = 0x70737368, FOURCC_SAIO = 0x7361696f, FOURCC_SAIZ = 0x7361697a, + FOURCC_SBGP = 0x73626770, FOURCC_SCHI = 0x73636869, FOURCC_SCHM = 0x7363686d, FOURCC_SDTP = 0x73647470, + FOURCC_SEIG = 0x73656967, + FOURCC_SGPD = 0x73677064, FOURCC_SIDX = 0x73696478, FOURCC_SINF = 0x73696e66, FOURCC_SKIP = 0x736b6970,