Define SampleEncryption 'senc' box

Issue #41

Change-Id: Ib28c079209f3a58ecd8d7b86d720a1c8c774b669
This commit is contained in:
KongQun Yang 2015-12-21 10:34:21 -08:00
parent 814d2414e3
commit 0c870b7c02
10 changed files with 321 additions and 24 deletions

View File

@ -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));
}

View File

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

View File

@ -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;
}

View File

@ -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<uint32_t>::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<SampleEncryptionEntry>* 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;

View File

@ -7,6 +7,7 @@
#include <vector>
#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<uint8_t> 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<uint8_t> initialization_vector;
std::vector<SubsampleEntry> 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<SampleEncryptionEntry>* 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<uint8_t> sample_encryption_data;
size_t iv_size;
std::vector<SampleEncryptionEntry> 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<SampleEntry> sample_entries.
std::vector<VideoSampleEntry> video_entries;
std::vector<AudioSampleEntry> audio_entries;
std::vector<WVTTSampleEntry> 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 {

View File

@ -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) {

View File

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

View File

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

View File

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

View File

@ -42,7 +42,7 @@ class MP4Muxer : public Muxer {
Status DoAddSample(const MediaStream* stream,
scoped_refptr<MediaSample> 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,