Add support for AudioRollRecoveryEntry

This is part of the effort to support Opus in iso-bmff #83.

Changes in this CL:
- Add support for multiple SampleGroupDescription and SampleToGroup
  boxes in TrackFragment box;
- Add support for SampleGroupDescription and SampleToGroup boxes
  in SampleTable box;
- Add support for AudioRollRecoveryEntry in Sample Group and 'roll'
  sample grouping type;
- Also fix a bug that default length was not set correctly in
  SampleGroupDescription when constant iv is used.

Change-Id: I7e31386ea6cd17a8ee5e1dca4c1a90937d51368f
This commit is contained in:
Kongqun Yang 2016-05-06 16:56:50 -07:00 committed by KongQun Yang
parent cf4a2447c1
commit 940cc43de2
14 changed files with 499 additions and 313 deletions

View File

@ -82,6 +82,7 @@ enum FourCC : uint32_t {
FOURCC_pdin = 0x7064696e,
FOURCC_prft = 0x70726674,
FOURCC_pssh = 0x70737368,
FOURCC_roll = 0x726f6c6c,
FOURCC_saio = 0x7361696f,
FOURCC_saiz = 0x7361697a,
FOURCC_sbgp = 0x73626770,

View File

@ -921,6 +921,187 @@ uint32_t SyncSample::ComputeSizeInternal() {
sizeof(uint32_t) * sample_number.size();
}
CencSampleEncryptionInfoEntry::CencSampleEncryptionInfoEntry()
: is_protected(0),
per_sample_iv_size(0),
crypt_byte_block(0),
skip_byte_block(0) {}
CencSampleEncryptionInfoEntry::~CencSampleEncryptionInfoEntry() {};
bool CencSampleEncryptionInfoEntry::ReadWrite(BoxBuffer* buffer) {
if (!buffer->Reading()) {
if (key_id.size() != kCencKeyIdSize) {
LOG(WARNING) << "CENC defines key id length of " << kCencKeyIdSize
<< " bytes; got " << key_id.size()
<< ". Resized accordingly.";
key_id.resize(kCencKeyIdSize);
}
RCHECK(crypt_byte_block < 16 && skip_byte_block < 16);
}
RCHECK(buffer->IgnoreBytes(1)); // reserved.
uint8_t pattern = crypt_byte_block << 4 | skip_byte_block;
RCHECK(buffer->ReadWriteUInt8(&pattern));
crypt_byte_block = pattern >> 4;
skip_byte_block = pattern & 0x0F;
RCHECK(buffer->ReadWriteUInt8(&is_protected) &&
buffer->ReadWriteUInt8(&per_sample_iv_size) &&
buffer->ReadWriteVector(&key_id, kCencKeyIdSize));
if (is_protected == 1) {
if (per_sample_iv_size == 0) { // For constant iv.
uint8_t constant_iv_size = constant_iv.size();
RCHECK(buffer->ReadWriteUInt8(&constant_iv_size));
RCHECK(constant_iv_size == 8 || constant_iv_size == 16);
RCHECK(buffer->ReadWriteVector(&constant_iv, constant_iv_size));
} else {
RCHECK(per_sample_iv_size == 8 || per_sample_iv_size == 16);
DCHECK(constant_iv.empty());
}
} else {
// Expect |is_protected| to be 0, i.e. not protected. Other values of
// |is_protected| is not supported.
RCHECK(is_protected == 0);
RCHECK(per_sample_iv_size == 0);
}
return true;
}
uint32_t CencSampleEncryptionInfoEntry::ComputeSize() const {
return sizeof(uint32_t) + kCencKeyIdSize +
(constant_iv.empty() ? 0 : (sizeof(uint8_t) + constant_iv.size()));
}
AudioRollRecoveryEntry::AudioRollRecoveryEntry(): roll_distance(0) {}
AudioRollRecoveryEntry::~AudioRollRecoveryEntry() {}
bool AudioRollRecoveryEntry::ReadWrite(BoxBuffer* buffer) {
RCHECK(buffer->ReadWriteInt16(&roll_distance));
return true;
}
uint32_t AudioRollRecoveryEntry::ComputeSize() const {
return sizeof(roll_distance);
}
SampleGroupDescription::SampleGroupDescription() : grouping_type(0) {}
SampleGroupDescription::~SampleGroupDescription() {}
FourCC SampleGroupDescription::BoxType() const { return FOURCC_sgpd; }
bool SampleGroupDescription::ReadWriteInternal(BoxBuffer* buffer) {
RCHECK(ReadWriteHeaderInternal(buffer) &&
buffer->ReadWriteUInt32(&grouping_type));
switch (grouping_type) {
case FOURCC_seig:
return ReadWriteEntries(buffer, &cenc_sample_encryption_info_entries);
case FOURCC_roll:
return ReadWriteEntries(buffer, &audio_roll_recovery_entries);
default:
DCHECK(buffer->Reading());
DLOG(WARNING) << "Sample group '" << grouping_type
<< "' is not supported.";
return true;
}
}
template <typename T>
bool SampleGroupDescription::ReadWriteEntries(BoxBuffer* buffer,
std::vector<T>* entries) {
uint32_t default_length = 0;
if (!buffer->Reading()) {
DCHECK(!entries->empty());
default_length = (*entries)[0].ComputeSize();
DCHECK_NE(default_length, 0u);
}
if (version == 1)
RCHECK(buffer->ReadWriteUInt32(&default_length));
if (version >= 2) {
NOTIMPLEMENTED() << "Unsupported SampleGroupDescriptionBox 'sgpd' version "
<< static_cast<int>(version);
return false;
}
uint32_t count = entries->size();
RCHECK(buffer->ReadWriteUInt32(&count));
RCHECK(count != 0);
entries->resize(count);
for (T& entry : *entries) {
if (version == 1) {
uint32_t description_length = default_length;
if (buffer->Reading() && default_length == 0)
RCHECK(buffer->ReadWriteUInt32(&description_length));
RCHECK(entry.ReadWrite(buffer));
RCHECK(entry.ComputeSize() == description_length);
} else {
RCHECK(entry.ReadWrite(buffer));
}
}
return true;
}
uint32_t SampleGroupDescription::ComputeSizeInternal() {
// Version 0 is obsoleted, so always generate version 1 box.
version = 1;
size_t entries_size = 0;
switch (grouping_type) {
case FOURCC_seig:
for (const auto& entry : cenc_sample_encryption_info_entries)
entries_size += entry.ComputeSize();
break;
case FOURCC_roll:
for (const auto& entry : audio_roll_recovery_entries)
entries_size += entry.ComputeSize();
break;
}
// This box is optional. Skip it if it is not used.
if (entries_size == 0)
return 0;
return HeaderSize() + sizeof(grouping_type) +
(version == 1 ? sizeof(uint32_t) : 0) + sizeof(uint32_t) +
entries_size;
}
SampleToGroup::SampleToGroup() : grouping_type(0), grouping_type_parameter(0) {}
SampleToGroup::~SampleToGroup() {}
FourCC SampleToGroup::BoxType() const { return FOURCC_sbgp; }
bool SampleToGroup::ReadWriteInternal(BoxBuffer* buffer) {
RCHECK(ReadWriteHeaderInternal(buffer) &&
buffer->ReadWriteUInt32(&grouping_type));
if (version == 1)
RCHECK(buffer->ReadWriteUInt32(&grouping_type_parameter));
if (grouping_type != FOURCC_seig && grouping_type != FOURCC_roll) {
DCHECK(buffer->Reading());
DLOG(WARNING) << "Sample group "
<< FourCCToString(static_cast<FourCC>(grouping_type))
<< " is not supported.";
return true;
}
uint32_t count = entries.size();
RCHECK(buffer->ReadWriteUInt32(&count));
entries.resize(count);
for (uint32_t i = 0; i < count; ++i) {
RCHECK(buffer->ReadWriteUInt32(&entries[i].sample_count) &&
buffer->ReadWriteUInt32(&entries[i].group_description_index));
}
return true;
}
uint32_t SampleToGroup::ComputeSizeInternal() {
// This box is optional. Skip it if it is not used.
if (entries.empty())
return 0;
return HeaderSize() + sizeof(grouping_type) +
(version == 1 ? sizeof(grouping_type_parameter) : 0) +
sizeof(uint32_t) + entries.size() * sizeof(entries[0]);
}
SampleTable::SampleTable() {}
SampleTable::~SampleTable() {}
FourCC SampleTable::BoxType() const { return FOURCC_stbl; }
@ -961,15 +1142,30 @@ bool SampleTable::ReadWriteInternal(BoxBuffer* buffer) {
buffer->ReadWriteChild(&chunk_large_offset));
}
RCHECK(buffer->TryReadWriteChild(&sync_sample));
if (buffer->Reading()) {
RCHECK(buffer->reader()->TryReadChildren(&sample_group_descriptions) &&
buffer->reader()->TryReadChildren(&sample_to_groups));
} else {
for (auto& sample_group_description : sample_group_descriptions)
RCHECK(buffer->ReadWriteChild(&sample_group_description));
for (auto& sample_to_group : sample_to_groups)
RCHECK(buffer->ReadWriteChild(&sample_to_group));
}
return true;
}
uint32_t SampleTable::ComputeSizeInternal() {
return HeaderSize() + description.ComputeSize() +
uint32_t box_size =
HeaderSize() + description.ComputeSize() +
decoding_time_to_sample.ComputeSize() +
composition_time_to_sample.ComputeSize() +
sample_to_chunk.ComputeSize() + sample_size.ComputeSize() +
chunk_large_offset.ComputeSize() + sync_sample.ComputeSize();
composition_time_to_sample.ComputeSize() + sample_to_chunk.ComputeSize() +
sample_size.ComputeSize() + chunk_large_offset.ComputeSize() +
sync_sample.ComputeSize();
for (auto& sample_group_description : sample_group_descriptions)
box_size += sample_group_description.ComputeSize();
for (auto& sample_to_group : sample_to_groups)
box_size += sample_to_group.ComputeSize();
return box_size;
}
EditList::EditList() {}
@ -2199,152 +2395,6 @@ uint32_t TrackFragmentRun::ComputeSizeInternal() {
return box_size;
}
SampleToGroup::SampleToGroup() : grouping_type(0), grouping_type_parameter(0) {}
SampleToGroup::~SampleToGroup() {}
FourCC SampleToGroup::BoxType() const { return FOURCC_sbgp; }
bool SampleToGroup::ReadWriteInternal(BoxBuffer* buffer) {
RCHECK(ReadWriteHeaderInternal(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 "
<< FourCCToString(static_cast<FourCC>(grouping_type))
<< " is not supported.";
return true;
}
uint32_t count = entries.size();
RCHECK(buffer->ReadWriteUInt32(&count));
entries.resize(count);
for (uint32_t i = 0; i < count; ++i) {
RCHECK(buffer->ReadWriteUInt32(&entries[i].sample_count) &&
buffer->ReadWriteUInt32(&entries[i].group_description_index));
}
return true;
}
uint32_t SampleToGroup::ComputeSizeInternal() {
// This box is optional. Skip it if it is not used.
if (entries.empty())
return 0;
return HeaderSize() + sizeof(grouping_type) +
(version == 1 ? sizeof(grouping_type_parameter) : 0) +
sizeof(uint32_t) + entries.size() * sizeof(entries[0]);
}
CencSampleEncryptionInfoEntry::CencSampleEncryptionInfoEntry()
: is_protected(0),
per_sample_iv_size(0),
crypt_byte_block(0),
skip_byte_block(0) {}
CencSampleEncryptionInfoEntry::~CencSampleEncryptionInfoEntry() {};
SampleGroupDescription::SampleGroupDescription() : grouping_type(0) {}
SampleGroupDescription::~SampleGroupDescription() {}
FourCC SampleGroupDescription::BoxType() const { return FOURCC_sgpd; }
bool SampleGroupDescription::ReadWriteInternal(BoxBuffer* buffer) {
RCHECK(ReadWriteHeaderInternal(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 kEntrySize = sizeof(uint32_t) + kCencKeyIdSize;
uint32_t 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_t count = entries.size();
RCHECK(buffer->ReadWriteUInt32(&count));
entries.resize(count);
for (uint32_t i = 0; i < count; ++i) {
if (version == 1) {
if (buffer->Reading() && default_length == 0) {
uint32_t description_length = 0;
RCHECK(buffer->ReadWriteUInt32(&description_length));
RCHECK(description_length >= kEntrySize);
}
}
if (!buffer->Reading()) {
if (entries[i].key_id.size() != kCencKeyIdSize) {
LOG(WARNING) << "CENC defines key id length of " << kCencKeyIdSize
<< " bytes; got " << entries[i].key_id.size()
<< ". Resized accordingly.";
entries[i].key_id.resize(kCencKeyIdSize);
}
RCHECK(entries[i].crypt_byte_block < 16 &&
entries[i].skip_byte_block < 16);
}
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 (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].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;
}
uint32_t SampleGroupDescription::ComputeSizeInternal() {
// Version 0 is obsoleted, so always generate version 1 box.
version = 1;
// This box is optional. Skip it if it is not used.
if (entries.empty())
return 0;
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;
}
TrackFragment::TrackFragment() : decode_time_absent(false) {}
TrackFragment::~TrackFragment() {}
FourCC TrackFragment::BoxType() const { return FOURCC_traf; }
@ -2358,27 +2408,18 @@ bool TrackFragment::ReadWriteInternal(BoxBuffer* buffer) {
decode_time_absent = !buffer->reader()->ChildExist(&decode_time);
if (!decode_time_absent)
RCHECK(buffer->ReadWriteChild(&decode_time));
RCHECK(buffer->reader()->TryReadChildren(&runs));
// There could be multiple SampleGroupDescription and SampleToGroup boxes
// with different grouping types. For common encryption, the relevant
// grouping type is 'seig'. Continue reading until 'seig' is found, or
// until running out of child boxes.
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));
}
RCHECK(buffer->reader()->TryReadChildren(&runs) &&
buffer->reader()->TryReadChildren(&sample_group_descriptions) &&
buffer->reader()->TryReadChildren(&sample_to_groups));
} else {
if (!decode_time_absent)
RCHECK(buffer->ReadWriteChild(&decode_time));
for (uint32_t i = 0; i < runs.size(); ++i)
RCHECK(buffer->ReadWriteChild(&runs[i]));
RCHECK(buffer->TryReadWriteChild(&sample_to_group) &&
buffer->TryReadWriteChild(&sample_group_description));
for (uint32_t i = 0; i < sample_to_groups.size(); ++i)
RCHECK(buffer->ReadWriteChild(&sample_to_groups[i]));
for (uint32_t i = 0; i < sample_group_descriptions.size(); ++i)
RCHECK(buffer->ReadWriteChild(&sample_group_descriptions[i]));
}
return buffer->TryReadWriteChild(&auxiliary_size) &&
buffer->TryReadWriteChild(&auxiliary_offset) &&
@ -2388,11 +2429,14 @@ bool TrackFragment::ReadWriteInternal(BoxBuffer* buffer) {
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() +
sample_encryption.ComputeSize();
for (uint32_t i = 0; i < runs.size(); ++i)
box_size += runs[i].ComputeSize();
for (uint32_t i = 0; i < sample_group_descriptions.size(); ++i)
box_size += sample_group_descriptions[i].ComputeSize();
for (uint32_t i = 0; i < sample_to_groups.size(); ++i)
box_size += sample_to_groups[i].ComputeSize();
return box_size;
}

View File

@ -468,6 +468,67 @@ struct SyncSample : FullBox {
std::vector<uint32_t> sample_number;
};
struct CencSampleEncryptionInfoEntry {
CencSampleEncryptionInfoEntry();
~CencSampleEncryptionInfoEntry();
bool ReadWrite(BoxBuffer* buffer);
uint32_t ComputeSize() const;
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 AudioRollRecoveryEntry {
AudioRollRecoveryEntry();
~AudioRollRecoveryEntry();
bool ReadWrite(BoxBuffer* buffer);
uint32_t ComputeSize() const;
int16_t roll_distance;
};
struct SampleGroupDescription : FullBox {
DECLARE_BOX_METHODS(SampleGroupDescription);
template <typename T>
bool ReadWriteEntries(BoxBuffer* buffer, std::vector<T>* entries);
uint32_t grouping_type;
// Only present if grouping_type == 'seig'.
std::vector<CencSampleEncryptionInfoEntry>
cenc_sample_encryption_info_entries;
// Only present if grouping_type == 'roll'.
std::vector<AudioRollRecoveryEntry> audio_roll_recovery_entries;
};
struct SampleToGroupEntry {
enum GroupDescriptionIndexBase {
kTrackGroupDescriptionIndexBase = 0,
kTrackFragmentGroupDescriptionIndexBase = 0x10000,
};
uint32_t sample_count;
uint32_t group_description_index;
};
struct SampleToGroup : FullBox {
DECLARE_BOX_METHODS(SampleToGroup);
uint32_t grouping_type;
uint32_t grouping_type_parameter; // Version 1 only.
std::vector<SampleToGroupEntry> entries;
};
struct SampleTable : Box {
DECLARE_BOX_METHODS(SampleTable);
@ -481,6 +542,8 @@ struct SampleTable : Box {
// ChunkLargeOffset.
ChunkLargeOffset chunk_large_offset;
SyncSample sync_sample;
std::vector<SampleGroupDescription> sample_group_descriptions;
std::vector<SampleToGroup> sample_to_groups;
};
struct MediaHeader : FullBox {
@ -654,47 +717,6 @@ struct TrackFragmentRun : FullBox {
std::vector<int64_t> sample_composition_time_offsets;
};
struct SampleToGroupEntry {
enum GroupDescriptionIndexBase {
kTrackGroupDescriptionIndexBase = 0,
kTrackFragmentGroupDescriptionIndexBase = 0x10000,
};
uint32_t sample_count;
uint32_t group_description_index;
};
struct SampleToGroup : FullBox {
DECLARE_BOX_METHODS(SampleToGroup);
uint32_t grouping_type;
uint32_t grouping_type_parameter; // Version 1 only.
std::vector<SampleToGroupEntry> entries;
};
struct CencSampleEncryptionInfoEntry {
CencSampleEncryptionInfoEntry();
~CencSampleEncryptionInfoEntry();
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 {
DECLARE_BOX_METHODS(SampleGroupDescription);
uint32_t grouping_type;
std::vector<CencSampleEncryptionInfoEntry> entries;
};
struct TrackFragment : Box {
DECLARE_BOX_METHODS(TrackFragment);
@ -702,8 +724,8 @@ struct TrackFragment : Box {
std::vector<TrackFragmentRun> runs;
bool decode_time_absent;
TrackFragmentDecodeTime decode_time;
SampleToGroup sample_to_group;
SampleGroupDescription sample_group_description;
std::vector<SampleGroupDescription> sample_group_descriptions;
std::vector<SampleToGroup> sample_to_groups;
SampleAuxiliaryInformationSize auxiliary_size;
SampleAuxiliaryInformationOffset auxiliary_offset;
SampleEncryption sample_encryption;

View File

@ -156,6 +156,42 @@ inline bool operator==(const SyncSample& lhs, const SyncSample& rhs) {
return lhs.sample_number == rhs.sample_number;
}
inline bool operator==(const CencSampleEncryptionInfoEntry& lhs,
const CencSampleEncryptionInfoEntry& rhs) {
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 AudioRollRecoveryEntry& lhs,
const AudioRollRecoveryEntry& rhs) {
return lhs.roll_distance == rhs.roll_distance;
}
inline bool operator==(const SampleGroupDescription& lhs,
const SampleGroupDescription& rhs) {
return lhs.grouping_type == rhs.grouping_type &&
lhs.cenc_sample_encryption_info_entries ==
rhs.cenc_sample_encryption_info_entries &&
lhs.audio_roll_recovery_entries == rhs.audio_roll_recovery_entries;
}
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 SampleTable& lhs, const SampleTable& rhs) {
return lhs.description == rhs.description &&
lhs.decoding_time_to_sample == rhs.decoding_time_to_sample &&
@ -163,7 +199,9 @@ inline bool operator==(const SampleTable& lhs, const SampleTable& rhs) {
lhs.sample_to_chunk == rhs.sample_to_chunk &&
lhs.sample_size == rhs.sample_size &&
lhs.chunk_large_offset == rhs.chunk_large_offset &&
lhs.sync_sample == rhs.sync_sample;
lhs.sync_sample == rhs.sync_sample &&
lhs.sample_group_descriptions == rhs.sample_group_descriptions &&
lhs.sample_to_groups == rhs.sample_to_groups;
}
inline bool operator==(const EditListEntry& lhs, const EditListEntry& rhs) {
@ -385,35 +423,6 @@ 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_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,
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

@ -567,6 +567,45 @@ class BoxDefinitionsTestGeneral : public testing::Test {
void Modify(SyncSample* stss) { stss->sample_number.pop_back(); }
void Fill(SampleGroupDescription* sgpd) {
sgpd->grouping_type = FOURCC_seig;
sgpd->cenc_sample_encryption_info_entries.resize(2);
sgpd->cenc_sample_encryption_info_entries[0].is_protected = 1;
sgpd->cenc_sample_encryption_info_entries[0].per_sample_iv_size = 8;
sgpd->cenc_sample_encryption_info_entries[0].key_id.assign(
kData16Bytes, kData16Bytes + arraysize(kData16Bytes));
sgpd->cenc_sample_encryption_info_entries[0].crypt_byte_block = 3;
sgpd->cenc_sample_encryption_info_entries[0].skip_byte_block = 7;
sgpd->cenc_sample_encryption_info_entries[1].is_protected = 0;
sgpd->cenc_sample_encryption_info_entries[1].per_sample_iv_size = 0;
sgpd->cenc_sample_encryption_info_entries[1].key_id.resize(16);
sgpd->version = 1;
}
void Modify(SampleGroupDescription* sgpd) {
sgpd->cenc_sample_encryption_info_entries.resize(1);
sgpd->cenc_sample_encryption_info_entries[0].is_protected = 1;
sgpd->cenc_sample_encryption_info_entries[0].per_sample_iv_size = 0;
sgpd->cenc_sample_encryption_info_entries[0].constant_iv.assign(
kData16Bytes, kData16Bytes + arraysize(kData16Bytes));
sgpd->cenc_sample_encryption_info_entries[0].key_id.resize(16);
}
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[1].sample_count = 1212;
sbgp->entries[1].group_description_index = 0x10001;
}
void Modify(SampleToGroup* sbgp) {
sbgp->entries.resize(1);
sbgp->entries[0].sample_count = 5;
sbgp->entries[0].group_description_index = 0x10001;
}
void Fill(SampleTable* stbl) {
Fill(&stbl->description);
Fill(&stbl->decoding_time_to_sample);
@ -575,11 +614,17 @@ class BoxDefinitionsTestGeneral : public testing::Test {
Fill(&stbl->sample_size);
Fill(&stbl->chunk_large_offset);
Fill(&stbl->sync_sample);
stbl->sample_group_descriptions.resize(1);
Fill(&stbl->sample_group_descriptions[0]);
stbl->sample_to_groups.resize(1);
Fill(&stbl->sample_to_groups[0]);
}
void Modify(SampleTable* stbl) {
Modify(&stbl->chunk_large_offset);
Modify(&stbl->sync_sample);
stbl->sample_group_descriptions.clear();
stbl->sample_to_groups.clear();
}
void Fill(MediaHeader* mdhd) {
@ -768,47 +813,6 @@ class BoxDefinitionsTestGeneral : public testing::Test {
trun->version = 0;
}
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[1].sample_count = 1212;
sbgp->entries[1].group_description_index = 0x10001;
}
void Modify(SampleToGroup* sbgp) {
sbgp->entries.resize(1);
sbgp->entries[0].sample_count = 5;
sbgp->entries[0].group_description_index = 0x10001;
}
void Fill(SampleGroupDescription* sgpd) {
sgpd->grouping_type = FOURCC_seig;
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[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;
}
void Modify(SampleGroupDescription* sgpd) {
sgpd->entries.resize(1);
sgpd->entries[0].key_id[4] = 88;
sgpd->version = 1;
}
void Fill(TrackFragment* traf) {
Fill(&traf->header);
traf->runs.resize(1);
@ -821,8 +825,19 @@ class BoxDefinitionsTestGeneral : public testing::Test {
void Modify(TrackFragment* traf) {
Modify(&traf->header);
Modify(&traf->decode_time);
Fill(&traf->sample_to_group);
Fill(&traf->sample_group_description);
traf->sample_group_descriptions.resize(2);
Fill(&traf->sample_group_descriptions[0]);
traf->sample_group_descriptions[1].grouping_type = FOURCC_roll;
traf->sample_group_descriptions[1].audio_roll_recovery_entries.resize(1);
traf->sample_group_descriptions[1]
.audio_roll_recovery_entries[0]
.roll_distance = -10;
traf->sample_to_groups.resize(2);
Fill(&traf->sample_to_groups[0]);
Modify(&traf->sample_to_groups[1]);
traf->sample_to_groups[1].grouping_type = FOURCC_roll;
}
void Fill(MovieFragment* moof) {
@ -951,10 +966,10 @@ class BoxDefinitionsTestGeneral : public testing::Test {
bool IsOptional(const WebVTTSourceLabelBox* box) { return true; }
bool IsOptional(const CompositionTimeToSample* box) { return true; }
bool IsOptional(const SyncSample* box) { return true; }
bool IsOptional(const SampleGroupDescription* box) { return true; }
bool IsOptional(const SampleToGroup* 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; }
bool IsOptional(const CueSourceIDBox* box) { return true; }
bool IsOptional(const CueIDBox* box) { return true; }
bool IsOptional(const CueTimeBox* box) { return true; }
@ -1006,6 +1021,8 @@ typedef testing::Types<FileType,
ChunkLargeOffset,
ChunkOffset,
SyncSample,
SampleGroupDescription,
SampleToGroup,
SampleTable>
Boxes;
typedef testing::Types<MediaHeader,
@ -1029,8 +1046,6 @@ typedef testing::Types<MediaHeader,
TrackFragment,
MovieFragment,
SegmentIndex,
SampleToGroup,
SampleGroupDescription,
CueSourceIDBox,
CueTimeBox,
CueIDBox,

View File

@ -67,7 +67,7 @@ EncryptingFragmenter::EncryptingFragmenter(
FourCC protection_scheme,
uint8_t crypt_byte_block,
uint8_t skip_byte_block)
: Fragmenter(traf),
: Fragmenter(info, traf),
info_(info),
encryption_key_(encryption_key.Pass()),
nalu_length_size_(GetNaluLengthSize(*info)),

View File

@ -26,6 +26,7 @@ namespace mp4 {
/// EncryptingFragmenter generates MP4 fragments with sample encrypted.
class EncryptingFragmenter : public Fragmenter {
public:
/// @param info contains stream information.
/// @param traf points to a TrackFragment box.
/// @param encryption_key contains the encryption parameters.
/// @param clear_time specifies clear lead duration in units of the current

View File

@ -9,6 +9,7 @@
#include <limits>
#include "packager/media/base/buffer_writer.h"
#include "packager/media/base/audio_stream_info.h"
#include "packager/media/base/media_sample.h"
#include "packager/media/formats/mp4/box_definitions.h"
@ -18,10 +19,18 @@ namespace mp4 {
namespace {
const int64_t kInvalidTime = std::numeric_limits<int64_t>::max();
uint64_t GetSeekPreroll(const StreamInfo& stream_info) {
if (stream_info.stream_type() != kStreamAudio) return 0;
const AudioStreamInfo& audio_stream_info =
static_cast<const AudioStreamInfo&>(stream_info);
return audio_stream_info.seek_preroll_ns();
}
} // namespace
Fragmenter::Fragmenter(TrackFragment* traf)
Fragmenter::Fragmenter(scoped_refptr<StreamInfo> info, TrackFragment* traf)
: traf_(traf),
seek_preroll_(GetSeekPreroll(*info)),
fragment_initialized_(false),
fragment_finalized_(false),
fragment_duration_(0),
@ -80,6 +89,8 @@ Status Fragmenter::InitializeFragment(int64_t first_sample_dts) {
traf_->runs.clear();
traf_->runs.resize(1);
traf_->runs[0].flags = TrackFragmentRun::kDataOffsetPresentMask;
traf_->sample_group_descriptions.clear();
traf_->sample_to_groups.clear();
traf_->header.sample_description_index = 1; // 1-based.
traf_->header.flags = TrackFragmentHeader::kDefaultBaseIsMoofMask |
TrackFragmentHeader::kSampleDescriptionIndexPresentMask;
@ -113,6 +124,35 @@ void Fragmenter::FinalizeFragment() {
traf_->runs[0].flags |= TrackFragmentRun::kSampleFlagsPresentMask;
}
// Add SampleToGroup boxes. A SampleToGroup box with grouping type of 'roll'
// needs to be added if there is seek preroll, referencing sample group
// description in track level; Also need to add SampleToGroup boxes
// correponding to every SampleGroupDescription boxes, referencing sample
// group description in fragment level.
DCHECK_EQ(traf_->sample_to_groups.size(), 0u);
if (seek_preroll_ > 0) {
traf_->sample_to_groups.resize(traf_->sample_to_groups.size() + 1);
SampleToGroup& sample_to_group = traf_->sample_to_groups.back();
sample_to_group.grouping_type = FOURCC_roll;
sample_to_group.entries.resize(1);
SampleToGroupEntry& sample_to_group_entry = sample_to_group.entries.back();
sample_to_group_entry.sample_count = traf_->runs[0].sample_count;
sample_to_group_entry.group_description_index =
SampleToGroupEntry::kTrackGroupDescriptionIndexBase + 1;
}
for (const auto& sample_group_description : traf_->sample_group_descriptions) {
traf_->sample_to_groups.resize(traf_->sample_to_groups.size() + 1);
SampleToGroup& sample_to_group = traf_->sample_to_groups.back();
sample_to_group.grouping_type = sample_group_description.grouping_type;
sample_to_group.entries.resize(1);
SampleToGroupEntry& sample_to_group_entry = sample_to_group.entries.back();
sample_to_group_entry.sample_count = traf_->runs[0].sample_count;
sample_to_group_entry.group_description_index =
SampleToGroupEntry::kTrackFragmentGroupDescriptionIndexBase + 1;
}
fragment_finalized_ = true;
fragment_initialized_ = false;
}

View File

@ -19,6 +19,7 @@ namespace media {
class BufferWriter;
class MediaSample;
class StreamInfo;
namespace mp4 {
@ -29,8 +30,9 @@ struct TrackFragment;
/// box and corresponding 'mdat' box.
class Fragmenter {
public:
/// @param info contains stream information.
/// @param traf points to a TrackFragment box.
Fragmenter(TrackFragment* traf);
Fragmenter(scoped_refptr<StreamInfo> info, TrackFragment* traf);
virtual ~Fragmenter();
@ -74,6 +76,7 @@ class Fragmenter {
bool StartsWithSAP();
TrackFragment* traf_;
uint64_t seek_preroll_;
bool fragment_initialized_;
bool fragment_finalized_;
uint64_t fragment_duration_;

View File

@ -107,9 +107,15 @@ Status KeyRotationFragmenter::PrepareFragmentForEncryption(
// i.e. there is at most one key for the fragment. So there should be only
// one entry in SampleGroupDescription box and one entry in SampleToGroup box.
// Fill in SampleGroupDescription box information.
traf()->sample_group_description.grouping_type = FOURCC_seig;
traf()->sample_group_description.entries.resize(1);
auto& sample_group_entry = traf()->sample_group_description.entries[0];
traf()->sample_group_descriptions.resize(
traf()->sample_group_descriptions.size() + 1);
SampleGroupDescription& sample_group_description =
traf()->sample_group_descriptions.back();
sample_group_description.grouping_type = FOURCC_seig;
sample_group_description.cenc_sample_encryption_info_entries.resize(1);
CencSampleEncryptionInfoEntry& sample_group_entry =
sample_group_description.cenc_sample_encryption_info_entries.back();
sample_group_entry.is_protected = 1;
if (protection_scheme() == FOURCC_cbcs) {
// For 'cbcs' scheme, Constant IVs SHALL be used.
@ -122,23 +128,9 @@ Status KeyRotationFragmenter::PrepareFragmentForEncryption(
sample_group_entry.skip_byte_block = skip_byte_block();
sample_group_entry.key_id = encryption_key()->key_id;
// Fill in SampleToGroup box information.
traf()->sample_to_group.grouping_type = FOURCC_seig;
traf()->sample_to_group.entries.resize(1);
// sample_count is adjusted in |FinalizeFragment| later.
traf()->sample_to_group.entries[0].group_description_index =
SampleToGroupEntry::kTrackFragmentGroupDescriptionIndexBase + 1;
return Status::OK;
}
void KeyRotationFragmenter::FinalizeFragmentForEncryption() {
EncryptingFragmenter::FinalizeFragmentForEncryption();
DCHECK_EQ(1u, traf()->sample_to_group.entries.size());
traf()->sample_to_group.entries[0].sample_count =
traf()->auxiliary_size.sample_count;
}
} // namespace mp4
} // namespace media
} // namespace edash_packager

View File

@ -23,6 +23,7 @@ struct MovieFragment;
class KeyRotationFragmenter : public EncryptingFragmenter {
public:
/// @param moof points to a MovieFragment box.
/// @param info contains stream information.
/// @param traf points to a TrackFragment box.
/// @param encryption_key_source points to the source which generates
/// encryption keys.
@ -57,7 +58,6 @@ class KeyRotationFragmenter : public EncryptingFragmenter {
/// @name Fragmenter implementation overrides.
/// @{
Status PrepareFragmentForEncryption(bool enable_encryption) override;
void FinalizeFragmentForEncryption() override;
/// @}
private:

View File

@ -440,6 +440,34 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
return false;
}
// Extract possible seek preroll.
uint64_t seek_preroll_ns = 0;
for (const auto& sample_group_description :
track->media.information.sample_table.sample_group_descriptions) {
if (sample_group_description.grouping_type != FOURCC_roll)
continue;
const auto& audio_roll_recovery_entries =
sample_group_description.audio_roll_recovery_entries;
if (audio_roll_recovery_entries.size() != 1) {
LOG(WARNING) << "Unexpected number of entries in "
"SampleGroupDescription table with grouping type "
"'roll'.";
break;
}
const int16_t roll_distance_in_samples =
audio_roll_recovery_entries[0].roll_distance;
if (roll_distance_in_samples < 0) {
RCHECK(sampling_frequency != 0);
seek_preroll_ns = kNanosecondsPerSecond *
(-roll_distance_in_samples) / sampling_frequency;
} else {
LOG(WARNING)
<< "Roll distance is supposed to be negative, but seeing "
<< roll_distance_in_samples;
}
break;
}
const bool is_encrypted =
entry.sinf.info.track_encryption.default_is_protected == 1;
DVLOG(1) << "is_audio_track_encrypted_: " << is_encrypted;
@ -453,7 +481,7 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
entry.samplesize,
num_channels,
sampling_frequency,
0 /* seek preroll */,
seek_preroll_ns,
codec_delay_ns,
max_bitrate,
avg_bitrate,

View File

@ -287,10 +287,41 @@ void MP4Muxer::GenerateAudioTrak(const AudioStreamInfo* audio_info,
audio.channelcount = audio_info->num_channels();
audio.samplesize = audio_info->sample_bits();
audio.samplerate = audio_info->sampling_frequency();
SampleDescription& sample_description =
trak->media.information.sample_table.description;
SampleTable& sample_table = trak->media.information.sample_table;
SampleDescription& sample_description = sample_table.description;
sample_description.type = kAudio;
sample_description.audio_entries.push_back(audio);
// Opus requires at least one sample group description box and at least one
// sample to group box with grouping type 'roll' within sample table box.
if (audio_info->codec() == kCodecOpus) {
sample_table.sample_group_descriptions.resize(1);
SampleGroupDescription& sample_group_description =
sample_table.sample_group_descriptions.back();
sample_group_description.grouping_type = FOURCC_roll;
sample_group_description.audio_roll_recovery_entries.resize(1);
// The roll distance is expressed in sample units and always takes negative
// values.
const uint64_t kNanosecondsPerSecond = 1000000000ull;
sample_group_description.audio_roll_recovery_entries[0].roll_distance =
-(audio_info->seek_preroll_ns() * audio.samplerate +
kNanosecondsPerSecond / 2) /
kNanosecondsPerSecond;
sample_table.sample_to_groups.resize(1);
SampleToGroup& sample_to_group = sample_table.sample_to_groups.back();
sample_to_group.grouping_type = FOURCC_roll;
sample_to_group.entries.resize(1);
SampleToGroupEntry& sample_to_group_entry = sample_to_group.entries.back();
// All samples are in track fragments.
sample_to_group_entry.sample_count = 0;
sample_to_group_entry.group_description_index =
SampleToGroupEntry::kTrackGroupDescriptionIndexBase + 1;
} else if (audio_info->seek_preroll_ns() != 0) {
LOG(WARNING) << "Unexpected seek preroll for codec " << audio_info->codec();
return;
}
}
bool MP4Muxer::GetInitRangeStartAndEnd(uint32_t* start, uint32_t* end) {

View File

@ -170,7 +170,7 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
sidx_->reference_id = i + 1;
}
if (!encryption_key_source) {
fragmenters_[i] = new Fragmenter(&moof_->tracks[i]);
fragmenters_[i] = new Fragmenter(streams[i]->info(), &moof_->tracks[i]);
continue;
}