Add 'sbgp' and 'sgpd' box definitions

sbgp: Sample to Group box
sgpd: Sample Group Descripton box

They are required for DASH Live support.

Change-Id: I2f3b55843c5148db60c7c3a6891ba825b59934ce
This commit is contained in:
Kongqun Yang 2014-03-24 14:09:58 -07:00 committed by KongQun Yang
parent beac7bae62
commit 2c8418fd22
5 changed files with 288 additions and 18 deletions

View File

@ -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<SampleToGroupEntry>::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();

View File

@ -492,14 +492,50 @@ struct TrackFragmentRun : FullBox {
std::vector<int32> 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<SampleToGroupEntry> entries;
};
struct CencSampleEncryptionInfoEntry {
CencSampleEncryptionInfoEntry();
~CencSampleEncryptionInfoEntry();
bool is_encrypted;
uint8 iv_size;
std::vector<uint8> key_id;
};
struct SampleGroupDescription : FullBox {
DECLARE_BOX_METHODS(SampleGroupDescription);
uint32 grouping_type;
std::vector<CencSampleEncryptionInfoEntry> entries;
};
struct TrackFragment : Box {
DECLARE_BOX_METHODS(TrackFragment);
TrackFragmentHeader header;
std::vector<TrackFragmentRun> runs;
TrackFragmentDecodeTime decode_time;
SampleAuxiliaryInformationOffset auxiliary_offset;
SampleToGroup sample_to_group;
SampleGroupDescription sample_group_description;
SampleAuxiliaryInformationSize auxiliary_size;
SampleAuxiliaryInformationOffset auxiliary_offset;
};
struct MovieFragment : Box {

View File

@ -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 &&

View File

@ -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<BufferWriter> 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<Box> {};

View File

@ -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,