diff --git a/media/mp4/box_definitions_comparison.h b/media/mp4/box_definitions_comparison.h new file mode 100644 index 0000000000..396472c5fe --- /dev/null +++ b/media/mp4/box_definitions_comparison.h @@ -0,0 +1,337 @@ +// Copyright (c) 2013 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Overloads operator== for mp4 boxes, mainly used for testing. + +#ifndef MEDIA_MP4_BOX_DEFINITIONS_COMPARISON_H_ +#define MEDIA_MP4_BOX_DEFINITIONS_COMPARISON_H_ + +#include "media/mp4/box_definitions.h" + +namespace media { +namespace mp4 { + +inline bool operator==(const FileType& lhs, const FileType& rhs) { + return lhs.major_brand == rhs.major_brand && + lhs.minor_version == rhs.minor_version && + lhs.compatible_brands == rhs.compatible_brands; +} + +inline bool operator==(const ProtectionSystemSpecificHeader& lhs, + const ProtectionSystemSpecificHeader& rhs) { + return lhs.system_id == rhs.system_id && lhs.data == rhs.data; +} + +inline bool operator==(const SampleAuxiliaryInformationOffset& lhs, + const SampleAuxiliaryInformationOffset& rhs) { + return lhs.offsets == rhs.offsets; +} + +inline bool operator==(const SampleAuxiliaryInformationSize& lhs, + const SampleAuxiliaryInformationSize& rhs) { + return lhs.default_sample_info_size == rhs.default_sample_info_size && + lhs.sample_count == rhs.sample_count && + lhs.sample_info_sizes == rhs.sample_info_sizes; +} + +inline bool operator==(const OriginalFormat& lhs, const OriginalFormat& rhs) { + return lhs.format == rhs.format; +} + +inline bool operator==(const SchemeType& lhs, const SchemeType& rhs) { + return lhs.type == rhs.type && lhs.version == rhs.version; +} + +inline bool operator==(const TrackEncryption& lhs, const TrackEncryption& rhs) { + return lhs.is_encrypted == rhs.is_encrypted && + lhs.default_iv_size == rhs.default_iv_size && + lhs.default_kid == rhs.default_kid; +} + +inline bool operator==(const SchemeInfo& lhs, const SchemeInfo& rhs) { + return lhs.track_encryption == rhs.track_encryption; +} + +inline bool operator==(const ProtectionSchemeInfo& lhs, + const ProtectionSchemeInfo& rhs) { + return lhs.format == rhs.format && lhs.type == rhs.type && + lhs.info == rhs.info; +} + +inline bool operator==(const MovieHeader& lhs, const MovieHeader& rhs) { + return lhs.creation_time == rhs.creation_time && + lhs.modification_time == rhs.modification_time && + lhs.timescale == rhs.timescale && lhs.duration == rhs.duration && + lhs.rate == rhs.rate && lhs.volume == rhs.volume && + lhs.next_track_id == rhs.next_track_id; +} + +inline bool operator==(const TrackHeader& lhs, const TrackHeader& rhs) { + return lhs.creation_time == rhs.creation_time && + lhs.modification_time == rhs.modification_time && + lhs.track_id == rhs.track_id && lhs.duration == rhs.duration && + lhs.layer == rhs.layer && lhs.alternate_group == rhs.alternate_group && + lhs.volume == rhs.volume && lhs.width == rhs.width && + lhs.height == rhs.height; +} + +inline bool operator==(const SampleDescription& lhs, + const SampleDescription& rhs) { + return lhs.type == rhs.type && lhs.video_entries == rhs.video_entries && + lhs.audio_entries == rhs.audio_entries; +} + +inline bool operator==(const DecodingTime& lhs, const DecodingTime& rhs) { + return lhs.sample_count == rhs.sample_count && + lhs.sample_delta == rhs.sample_delta; +} + +inline bool operator==(const DecodingTimeToSample& lhs, + const DecodingTimeToSample& rhs) { + return lhs.decoding_time == rhs.decoding_time; +} + +inline bool operator==(const CompositionOffset& lhs, + const CompositionOffset& rhs) { + return lhs.sample_count == rhs.sample_count && + lhs.sample_offset == rhs.sample_offset; +} + +inline bool operator==(const CompositionTimeToSample& lhs, + const CompositionTimeToSample& rhs) { + return lhs.composition_offset == rhs.composition_offset; +} + +inline bool operator==(const ChunkInfo& lhs, const ChunkInfo& rhs) { + return lhs.first_chunk == rhs.first_chunk && + lhs.samples_per_chunk == rhs.samples_per_chunk && + lhs.sample_description_index == rhs.sample_description_index; +} + +inline bool operator==(const SampleToChunk& lhs, const SampleToChunk& rhs) { + return lhs.chunk_info == rhs.chunk_info; +} + +inline bool operator==(const SampleSize& lhs, const SampleSize& rhs) { + return lhs.sample_size == rhs.sample_size && + lhs.sample_count == rhs.sample_count && lhs.sizes == rhs.sizes; +} + +inline bool operator==(const CompactSampleSize& lhs, + const CompactSampleSize& rhs) { + return lhs.field_size == rhs.field_size && lhs.sizes == rhs.sizes; +} + +inline bool operator==(const ChunkLargeOffset& lhs, + const ChunkLargeOffset& rhs) { + return lhs.offsets == rhs.offsets; +} + +inline bool operator==(const SyncSample& lhs, const SyncSample& rhs) { + return lhs.sample_number == rhs.sample_number; +} + +inline bool operator==(const SampleTable& lhs, const SampleTable& rhs) { + return lhs.description == rhs.description && + lhs.decoding_time_to_sample == rhs.decoding_time_to_sample && + lhs.composition_time_to_sample == rhs.composition_time_to_sample && + 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; +} + +inline bool operator==(const EditListEntry& lhs, const EditListEntry& rhs) { + return lhs.segment_duration == rhs.segment_duration && + lhs.media_time == rhs.media_time && + lhs.media_rate_integer == rhs.media_rate_integer && + lhs.media_rate_fraction == rhs.media_rate_fraction; +} + +inline bool operator==(const EditList& lhs, const EditList& rhs) { + return lhs.edits == rhs.edits; +} + +inline bool operator==(const Edit& lhs, const Edit& rhs) { + return lhs.list == rhs.list; +} + +inline bool operator==(const HandlerReference& lhs, + const HandlerReference& rhs) { + return lhs.type == rhs.type; +} + +inline bool operator==(const AVCDecoderConfigurationRecord& lhs, + const AVCDecoderConfigurationRecord& rhs) { + return lhs.data == rhs.data; +} + +inline bool operator==(const PixelAspectRatioBox& lhs, + const PixelAspectRatioBox& rhs) { + return lhs.h_spacing == rhs.h_spacing && lhs.v_spacing == rhs.v_spacing; +} + +inline bool operator==(const VideoSampleEntry& lhs, + const VideoSampleEntry& rhs) { + return lhs.format == rhs.format && + lhs.data_reference_index == rhs.data_reference_index && + lhs.width == rhs.width && lhs.height == rhs.height && + lhs.pixel_aspect == rhs.pixel_aspect && lhs.sinf == rhs.sinf && + lhs.avcc == rhs.avcc; +} + +inline bool operator==(const ESDescriptor& lhs, const ESDescriptor& rhs) { + return lhs.esid() == rhs.esid() && lhs.object_type() == rhs.object_type() && + lhs.decoder_specific_info() == rhs.decoder_specific_info(); +} + +inline bool operator==(const ElementaryStreamDescriptor& lhs, + const ElementaryStreamDescriptor& rhs) { + return lhs.es_descriptor == rhs.es_descriptor; +} + +inline bool operator==(const AudioSampleEntry& lhs, + const AudioSampleEntry& rhs) { + return lhs.format == rhs.format && + lhs.data_reference_index == rhs.data_reference_index && + lhs.channelcount == rhs.channelcount && + lhs.samplesize == rhs.samplesize && lhs.samplerate == rhs.samplerate && + lhs.sinf == rhs.sinf && lhs.esds == rhs.esds; +} + +inline bool operator==(const MediaHeader& lhs, const MediaHeader& rhs) { + return lhs.creation_time == rhs.creation_time && + lhs.modification_time == rhs.modification_time && + lhs.timescale == rhs.timescale && lhs.duration == rhs.duration && + strcmp(lhs.language, rhs.language) == 0; +} + +inline bool operator==(const VideoMediaHeader& lhs, + const VideoMediaHeader& rhs) { + return lhs.graphicsmode == rhs.graphicsmode && + lhs.opcolor_red == rhs.opcolor_red && + lhs.opcolor_green == rhs.opcolor_green && + lhs.opcolor_blue == rhs.opcolor_blue; +} + +inline bool operator==(const SoundMediaHeader& lhs, + const SoundMediaHeader& rhs) { + return lhs.balance == rhs.balance; +} + +inline bool operator==(const DataEntryUrl& lhs, const DataEntryUrl& rhs) { + return lhs.flags == rhs.flags && lhs.location == rhs.location; +} + +inline bool operator==(const DataReference& lhs, const DataReference& rhs) { + return lhs.data_entry == rhs.data_entry; +} + +inline bool operator==(const DataInformation& lhs, const DataInformation& rhs) { + return lhs.dref == rhs.dref; +} + +inline bool operator==(const MediaInformation& lhs, + const MediaInformation& rhs) { + return lhs.dinf == rhs.dinf && lhs.sample_table == rhs.sample_table && + lhs.vmhd == rhs.vmhd && lhs.smhd == rhs.smhd; +} + +inline bool operator==(const Media& lhs, const Media& rhs) { + return lhs.header == rhs.header && lhs.handler == rhs.handler && + lhs.information == rhs.information; +} + +inline bool operator==(const Track& lhs, const Track& rhs) { + return lhs.header == rhs.header && lhs.media == rhs.media && + lhs.edit == rhs.edit; +} + +inline bool operator==(const MovieExtendsHeader& lhs, + const MovieExtendsHeader& rhs) { + return lhs.fragment_duration == rhs.fragment_duration; +} + +inline bool operator==(const TrackExtends& lhs, const TrackExtends& rhs) { + return lhs.track_id == rhs.track_id && + lhs.default_sample_description_index == + rhs.default_sample_description_index && + lhs.default_sample_duration == rhs.default_sample_duration && + lhs.default_sample_size == rhs.default_sample_size && + lhs.default_sample_flags == rhs.default_sample_flags; +} + +inline bool operator==(const MovieExtends& lhs, const MovieExtends& rhs) { + return lhs.header == rhs.header && lhs.tracks == rhs.tracks; +} + +inline bool operator==(const Movie& lhs, const Movie& rhs) { + return lhs.header == rhs.header && lhs.extends == rhs.extends && + lhs.tracks == rhs.tracks && lhs.pssh == rhs.pssh; +} + +inline bool operator==(const TrackFragmentDecodeTime& lhs, + const TrackFragmentDecodeTime& rhs) { + return lhs.decode_time == rhs.decode_time; +} + +inline bool operator==(const MovieFragmentHeader& lhs, + const MovieFragmentHeader& rhs) { + return lhs.sequence_number == rhs.sequence_number; +} + +inline bool operator==(const TrackFragmentHeader& lhs, + const TrackFragmentHeader& rhs) { + return lhs.flags == rhs.flags && lhs.track_id == rhs.track_id && + lhs.sample_description_index == rhs.sample_description_index && + lhs.default_sample_duration == rhs.default_sample_duration && + lhs.default_sample_size == rhs.default_sample_size && + lhs.default_sample_flags == rhs.default_sample_flags; +} + +inline bool operator==(const TrackFragmentRun& lhs, + const TrackFragmentRun& rhs) { + return lhs.flags == rhs.flags && lhs.sample_count == rhs.sample_count && + lhs.data_offset == rhs.data_offset && + lhs.sample_flags == rhs.sample_flags && + lhs.sample_sizes == rhs.sample_sizes && + lhs.sample_durations == rhs.sample_durations && + lhs.sample_composition_time_offsets == + rhs.sample_composition_time_offsets; +} + +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; +} + +inline bool operator==(const MovieFragment& lhs, const MovieFragment& rhs) { + return lhs.header == rhs.header && lhs.tracks == rhs.tracks && + lhs.pssh == rhs.pssh; +} + +inline bool operator==(const SegmentReference& lhs, + const SegmentReference& rhs) { + return lhs.reference_type == rhs.reference_type && + lhs.referenced_size == rhs.referenced_size && + lhs.subsegment_duration == rhs.subsegment_duration && + lhs.starts_with_sap == rhs.starts_with_sap && + lhs.sap_type == rhs.sap_type && + lhs.sap_delta_time == rhs.sap_delta_time; +} + +inline bool operator==(const SegmentIndex& lhs, const SegmentIndex& rhs) { + return lhs.reference_id == rhs.reference_id && + lhs.timescale == rhs.timescale && + lhs.earliest_presentation_time == rhs.earliest_presentation_time && + lhs.first_offset == rhs.first_offset && + lhs.references == rhs.references; +} + +} // namespace mp4 +} // namespace media + +#endif // MEDIA_MP4_BOX_DEFINITIONS_COMPARISON_H_ diff --git a/media/mp4/box_definitions_unittest.cc b/media/mp4/box_definitions_unittest.cc new file mode 100644 index 0000000000..392e00264b --- /dev/null +++ b/media/mp4/box_definitions_unittest.cc @@ -0,0 +1,857 @@ +// Copyright (c) 2013 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/mp4/box_definitions.h" + +#include "base/memory/scoped_ptr.h" +#include "media/base/buffer_writer.h" +#include "media/mp4/box_definitions_comparison.h" +#include "media/mp4/box_reader.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { +const uint8 kData16Bytes[] = {8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8}; +const uint8 kData4[] = {1, 5, 4, 3, 15}; +const uint8 kData8[] = {1, 8, 42, 98, 156}; +const uint16 kData16[] = {1, 15, 45, 768, 60000}; +const uint32 kData32[] = {1, 24, 99, 1234, 9000000}; +const uint64 kData64[] = {1, 9000000, 12345678901234, 56780909090900}; +const media::mp4::TrackType kSampleDescriptionTrackType = media::mp4::kVideo; + +// 4-byte FourCC + 4-bytes size. +const uint32 kBoxSize = 8; +} // namespace + +namespace media { +namespace mp4 { + +template +class BoxDefinitionsTestGeneral : public testing::Test { + public: + BoxDefinitionsTestGeneral() : buffer_(new BufferWriter) {} + + BoxReader* CreateReader() { + // Create a fake skip box contains the buffer and Write it. + BufferWriter buffer; + buffer.Swap(buffer_.get()); + uint32 skip_box_size = buffer.Size() + kBoxSize; + buffer_->AppendInt(skip_box_size); + buffer_->AppendInt(static_cast(FOURCC_SKIP)); + buffer_->AppendBuffer(buffer); + bool err = false; + return BoxReader::ReadTopLevelBox(buffer_->Buffer(), buffer_->Size(), &err); + } + + bool ReadBack(Box* box) { + BoxReader* reader = CreateReader(); + RCHECK(reader->ScanChildren() && reader->ReadChild(box)); + return true; + } + + // FourCC for VideoSampleEntry is not a constant, e.g. could be avc1, or encv. + bool ReadBack(VideoSampleEntry* video) { + BoxReader* reader = CreateReader(); + std::vector video_entries; + RCHECK(reader->ReadAllChildren(&video_entries)); + RCHECK(video_entries.size() == 1); + *video = video_entries[0]; + return true; + } + + // FourCC for AudioSampleEntry is not a constant, e.g. could be mp4a, or enca. + bool ReadBack(AudioSampleEntry* audio) { + BoxReader* reader = CreateReader(); + std::vector audio_entries; + RCHECK(reader->ReadAllChildren(&audio_entries)); + RCHECK(audio_entries.size() == 1); + *audio = audio_entries[0]; + return true; + } + + // SampleDescription cannot parse on its own. Its type parameter should + // be preset before scanning the box. + bool ReadBack(SampleDescription* stsd) { + stsd->type = kSampleDescriptionTrackType; + BoxReader* reader = CreateReader(); + RCHECK(reader->ScanChildren() && reader->ReadChild(stsd)); + return true; + } + + // SampleTable contains SampleDescription, which cannot parse on its own. + bool ReadBack(SampleTable* stbl) { + stbl->description.type = kSampleDescriptionTrackType; + BoxReader* reader = CreateReader(); + RCHECK(reader->ScanChildren() && reader->ReadChild(stbl)); + return true; + } + + // MediaInformation contains SampleDescription, which cannot parse on its own. + bool ReadBack(MediaInformation* minf) { + minf->sample_table.description.type = kSampleDescriptionTrackType; + BoxReader* reader = CreateReader(); + RCHECK(reader->ScanChildren() && reader->ReadChild(minf)); + return true; + } + + // Fill the box with sample data. + void Fill(Box* box) {} + + // Modify the box with another set of data. + void Modify(Box* box) {} + + // Is this box optional? + bool IsOptional(Box* box) { return false; } + + // Non-full box does not have version field. + uint8 GetAndClearVersion(Box* box) { return 0; } + + // Get full box version and then reset it to 0. + uint8 GetAndClearVersion(FullBox* full_box) { + uint8 version = full_box->version; + full_box->version = 0; + return version; + } + + void Fill(FileType* ftyp) { + ftyp->major_brand = FOURCC_DASH; + ftyp->minor_version = 567; + ftyp->compatible_brands.push_back(FOURCC_ISO6); + ftyp->compatible_brands.push_back(FOURCC_MP41); + ftyp->compatible_brands.push_back(FOURCC_AVC1); + } + + void Modify(FileType* ftyp) { + ftyp->major_brand = FOURCC_MP41; + ftyp->compatible_brands.clear(); + ftyp->compatible_brands.push_back(FOURCC_DASH); + } + + void Fill(ProtectionSystemSpecificHeader* pssh) { + pssh->system_id.assign(kData16Bytes, + kData16Bytes + arraysize(kData16Bytes)); + pssh->data.assign(kData8, kData8 + arraysize(kData8)); + } + + void Modify(ProtectionSystemSpecificHeader* pssh) { + pssh->system_id[2] *= 3; + pssh->data.assign(kData4, kData4 + arraysize(kData4)); + } + + void Fill(SampleAuxiliaryInformationOffset* saio) { + saio->offsets.assign(kData32, kData32 + arraysize(kData32)); + } + + void Modify(SampleAuxiliaryInformationOffset* saio) { + saio->offsets.push_back(23); + } + + void Fill(SampleAuxiliaryInformationSize* saiz) { + saiz->default_sample_info_size = 0; + saiz->sample_info_sizes.assign(kData8, kData8 + arraysize(kData8)); + saiz->sample_count = arraysize(kData8); + } + + void Modify(SampleAuxiliaryInformationSize* saiz) { + saiz->default_sample_info_size = 15; + saiz->sample_info_sizes.clear(); + } + + void Fill(OriginalFormat* frma) { frma->format = FOURCC_AVC1; } + + void Modify(OriginalFormat* frma) { frma->format = FOURCC_MP4A; } + + void Fill(SchemeType* schm) { + schm->type = FOURCC_CENC; + schm->version = 12344; + } + + void Modify(SchemeType* schm) { schm->version = 123; } + + void Fill(TrackEncryption* tenc) { + tenc->is_encrypted = true; + tenc->default_iv_size = 8; + tenc->default_kid.assign(kData16Bytes, + kData16Bytes + arraysize(kData16Bytes)); + } + + void Modify(TrackEncryption* tenc) { + tenc->is_encrypted = false; + tenc->default_iv_size = 0; + } + + void Fill(SchemeInfo* schi) { Fill(&schi->track_encryption); } + + void Modify(SchemeInfo* schi) { Modify(&schi->track_encryption); } + + void Fill(ProtectionSchemeInfo* sinf) { + Fill(&sinf->format); + Fill(&sinf->type); + Fill(&sinf->info); + } + + void Modify(ProtectionSchemeInfo* sinf) { + Modify(&sinf->type); + Modify(&sinf->info); + } + + void Fill(MovieHeader* mvhd) { + mvhd->creation_time = 1234; + mvhd->modification_time = 2456; + mvhd->timescale = 48000; + mvhd->duration = 96000; + mvhd->rate = 0x010000; + mvhd->volume = 0x0100; + mvhd->next_track_id = 1; + mvhd->version = 0; + } + + void Modify(MovieHeader* mvhd) { + mvhd->duration = 234141324123; + mvhd->next_track_id = 3; + mvhd->version = 1; + } + + void Fill(TrackHeader* tkhd) { + tkhd->creation_time = 34523443; + tkhd->modification_time = 34533443; + tkhd->track_id = 2; + tkhd->duration = 96000; + tkhd->layer = 1; + tkhd->alternate_group = 2; + tkhd->volume = 0; + tkhd->width = 800; + tkhd->height = 600; + tkhd->version = 0; + } + + void Modify(TrackHeader* tkhd) { + tkhd->modification_time = 345388873443; + tkhd->volume = 0x0100; + tkhd->width = 0; + tkhd->height = 0; + tkhd->version = 1; + } + + void Fill(EditList* elst) { + elst->edits.resize(2); + elst->edits[0].segment_duration = 100; + elst->edits[0].media_time = -1; + elst->edits[0].media_rate_integer = 1; + elst->edits[0].media_rate_fraction = 0; + elst->edits[1].segment_duration = 300; + elst->edits[1].media_time = 0; + elst->edits[1].media_rate_integer = 1; + elst->edits[1].media_rate_fraction = 0; + elst->version = 0; + } + + void Modify(EditList* elst) { + elst->edits.resize(1); + elst->edits[0].segment_duration = 0; + elst->edits[0].media_time = 20364563456; + elst->version = 1; + } + + void Fill(Edit* edts) { Fill(&edts->list); } + + void Modify(Edit* edts) { Modify(&edts->list); } + + void Fill(HandlerReference* hdlr) { + hdlr->type = kSampleDescriptionTrackType; + } + + void Modify(HandlerReference* hdlr) { hdlr->type = kAudio; } + + void Fill(PixelAspectRatioBox* pasp) { + pasp->h_spacing = 5; + pasp->v_spacing = 8; + } + + void Modify(PixelAspectRatioBox* pasp) { pasp->v_spacing *= 8; } + + void Fill(AVCDecoderConfigurationRecord* avcc) { + const uint8 kAvccData[] = { + 0x01, 0x64, 0x00, 0x1f, 0xff, 0xe1, 0x00, 0x18, 0x67, 0x64, 0x00, + 0x1f, 0xac, 0xd9, 0x40, 0x50, 0x05, 0xbb, 0x01, 0x10, 0x00, 0x00, + 0x3e, 0x90, 0x00, 0x0e, 0xa6, 0x00, 0xf1, 0x83, 0x19, 0x60, 0x01, + 0x00, 0x06, 0x68, 0xeb, 0xe3, 0xcb, 0x22, 0xc0}; + BufferReader buffer_reader(kAvccData, arraysize(kAvccData)); + CHECK(avcc->ParseData(&buffer_reader)); + avcc->data.assign(kAvccData, kAvccData + arraysize(kAvccData)); + } + + void Modify(AVCDecoderConfigurationRecord* avcc) { + const uint8 kAvccData[] = { + 0x01, 0x64, 0x00, 0x1e, 0xff, 0xe1, 0x00, 0x19, 0x67, 0x64, 0x00, + 0x1e, 0xac, 0xd9, 0x40, 0xa0, 0x2f, 0xf9, 0x70, 0x11, 0x00, 0x00, + 0x03, 0x03, 0xe9, 0x00, 0x00, 0xea, 0x60, 0x0f, 0x16, 0x2d, 0x96, + 0x01, 0x00, 0x05, 0x68, 0xeb, 0xec, 0xb2, 0x2c}; + BufferReader buffer_reader(kAvccData, arraysize(kAvccData)); + CHECK(avcc->ParseData(&buffer_reader)); + avcc->data.assign(kAvccData, kAvccData + arraysize(kAvccData)); + } + + void Fill(VideoSampleEntry* encv) { + encv->format = FOURCC_ENCV; + encv->data_reference_index = 1; + encv->width = 800; + encv->height = 600; + Fill(&encv->pixel_aspect); + Fill(&encv->sinf); + Fill(&encv->avcc); + } + + void Modify(VideoSampleEntry* encv) { + encv->height += 600; + Modify(&encv->avcc); + } + + void Fill(ElementaryStreamDescriptor* esds) { + const uint8 kDecoderSpecificInfo[] = {18, 16}; + esds->es_descriptor.set_esid(1); + esds->es_descriptor.set_object_type(kISO_14496_3); + std::vector decoder_specific_info( + kDecoderSpecificInfo, + kDecoderSpecificInfo + sizeof(kDecoderSpecificInfo)); + esds->es_descriptor.set_decoder_specific_info(decoder_specific_info); + } + + void Modify(ElementaryStreamDescriptor* esds) { + esds->es_descriptor.set_esid(2); + } + + void Fill(AudioSampleEntry* enca) { + enca->format = FOURCC_ENCA; + enca->data_reference_index = 2; + enca->channelcount = 5; + enca->samplesize = 16; + enca->samplerate = 44100; + Fill(&enca->sinf); + Fill(&enca->esds); + } + + void Modify(AudioSampleEntry* enca) { enca->channelcount = 2; } + + void Fill(SampleDescription* stsd) { + stsd->type = kSampleDescriptionTrackType; + stsd->video_entries.resize(1); + Fill(&stsd->video_entries[0]); + } + + void Fill(DecodingTimeToSample* stts) { + stts->decoding_time.resize(2); + stts->decoding_time[0].sample_count = 3; + stts->decoding_time[0].sample_delta = 5; + stts->decoding_time[1].sample_count = 2; + stts->decoding_time[1].sample_delta = 9; + } + + void Modify(DecodingTimeToSample* stts) { + stts->decoding_time.resize(3); + stts->decoding_time[2].sample_count = 9; + stts->decoding_time[2].sample_delta = 4; + } + + void Fill(CompositionTimeToSample* ctts) { + ctts->composition_offset.resize(2); + ctts->composition_offset[0].sample_count = 3; + ctts->composition_offset[0].sample_offset = 5; + ctts->composition_offset[1].sample_count = 2; + ctts->composition_offset[1].sample_offset = 9; + ctts->version = 1; + } + + void Modify(CompositionTimeToSample* ctts) { + ctts->composition_offset.resize(1); + ctts->composition_offset[0].sample_count = 6; + ctts->composition_offset[0].sample_offset = 1; + ctts->version = 1; + } + + void Fill(SampleToChunk* stsc) { + stsc->chunk_info.resize(2); + stsc->chunk_info[0].first_chunk = 1; + stsc->chunk_info[0].samples_per_chunk = 5; + stsc->chunk_info[0].sample_description_index = 0; + stsc->chunk_info[1].first_chunk = 5; + stsc->chunk_info[1].samples_per_chunk = 2; + stsc->chunk_info[1].sample_description_index = 1; + } + + void Modify(SampleToChunk* stsc) { + stsc->chunk_info.resize(4); + stsc->chunk_info[2].first_chunk = 7; + stsc->chunk_info[2].samples_per_chunk = 8; + stsc->chunk_info[2].sample_description_index = 1; + stsc->chunk_info[3].first_chunk = 9; + stsc->chunk_info[3].samples_per_chunk = 12; + stsc->chunk_info[3].sample_description_index = 0; + } + + void Fill(SampleSize* stsz) { + stsz->sample_size = 0; + stsz->sizes.assign(kData8, kData8 + arraysize(kData8)); + stsz->sample_count = arraysize(kData8); + } + + void Modify(SampleSize* stsz) { + stsz->sample_size = 35; + stsz->sizes.clear(); + } + + void Fill(CompactSampleSize* stz2) { + stz2->field_size = 4; + stz2->sizes.assign(kData4, kData4 + arraysize(kData4)); + } + + void Modify(CompactSampleSize* stz2) { + stz2->field_size = 8; + stz2->sizes.assign(kData8, kData8 + arraysize(kData8)); + } + + void Fill(ChunkLargeOffset* co64) { + co64->offsets.assign(kData64, kData64 + arraysize(kData64)); + } + + void Modify(ChunkLargeOffset* co64) { co64->offsets.pop_back(); } + + void Fill(ChunkOffset* stco) { + stco->offsets.assign(kData32, kData32 + arraysize(kData32)); + } + + void Modify(ChunkOffset* stco) { stco->offsets.push_back(10); } + + void Fill(SyncSample* stss) { + stss->sample_number.assign(kData32, kData32 + arraysize(kData32)); + } + + void Modify(SyncSample* stss) { stss->sample_number.pop_back(); } + + void Fill(SampleTable* stbl) { + Fill(&stbl->description); + Fill(&stbl->decoding_time_to_sample); + Fill(&stbl->composition_time_to_sample); + Fill(&stbl->sample_to_chunk); + Fill(&stbl->sample_size); + Fill(&stbl->chunk_large_offset); + Fill(&stbl->sync_sample); + } + + void Modify(SampleTable* stbl) { + Modify(&stbl->chunk_large_offset); + Modify(&stbl->sync_sample); + } + + void Fill(MediaHeader* mdhd) { + mdhd->creation_time = 124231432; + mdhd->modification_time = static_cast(kuint32max) + 1; + mdhd->timescale = 50000; + mdhd->duration = 250000; + strcpy(mdhd->language, "abc"); + mdhd->version = 1; + } + + void Modify(MediaHeader* mdhd) { + mdhd->creation_time = 2; + mdhd->modification_time = kuint32max; + strcpy(mdhd->language, "und"); + mdhd->version = 0; + } + + void Fill(VideoMediaHeader* vmhd) { + vmhd->graphicsmode = 4123; + vmhd->opcolor_red = 323; + vmhd->opcolor_green = 2135; + vmhd->opcolor_blue = 2387; + } + + void Modify(VideoMediaHeader* vmhd) { vmhd->graphicsmode *= 2; } + + void Fill(SoundMediaHeader* smhd) { smhd->balance = 8762; } + + void Modify(SoundMediaHeader* smhd) { smhd->balance /= 2; } + + void Fill(DataEntryUrl* url) { + url->flags = 2; + url->location.assign(kData8, kData8 + arraysize(kData8)); + } + + void Modify(DataEntryUrl* url) { + url->flags += 1; + url->location.assign(kData4, kData4 + arraysize(kData4)); + } + + void Fill(DataReference* dref) { + dref->data_entry.resize(2); + Fill(&dref->data_entry[0]); + Fill(&dref->data_entry[1]); + dref->data_entry[1].location.assign(kData4, kData4 + arraysize(kData4)); + } + + void Modify(DataReference* dref) { + dref->data_entry.resize(3); + Fill(&dref->data_entry[2]); + dref->data_entry[2].location.push_back(100); + } + + void Fill(DataInformation* dinf) { Fill(&dinf->dref); } + + void Modify(DataInformation* dinf) { Modify(&dinf->dref); } + + void Fill(MediaInformation* minf) { + Fill(&minf->dinf); + Fill(&minf->sample_table); + Fill(&minf->vmhd); + } + + void Modify(MediaInformation* minf) { + Modify(&minf->dinf); + Modify(&minf->sample_table); + } + + void Fill(Media* mdia) { + Fill(&mdia->header); + Fill(&mdia->handler); + Fill(&mdia->information); + } + + void Modify(Media* mdia) { Modify(&mdia->information); } + + void Fill(Track* trak) { + Fill(&trak->header); + Fill(&trak->media); + Fill(&trak->edit); + } + + void Modify(Track* trak) { Modify(&trak->media); } + + void Fill(MovieExtendsHeader* mehd) { + mehd->fragment_duration = 23489038090; + mehd->version = 1; + } + + void Modify(MovieExtendsHeader* mehd) { + mehd->fragment_duration = 123456; + mehd->version = 0; + } + + void Fill(TrackExtends* trex) { + trex->track_id = 2; + trex->default_sample_description_index = 3; + trex->default_sample_duration = 832; + trex->default_sample_size = 89723; + trex->default_sample_flags = 12; + } + + void Modify(TrackExtends* trex) { trex->default_sample_size = 543; } + + void Fill(MovieExtends* mvex) { + Fill(&mvex->header); + mvex->tracks.resize(2); + Fill(&mvex->tracks[0]); + mvex->tracks[1].track_id = 1; + mvex->tracks[1].default_sample_description_index = 13; + mvex->tracks[1].default_sample_duration = 97687; + mvex->tracks[1].default_sample_size = 1232; + mvex->tracks[1].default_sample_flags = 6; + } + + void Modify(MovieExtends* mvex) { mvex->tracks.resize(1); } + + void Fill(Movie* moov) { + Fill(&moov->header); + Fill(&moov->extends); + moov->tracks.resize(2); + Fill(&moov->tracks[0]); + Fill(&moov->tracks[1]); + } + + void Modify(Movie* moov) { moov->tracks.resize(1); } + + void Fill(TrackFragmentDecodeTime* tfdt) { + tfdt->decode_time = 234029673820; + tfdt->version = 1; + } + + void Modify(TrackFragmentDecodeTime* tfdt) { + tfdt->decode_time = 4567; + tfdt->version = 0; + } + + void Fill(MovieFragmentHeader* mfhd) { mfhd->sequence_number = 23235; } + + void Modify(MovieFragmentHeader* mfhd) { mfhd->sequence_number = 67890; } + + void Fill(TrackFragmentHeader* tfhd) { + tfhd->flags = TrackFragmentHeader::kSampleDescriptionIndexPresentMask | + TrackFragmentHeader::kDefaultSampleDurationPresentMask | + TrackFragmentHeader::kDefaultSampleSizePresentMask | + TrackFragmentHeader::kDefaultSampleFlagsPresentMask; + tfhd->track_id = 1; + tfhd->sample_description_index = 233; + tfhd->default_sample_duration = 42545; + tfhd->default_sample_size = 8765; + tfhd->default_sample_flags = 65; + } + + void Modify(TrackFragmentHeader* tfhd) { tfhd->default_sample_size = 888; } + + void Fill(TrackFragmentRun* trun) { + trun->flags = TrackFragmentRun::kDataOffsetPresentMask | + TrackFragmentRun::kSampleDurationPresentMask | + TrackFragmentRun::kSampleSizePresentMask | + TrackFragmentRun::kSampleFlagsPresentMask | + TrackFragmentRun::kSampleCompTimeOffsetsPresentMask; + trun->data_offset = 783246; + trun->sample_count = arraysize(kData32); + trun->sample_flags.assign(kData32, kData32 + arraysize(kData32)); + trun->sample_sizes = trun->sample_flags; + trun->sample_sizes[0] += 1000; + trun->sample_durations = trun->sample_flags; + trun->sample_durations[1] += 2343; + trun->sample_composition_time_offsets.assign(kData32, + kData32 + arraysize(kData32)); + trun->sample_composition_time_offsets[2] -= 89782; + trun->version = 1; + } + + void Modify(TrackFragmentRun* trun) { + trun->flags |= TrackFragmentRun::kFirstSampleFlagsPresentMask; + trun->flags &= ~TrackFragmentRun::kSampleFlagsPresentMask; + trun->sample_flags.resize(1); + trun->version = 1; + } + + void Fill(TrackFragment* traf) { + Fill(&traf->header); + traf->runs.resize(1); + Fill(&traf->runs[0]); + Fill(&traf->decode_time); + Fill(&traf->auxiliary_offset); + Fill(&traf->auxiliary_size); + } + + void Modify(TrackFragment* traf) { + Modify(&traf->header); + Modify(&traf->decode_time); + } + + void Fill(MovieFragment* moof) { + Fill(&moof->header); + moof->tracks.resize(1); + Fill(&moof->tracks[0]); + } + + void Modify(MovieFragment* moof) { + moof->tracks.resize(2); + Fill(&moof->tracks[1]); + Modify(&moof->tracks[1]); + } + + void Fill(SegmentIndex* sidx) { + sidx->reference_id = 3; + sidx->timescale = 56700; + sidx->earliest_presentation_time = 234; + sidx->first_offset = 876223; + sidx->references.resize(2); + sidx->references[0].reference_type = true; + sidx->references[0].referenced_size = 23424; + sidx->references[0].subsegment_duration = 9083423; + sidx->references[0].starts_with_sap = true; + sidx->references[0].sap_type = SegmentReference::Type1; + sidx->references[0].sap_delta_time = 2382; + sidx->references[1].reference_type = false; + sidx->references[1].referenced_size = 34572; + sidx->references[1].subsegment_duration = 7234323; + sidx->references[1].starts_with_sap = false; + sidx->references[1].sap_type = SegmentReference::Type5; + sidx->references[1].sap_delta_time = 53; + sidx->version = 0; + } + + void Modify(SegmentIndex* sidx) { + sidx->earliest_presentation_time = 2348677865434; + sidx->references.resize(3); + sidx->references[2] = sidx->references[1]; + sidx->references[2].subsegment_duration = 87662; + 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; } + + protected: + scoped_ptr buffer_; +}; + +typedef testing::Types< + FileType, + SegmentType, + // ProtectionSystemSpecificHeader, // Not fully parsed. + SampleAuxiliaryInformationOffset, + SampleAuxiliaryInformationSize, + OriginalFormat, + SchemeType, + TrackEncryption, + SchemeInfo, + ProtectionSchemeInfo, + MovieHeader, + TrackHeader, + EditList, + Edit, + HandlerReference, + AVCDecoderConfigurationRecord, + PixelAspectRatioBox, + VideoSampleEntry, + ElementaryStreamDescriptor, + AudioSampleEntry, + SampleDescription, + DecodingTimeToSample, + CompositionTimeToSample, + SampleToChunk, + SampleSize, + CompactSampleSize, + ChunkLargeOffset, + ChunkOffset, + SyncSample, + SampleTable, + MediaHeader, + VideoMediaHeader, + SoundMediaHeader, + DataEntryUrl, + DataReference, + DataInformation, + MediaInformation, + Media, + Track, + MovieExtendsHeader, + TrackExtends, + MovieExtends, + Movie, + TrackFragmentDecodeTime, + MovieFragmentHeader, + TrackFragmentHeader, + TrackFragmentRun, + TrackFragment, + MovieFragment, + SegmentIndex> Boxes; + +TYPED_TEST_CASE(BoxDefinitionsTestGeneral, Boxes); + +TYPED_TEST(BoxDefinitionsTestGeneral, WriteReadbackCompare) { + TypeParam box; + LOG(INFO) << "Processing " << FourCCToString(box.BoxType()); + Fill(&box); + box.Write(this->buffer_.get()); + + TypeParam box_readback; + ASSERT_TRUE(ReadBack(&box_readback)); + ASSERT_EQ(box, box_readback); +} + +TYPED_TEST(BoxDefinitionsTestGeneral, WriteModifyWrite) { + TypeParam box; + LOG(INFO) << "Processing " << FourCCToString(box.BoxType()); + Fill(&box); + // Save the expected version set earlier in function |Fill|, then clear + // the version, expecting box.Write set version as expected. + uint8 version = GetAndClearVersion(&box); + box.Write(this->buffer_.get()); + EXPECT_EQ(version, GetAndClearVersion(&box)); + + this->buffer_->Clear(); + Modify(&box); + version = GetAndClearVersion(&box); + box.Write(this->buffer_.get()); + EXPECT_EQ(version, GetAndClearVersion(&box)); + + TypeParam box_readback; + ASSERT_TRUE(ReadBack(&box_readback)); + ASSERT_EQ(box, box_readback); +} + +TYPED_TEST(BoxDefinitionsTestGeneral, Empty) { + TypeParam box; + LOG(INFO) << "Processing " << FourCCToString(box.BoxType()); + if (IsOptional(&box)) { + ASSERT_EQ(0, box.ComputeSize()); + } else { + ASSERT_NE(0, box.ComputeSize()); + } +} + +// Test other cases of box input. +class BoxDefinitionsTest : public BoxDefinitionsTestGeneral {}; + +TEST_F(BoxDefinitionsTest, ProtectionSystemSpecificHeader) { + ProtectionSystemSpecificHeader pssh; + Fill(&pssh); + pssh.Write(this->buffer_.get()); + + ProtectionSystemSpecificHeader pssh_readback; + ASSERT_TRUE(ReadBack(&pssh_readback)); + // PSSH does not parse data. + ASSERT_EQ(pssh.system_id, pssh_readback.system_id); +} + +TEST_F(BoxDefinitionsTest, CompactSampleSize_FieldSize16) { + CompactSampleSize stz2; + stz2.field_size = 16; + stz2.sizes.assign(kData16, kData16 + arraysize(kData16)); + stz2.Write(this->buffer_.get()); + + CompactSampleSize stz2_readback; + ASSERT_TRUE(ReadBack(&stz2_readback)); + ASSERT_EQ(stz2, stz2_readback); +} + +TEST_F(BoxDefinitionsTest, ChunkLargeOffsetSmallOffset) { + ChunkLargeOffset co64; + co64.offsets.assign(kData32, kData32 + arraysize(kData32)); + co64.Write(this->buffer_.get()); + + // The data is stored in ChunkOffset box instead. + ChunkOffset stco; + ASSERT_TRUE(ReadBack(&stco)); + ASSERT_EQ(co64, stco); +} + +TEST_F(BoxDefinitionsTest, TrackFragmentHeader_NoSampleSize) { + TrackFragmentHeader tfhd; + Fill(&tfhd); + tfhd.flags &= ~TrackFragmentHeader::kDefaultSampleSizePresentMask; + tfhd.Write(this->buffer_.get()); + + TrackFragmentHeader tfhd_readback; + ASSERT_TRUE(ReadBack(&tfhd_readback)); + EXPECT_EQ(0, tfhd_readback.default_sample_size); + tfhd.default_sample_size = 0; + ASSERT_EQ(tfhd, tfhd_readback); +} + +TEST_F(BoxDefinitionsTest, TrackFragmentRun_NoSampleSize) { + TrackFragmentRun trun; + Fill(&trun); + trun.flags &= ~TrackFragmentRun::kSampleSizePresentMask; + trun.Write(this->buffer_.get()); + + TrackFragmentRun trun_readback; + ASSERT_TRUE(ReadBack(&trun_readback)); + EXPECT_TRUE(trun_readback.sample_sizes.empty()); + trun.sample_sizes.clear(); + ASSERT_EQ(trun, trun_readback); +} + +} // namespace mp4 +} // namespace media