UNPKG

videostream-mp4box

Version:

JavaScript version of GPAC's MP4Box tool

1,560 lines (1,447 loc) 45.9 kB
/* * Copyright (c) 2012-2013. Telecom ParisTech/TSI/MM/GPAC Cyril Concolato * License: BSD-3-Clause (see LICENSE file) */ var DataStream = require('./DataStream'); var MPEG4DescriptorParser = require('./descriptor'); var Log = require('./log'); var BoxParser = { ERR_NOT_ENOUGH_DATA : 0, OK : 1, boxCodes : [ "mdat", "avcC", "hvcC", "ftyp", "payl", "vmhd", "smhd", "hmhd", "dref", "elst" // full boxes not yet parsed ], fullBoxCodes : [ "mvhd", "tkhd", "mdhd", "hdlr", "smhd", "hmhd", "nhmd", "url ", "urn ", "ctts", "cslg", "stco", "co64", "stsc", "stss", "stsz", "stz2", "stts", "stsh", "mehd", "trex", "mfhd", "tfhd", "trun", "tfdt", "esds", "subs", "txtC" /* missing "stsd": special case full box and container */ ], containerBoxCodes : [ [ "moov", [ "trak" ] ], [ "trak" ], [ "edts" ], [ "mdia" ], [ "minf" ], [ "dinf" ], [ "stbl" ], [ "mvex", [ "trex" ] ], [ "moof", [ "traf" ] ], [ "traf", [ "trun" ] ], [ "vttc" ], [ "tref" ] ], sampleEntryCodes : [ /* 4CC as registered on http://mp4ra.org/codecs.html */ { prefix: "Visual", types: [ "mp4v", "avc1", "avc2", "avc3", "avc4", "avcp", "drac", "encv", "mjp2", "mvc1", "mvc2", "resv", "s263", "svc1", "vc-1", "hvc1", "hev1" ] }, { prefix: "Audio", types: [ "mp4a", "ac-3", "alac", "dra1", "dtsc", "dtse", ,"dtsh", "dtsl", "ec-3", "enca", "g719", "g726", "m4ae", "mlpa", "raw ", "samr", "sawb", "sawp", "sevc", "sqcp", "ssmv", "twos" ] }, { prefix: "Hint", types: [ "fdp ", "m2ts", "pm2t", "prtp", "rm2t", "rrtp", "rsrp", "rtp ", "sm2t", "srtp" ] }, { prefix: "Metadata", types: [ "metx", "mett", "urim" ] }, { prefix: "Subtitle", types: [ "stpp", "wvtt", "sbtt", "tx3g", "stxt" ] } ], trackReferenceTypes: [ "scal" ], initialize: function() { var i, j; var length; BoxParser.FullBox.prototype = new BoxParser.Box(); BoxParser.ContainerBox.prototype = new BoxParser.Box(); BoxParser.stsdBox.prototype = new BoxParser.FullBox(); BoxParser.SampleEntry.prototype = new BoxParser.FullBox(); BoxParser.TrackReferenceTypeBox.prototype = new BoxParser.Box(); /* creating constructors for simple boxes */ length = BoxParser.boxCodes.length; for (i=0; i<length; i++) { BoxParser[BoxParser.boxCodes[i]+"Box"] = (function (j) { /* creating a closure around the iterating value of i */ return function(size) { BoxParser.Box.call(this, BoxParser.boxCodes[j], size); } })(i); BoxParser[BoxParser.boxCodes[i]+"Box"].prototype = new BoxParser.Box(); } /* creating constructors for full boxes */ length = BoxParser.fullBoxCodes.length; for (i=0; i<length; i++) { BoxParser[BoxParser.fullBoxCodes[i]+"Box"] = (function (j) { return function(size) { BoxParser.FullBox.call(this, BoxParser.fullBoxCodes[j], size); } })(i); BoxParser[BoxParser.fullBoxCodes[i]+"Box"].prototype = new BoxParser.FullBox(); } /* creating constructors for container boxes */ length = BoxParser.containerBoxCodes.length; for (i=0; i<length; i++) { BoxParser[BoxParser.containerBoxCodes[i][0]+"Box"] = (function (j, subBoxNames) { return function(size) { BoxParser.ContainerBox.call(this, BoxParser.containerBoxCodes[j][0], size); if (subBoxNames) { this.subBoxNames = subBoxNames; var nbSubBoxes = subBoxNames.length; for (var k = 0; k<nbSubBoxes; k++) { this[subBoxNames[k]+"s"] = []; } } } })(i, BoxParser.containerBoxCodes[i][1]); BoxParser[BoxParser.containerBoxCodes[i][0]+"Box"].prototype = new BoxParser.ContainerBox(); } /* creating constructors for stsd entries */ length = BoxParser.sampleEntryCodes.length; for (j = 0; j < length; j++) { var prefix = BoxParser.sampleEntryCodes[j].prefix; var types = BoxParser.sampleEntryCodes[j].types; var nb_types = types.length; BoxParser[prefix+"SampleEntry"] = function(type, size) { BoxParser.SampleEntry.call(this, type, size); }; BoxParser[prefix+"SampleEntry"].prototype = new BoxParser.SampleEntry(); for (i=0; i<nb_types; i++) { BoxParser[types[i]+"Box"] = (function (k, l) { return function(size) { BoxParser[BoxParser.sampleEntryCodes[k].prefix+"SampleEntry"].call(this, BoxParser.sampleEntryCodes[k].types[l], size); } })(j, i); BoxParser[types[i]+"Box"].prototype = new BoxParser[prefix+"SampleEntry"](); } } /* creating constructors for track reference type boxes */ length = BoxParser.trackReferenceTypes.length; for (i=0; i<length; i++) { BoxParser[BoxParser.trackReferenceTypes[i]+"Box"] = (function (j) { return function(size) { BoxParser.TrackReferenceTypeBox.call(this, BoxParser.trackReferenceTypes[j], size); } })(i); BoxParser[BoxParser.trackReferenceTypes[i]+"Box"].prototype = new BoxParser.Box(); } }, Box: function(_type, _size) { this.type = _type; this.size = _size; }, FullBox: function(type, size) { BoxParser.Box.call(this, type, size); this.flags = 0; this.version = 0; }, ContainerBox: function(type, size) { BoxParser.Box.call(this, type, size); this.boxes = []; }, SampleEntry: function(type, size) { BoxParser.Box.call(this, type, size); this.boxes = []; }, TrackReferenceTypeBox: function(type, size) { BoxParser.Box.call(this, type, size); this.track_ids = []; }, stsdBox: function(size) { BoxParser.FullBox.call(this, "stsd", size); this.entries = []; }, parseOneBox: function(stream, isSampleEntry) { var box; var start = stream.position; var hdr_size = 0; if (stream.byteLength - stream.position < 8) { Log.d("BoxParser", "Not enough data in stream to parse the type and size of the box"); return { code: BoxParser.ERR_NOT_ENOUGH_DATA }; } var size = stream.readUint32(); var type = stream.readString(4); Log.d("BoxParser", "Found box of type "+type+" and size "+size+" at position "+start+" in the current buffer ("+(stream.buffer.fileStart+start)+" in the file)"); hdr_size = 8; // TODO: fix this properly. For now, consider the uuid as part of the body and not the header, which will cause the box to be written back out correctly // if (type == "uuid") { // uuid = stream.readString(16); // hdr_size += 16; // } if (size == 1) { if (stream.byteLength - stream.position < 8) { stream.seek(start); Log.w("BoxParser", "Not enough data in stream to parse the extended size of the \""+type+"\" box"); return { code: BoxParser.ERR_NOT_ENOUGH_DATA }; } size = stream.readUint64(); hdr_size += 8; } else if (size === 0) { /* box extends till the end of file */ throw "Unlimited box size not supported"; } if (start + size > stream.byteLength ) { stream.seek(start); Log.w("BoxParser", "Not enough data in stream to parse the entire \""+type+"\" box"); return { code: BoxParser.ERR_NOT_ENOUGH_DATA, type: type, size: size, hdr_size: hdr_size }; } if (BoxParser[type+"Box"]) { box = new BoxParser[type+"Box"](size - hdr_size); } else { if (isSampleEntry) { box = new BoxParser.SampleEntry(type, size - hdr_size); } else { box = new BoxParser.Box(type, size - hdr_size); } } /* recording the position of the box in the input stream */ box.hdr_size = hdr_size; box.start = start; box.fileStart = start + stream.buffer.fileStart; box.parse(stream); stream.seek(start + size); return { code: BoxParser.OK, box: box, size: size }; }, } module.exports = BoxParser; BoxParser.initialize(); BoxParser.Box.prototype.parse = function(stream) { if (this.type != "mdat") { this.data = stream.readUint8Array(this.size); } else { stream.seek(this.start+this.size+this.hdr_size); } } BoxParser.FullBox.prototype.parseFullHeader = function (stream) { this.version = stream.readUint8(); this.flags = stream.readUint24(); this.size -= 4; } BoxParser.ContainerBox.prototype.parse = function(stream) { var ret; var box; var start; start = stream.position; while (stream.position < start+this.size) { ret = BoxParser.parseOneBox(stream); box = ret.box; /* store the box in the 'boxes' array to preserve box order (for offset) but also store box in a property for more direct access */ this.boxes.push(box); if (this.subBoxNames && this.subBoxNames.indexOf(box.type) != -1) { this[this.subBoxNames+"s"].push(box); } else { this[box.type] = box; } } } BoxParser.SampleEntry.prototype.isVideo = function() { return false; } BoxParser.SampleEntry.prototype.isAudio = function() { return false; } BoxParser.SampleEntry.prototype.isSubtitle = function() { return false; } BoxParser.SampleEntry.prototype.isMetadata = function() { return false; } BoxParser.SampleEntry.prototype.isHint = function() { return false; } BoxParser.SampleEntry.prototype.getCodec = function() { return this.type; } BoxParser.SampleEntry.prototype.getWidth = function() { return ""; } BoxParser.SampleEntry.prototype.getHeight = function() { return ""; } BoxParser.SampleEntry.prototype.getChannelCount = function() { return ""; } BoxParser.SampleEntry.prototype.getSampleRate = function() { return ""; } BoxParser.SampleEntry.prototype.getSampleSize = function() { return ""; } BoxParser.SampleEntry.prototype.parseHeader = function(stream) { this.start = stream.position; stream.readUint8Array(6); this.data_reference_index = stream.readUint16(); } BoxParser.SampleEntry.prototype.parse = function(stream) { this.parseHeader(stream); stream.seek(this.start+this.size); } BoxParser.SampleEntry.prototype.parseFooter = function(stream) { var ret; var box; while (stream.position < this.start+this.size) { ret = BoxParser.parseOneBox(stream, false); box = ret.box; this.boxes.push(box); this[box.type] = box; } } BoxParser.VisualSampleEntry.prototype.parse = function(stream) { this.parseHeader(stream); stream.readUint16(); stream.readUint16(); stream.readUint32Array(3); this.width = stream.readUint16(); this.height = stream.readUint16(); this.horizresolution = stream.readUint32(); this.vertresolution = stream.readUint32(); stream.readUint32(); this.frame_count = stream.readUint16(); this.compressorname = stream.readString(32); this.depth = stream.readUint16(); stream.readUint16(); this.parseFooter(stream); } BoxParser.VisualSampleEntry.prototype.isVideo = function() { return true; } BoxParser.VisualSampleEntry.prototype.getWidth = function() { return this.width; } BoxParser.VisualSampleEntry.prototype.getHeight = function() { return this.height; } BoxParser.AudioSampleEntry.prototype.parse = function(stream) { this.parseHeader(stream); stream.readUint32Array(2); this.channel_count = stream.readUint16(); this.samplesize = stream.readUint16(); stream.readUint16(); stream.readUint16(); this.samplerate = (stream.readUint32()/(1<<16)); this.parseFooter(stream); } BoxParser.AudioSampleEntry.prototype.isAudio = function() { return true; } BoxParser.AudioSampleEntry.prototype.getChannelCount = function() { return this.channel_count; } BoxParser.AudioSampleEntry.prototype.getSampleRate = function() { return this.samplerate; } BoxParser.AudioSampleEntry.prototype.getSampleSize = function() { return this.samplesize; } BoxParser.SubtitleSampleEntry.prototype.parse = function(stream) { this.parseHeader(stream); this.parseFooter(stream); } BoxParser.SubtitleSampleEntry.prototype.isSubtitle = function() { return true; } BoxParser.MetadataSampleEntry.prototype.parse = function(stream) { this.parseHeader(stream); this.parseFooter(stream); } BoxParser.MetadataSampleEntry.prototype.isMetadata = function() { return true; } BoxParser.TrackReferenceTypeBox.prototype.parse = function(stream) { this.track_ids = stream.readUint8Array(this.size); } BoxParser.metxBox.prototype.parse = function(stream) { this.parseHeader(stream); this.content_encoding = stream.readCString(); this.namespace = stream.readCString(); this.schema_location = stream.readCString(); this.parseFooter(stream); } BoxParser.mettBox.prototype.parse = function(stream) { this.parseHeader(stream); this.content_encoding = stream.readCString(); this.mime_format = stream.readCString(); this.parseFooter(stream); } BoxParser.sbttBox.prototype.parse = function(stream) { this.parseHeader(stream); this.content_encoding = stream.readCString(); this.mime_format = stream.readCString(); this.parseFooter(stream); } BoxParser.stxtBox.prototype.parse = function(stream) { this.parseHeader(stream); this.content_encoding = stream.readCString(); this.mime_format = stream.readCString(); this.parseFooter(stream); } BoxParser.stppBox.prototype.parse = function(stream) { this.parseHeader(stream); this.namespace = stream.readCString(); this.schema_location = stream.readCString(); this.auxiliary_mime_types = stream.readCString(); this.parseFooter(stream); } BoxParser.tx3gBox.prototype.parse = function(stream) { this.parseHeader(stream); this.displayFlags = stream.readUint32(); this.horizontal_justification = stream.readInt8(); this.vertical_justification = stream.readInt8(); this.bg_color_rgba = stream.readUint8Array(4); this.box_record = stream.readInt16Array(4); this.style_record = stream.readUint8Array(12); this.parseFooter(stream); } BoxParser.ftypBox.prototype.parse = function(stream) { this.major_brand = stream.readString(4); this.minor_version = stream.readUint32(); this.size -= 8; this.compatible_brands = []; var i = 0; while (this.size>=4) { this.compatible_brands[i] = stream.readString(4); this.size -= 4; i++; } } BoxParser.mvhdBox.prototype.parse = function(stream) { this.flags = 0; this.parseFullHeader(stream); if (this.version == 1) { this.creation_time = stream.readUint64(); this.modification_time = stream.readUint64(); this.timescale = stream.readUint32(); this.duration = stream.readUint64(); } else { this.creation_time = stream.readUint32(); this.modification_time = stream.readUint32(); this.timescale = stream.readUint32(); this.duration = stream.readUint32(); } this.rate = stream.readUint32(); this.volume = stream.readUint16()>>8; stream.readUint16(); stream.readUint32Array(2); this.matrix = stream.readUint32Array(9); stream.readUint32Array(6); this.next_track_id = stream.readUint32(); } BoxParser.TKHD_FLAG_ENABLED = 0x000001; BoxParser.TKHD_FLAG_IN_MOVIE = 0x000002; BoxParser.TKHD_FLAG_IN_PREVIEW = 0x000004; BoxParser.tkhdBox.prototype.parse = function(stream) { this.parseFullHeader(stream); if (this.version == 1) { this.creation_time = stream.readUint64(); this.modification_time = stream.readUint64(); this.track_id = stream.readUint32(); stream.readUint32(); this.duration = stream.readUint64(); } else { this.creation_time = stream.readUint32(); this.modification_time = stream.readUint32(); this.track_id = stream.readUint32(); stream.readUint32(); this.duration = stream.readUint32(); } stream.readUint32Array(2); this.layer = stream.readInt16(); this.alternate_group = stream.readInt16(); this.volume = stream.readInt16()>>8; stream.readUint16(); this.matrix = stream.readInt32Array(9); this.width = stream.readUint32(); this.height = stream.readUint32(); } BoxParser.mdhdBox.prototype.parse = function(stream) { this.parseFullHeader(stream); if (this.version == 1) { this.creation_time = stream.readUint64(); this.modification_time = stream.readUint64(); this.timescale = stream.readUint32(); this.duration = stream.readUint64(); } else { this.creation_time = stream.readUint32(); this.modification_time = stream.readUint32(); this.timescale = stream.readUint32(); this.duration = stream.readUint32(); } this.language = stream.readUint16(); var chars = []; chars[0] = (this.language>>10)&0x1F; chars[1] = (this.language>>5)&0x1F; chars[2] = (this.language)&0x1F; this.languageString = String.fromCharCode(chars[0]+0x60, chars[1]+0x60, chars[2]+0x60); stream.readUint16(); } BoxParser.hdlrBox.prototype.parse = function(stream) { this.parseFullHeader(stream); if (this.version === 0) { stream.readUint32(); this.handler = stream.readString(4); stream.readUint32Array(3); this.name = stream.readCString(); } else { this.data = stream.readUint8Array(size); } } BoxParser.stsdBox.prototype.parse = function(stream) { var ret; var entryCount; this.parseFullHeader(stream); entryCount = stream.readUint32(); for (i = 1; i <= entryCount; i++) { ret = BoxParser.parseOneBox(stream, true); this.entries.push(ret.box); } } BoxParser.avcCBox.prototype.parse = function(stream) { var i; var nb_nalus; var length; this.configurationVersion = stream.readUint8(); this.AVCProfileIndication = stream.readUint8(); this.profile_compatibility = stream.readUint8(); this.AVCLevelIndication = stream.readUint8(); this.lengthSizeMinusOne = (stream.readUint8() & 0x3); nb_nalus = (stream.readUint8() & 0x1F); this.size -= 6; this.SPS = new Array(nb_nalus); for (i = 0; i < nb_nalus; i++) { length = stream.readUint16(); this.SPS[i] = stream.readUint8Array(length); this.size -= 2+length; } nb_nalus = stream.readUint8(); this.size--; this.PPS = new Array(nb_nalus); for (i = 0; i < nb_nalus; i++) { length = stream.readUint16(); this.PPS[i] = stream.readUint8Array(length); this.size -= 2+length; } if (this.size>0) { this.ext = stream.readUint8Array(this.size); } } BoxParser.hvcCBox.prototype.parse = function(stream) { var i; var nb_nalus; var length; var tmp_byte; this.configurationVersion = stream.readUint8(); tmp_byte = stream.readUint8(); this.general_profile_space = tmp_byte >> 6; this.general_tier_flag = (tmp_byte & 0x20) >> 5; this.general_profile_idc = (tmp_byte & 0x1F); this.general_profile_compatibility = stream.readUint32(); this.general_constraint_indicator = stream.readUint8Array(6); this.general_level_idc = stream.readUint8(); this.min_spatial_segmentation_idc = stream.readUint16() & 0xFFF; this.parallelismType = (stream.readUint8() & 0x3); this.chromaFormat = (stream.readUint8() & 0x3); this.bitDepthLumaMinus8 = (stream.readUint8() & 0x7); this.bitDepthChromaMinus8 = (stream.readUint8() & 0x7); this.avgFrameRate = stream.readUint16(); tmp_byte = stream.readUint8(); this.constantFrameRate = (tmp_byte >> 6); this.numTemporalLayers = (tmp_byte & 0XD) >> 3; this.temporalIdNested = (tmp_byte & 0X4) >> 2; this.lengthSizeMinusOne = (tmp_byte & 0X3); this.nalu_arrays = []; numOfArrays = stream.readUint8(); for (i = 0; i < numOfArrays; i++) { var nalu_array = []; this.nalu_arrays.push(nalu_array); tmp_byte = stream.readUint8() nalu_array.completeness = (tmp_byte & 0x80) >> 7; nalu_array.nalu_type = tmp_byte & 0x3F; numNalus = stream.readUint16(); for (j = 0; j < numNalus; j++) { var nalu = {} nalu_array.push(nalu); length = stream.readUint16(); nalu.data = stream.readUint8Array(length); } } } function decimalToHex(d, padding) { var hex = Number(d).toString(16); padding = typeof (padding) === "undefined" || padding === null ? padding = 2 : padding; while (hex.length < padding) { hex = "0" + hex; } return hex; } BoxParser.avc1Box.prototype.getCodec = function() { var baseCodec = BoxParser.SampleEntry.prototype.getCodec.call(this); if (this.avcC) { return baseCodec+"."+decimalToHex(this.avcC.AVCProfileIndication)+ ""+decimalToHex(this.avcC.profile_compatibility)+ ""+decimalToHex(this.avcC.AVCLevelIndication); } else { return baseCodec; } } BoxParser.hvc1Box.prototype.getCodec = function() { var i; var baseCodec = BoxParser.SampleEntry.prototype.getCodec.call(this); if (this.hvcC) { baseCodec += '.'; switch (this.hvcC.general_profile_space) { case 0: baseCodec += ''; break; case 1: baseCodec += 'A'; break; case 2: baseCodec += 'B'; break; case 3: baseCodec += 'C'; break; } baseCodec += this.hvcC.general_profile_idc; baseCodec += '.'; var val = this.hvcC.general_profile_compatibility; var reversed = 0; for (i=0; i<32; i++) { reversed |= val & 1; if (i==31) break; reversed <<= 1; val >>=1; } baseCodec += decimalToHex(reversed, 0); baseCodec += '.'; if (this.hvcC.general_tier_flag === 0) { baseCodec += 'L'; } else { baseCodec += 'H'; } baseCodec += this.hvcC.general_level_idc; var hasByte = false; var constraint_string = ""; for (i = 5; i >= 0; i--) { if (this.hvcC.general_constraint_indicator[i] || hasByte) { constraint_string = "."+decimalToHex(this.hvcC.general_constraint_indicator[i], 0)+constraint_string; hasByte = true; } } baseCodec += constraint_string; } return baseCodec; } BoxParser.mp4aBox.prototype.getCodec = function() { var baseCodec = BoxParser.SampleEntry.prototype.getCodec.call(this); if (this.esds && this.esds.esd) { var oti = this.esds.esd.getOTI(); var dsi = this.esds.esd.getAudioConfig(); return baseCodec+"."+decimalToHex(oti)+(dsi ? "."+dsi: ""); } else { return baseCodec; } } BoxParser.esdsBox.prototype.parse = function(stream) { this.parseFullHeader(stream); this.data = stream.readUint8Array(this.size); this.size = 0; var esd_parser = new MPEG4DescriptorParser(); this.esd = esd_parser.parseOneDescriptor(new DataStream(this.data.buffer, 0, DataStream.BIG_ENDIAN)); } BoxParser.txtCBox.prototype.parse = function(stream) { this.parseFullHeader(stream); this.config = stream.readCString(); } BoxParser.cttsBox.prototype.parse = function(stream) { var entry_count; var i; this.parseFullHeader(stream); entry_count = stream.readUint32(); this.sample_counts = []; this.sample_offsets = []; if (this.version === 0) { for(i=0; i<entry_count; i++) { this.sample_counts.push(stream.readUint32()); /* some files are buggy and declare version=0 while using signed offsets. The likelyhood of using the most significant bit in a 32-bits time offset is very low, so using signed value here as well */ this.sample_offsets.push(stream.readInt32()); } } else if (this.version == 1) { for(i=0; i<entry_count; i++) { this.sample_counts.push(stream.readUint32()); this.sample_offsets.push(stream.readInt32()); /* signed */ } } else { this.data = stream.readUint8Array(this.size-4); } } BoxParser.cttsBox.prototype.unpack = function(samples) { var i, j, k; k = 0; for (i = 0; i < this.sample_counts.length; i++) { for (j = 0; j < this.sample_counts[i]; j++) { samples[k].pts = samples[k].dts + this.sample_offsets[i]; k++; } } } BoxParser.cslgBox.prototype.parse = function(stream) { var entry_count; this.parseFullHeader(stream); if (this.version === 0) { this.compositionToDTSShift = stream.readInt32(); /* signed */ this.leastDecodeToDisplayDelta = stream.readInt32(); /* signed */ this.greatestDecodeToDisplayDelta = stream.readInt32(); /* signed */ this.compositionStartTime = stream.readInt32(); /* signed */ this.compositionEndTime = stream.readInt32(); /* signed */ } else { this.data = stream.readUint8Array(this.size-4); } } BoxParser.sttsBox.prototype.parse = function(stream) { var entry_count; var i; this.parseFullHeader(stream); entry_count = stream.readUint32(); this.sample_counts = []; this.sample_deltas = []; if (this.version === 0) { for(i=0; i<entry_count; i++) { this.sample_counts.push(stream.readUint32()); this.sample_deltas.push(stream.readUint32()); } } else { this.data = stream.readUint8Array(this.size-4); } } BoxParser.sttsBox.prototype.unpack = function(samples) { var i, j, k; k = 0; for (i = 0; i < this.sample_counts.length; i++) { for (j = 0; j < this.sample_counts[i]; j++) { if (k === 0) { samples[k].dts = 0; } else { samples[k].dts = samples[k-1].dts + this.sample_deltas[i]; } k++; } } } BoxParser.stssBox.prototype.parse = function(stream) { var entry_count; this.parseFullHeader(stream); entry_count = stream.readUint32(); if (this.version === 0) { this.sample_numbers = stream.readUint32Array(entry_count); } else { this.data = stream.readUint8Array(this.size-4); } } BoxParser.stshBox.prototype.parse = function(stream) { var entry_count; var i; this.parseFullHeader(stream); entry_count = stream.readUint32(); this.shadowed_sample_numbers = []; this.sync_sample_numbers = []; if (this.version === 0) { for(i=0; i<entry_count; i++) { this.shadowed_sample_numbers.push(stream.readUint32()); this.sync_sample_numbers.push(stream.readUint32()); } } else { this.data = stream.readUint8Array(this.size-4); } } BoxParser.stcoBox.prototype.parse = function(stream) { var entry_count; this.parseFullHeader(stream); entry_count = stream.readUint32(); if (this.version === 0) { this.chunk_offsets = stream.readUint32Array(entry_count); } else { this.data = stream.readUint8Array(this.size-4); } } BoxParser.stcoBox.prototype.unpack = function(samples) { var i; for (i = 0; i < this.chunk_offsets.length; i++) { samples[i].offset = this.chunk_offsets[i]; } } BoxParser.co64Box.prototype.parse = function(stream) { var entry_count; var i; this.parseFullHeader(stream); entry_count = stream.readUint32(); this.chunk_offsets = []; if (this.version === 0) { for(i=0; i<entry_count; i++) { this.chunk_offsets.push(stream.readUint64()); } } else { this.data = stream.readUint8Array(this.size-4); } } BoxParser.stscBox.prototype.parse = function(stream) { var entry_count; var i; this.parseFullHeader(stream); entry_count = stream.readUint32(); this.first_chunk = []; this.samples_per_chunk = []; this.sample_description_index = []; if (this.version === 0) { for(i=0; i<entry_count; i++) { this.first_chunk.push(stream.readUint32()); this.samples_per_chunk.push(stream.readUint32()); this.sample_description_index.push(stream.readUint32()); } } else { this.data = stream.readUint8Array(this.size-4); } } BoxParser.stscBox.prototype.unpack = function(samples) { var i, j, k, l, m; l = 0; m = 0; for (i = 0; i < this.first_chunk.length; i++) { for (j = 0; j < (i+1 < this.first_chunk.length ? this.first_chunk[i+1] : Infinity); j++) { m++; for (k = 0; k < this.samples_per_chunk[i]; k++) { if (samples[l]) { samples[l].description_index = this.sample_description_index[i]; samples[l].chunk_index = m; } else { return; } l++; } } } } BoxParser.stszBox.prototype.parse = function(stream) { var i; var sample_size; var sample_count; this.parseFullHeader(stream); this.sample_sizes = []; if (this.version === 0) { sample_size = stream.readUint32(); sample_count = stream.readUint32(); if (sample_size === 0) { this.sample_sizes = stream.readUint32Array(sample_count); } else { this.sample_sizes = []; for (i = 0; i < sample_count; i++) { this.sample_sizes[i] = sample_size; } } } else { this.data = stream.readUint8Array(this.size); } } BoxParser.stszBox.prototype.unpack = function(samples) { var i; for (i = 0; i < this.sample_sizes.length; i++) { samples[i].size = this.sample_sizes[i]; } } BoxParser.mehdBox.prototype.parse = function(stream) { this.parseFullHeader(stream); if (this.version == 1) { this.fragment_duration = stream.readUint64(); } else { this.fragment_duration = stream.readUint32(); } } BoxParser.trexBox.prototype.parse = function(stream) { this.parseFullHeader(stream); this.track_id = stream.readUint32(); this.default_sample_description_index = stream.readUint32(); this.default_sample_duration = stream.readUint32(); this.default_sample_size = stream.readUint32(); this.default_sample_flags = stream.readUint32(); } BoxParser.mfhdBox.prototype.parse = function(stream) { this.parseFullHeader(stream); this.sequence_number = stream.readUint32(); } BoxParser.TFHD_FLAG_BASE_DATA_OFFSET = 0x01; BoxParser.TFHD_FLAG_SAMPLE_DESC = 0x02; BoxParser.TFHD_FLAG_SAMPLE_DUR = 0x08; BoxParser.TFHD_FLAG_SAMPLE_SIZE = 0x10; BoxParser.TFHD_FLAG_SAMPLE_FLAGS = 0x20; BoxParser.TFHD_FLAG_DUR_EMPTY = 0x10000; BoxParser.TFHD_FLAG_DEFAULT_BASE_IS_MOOF = 0x20000; BoxParser.tfhdBox.prototype.parse = function(stream) { var readBytes = 0; this.parseFullHeader(stream); this.track_id = stream.readUint32(); if (this.size > readBytes && (this.flags & BoxParser.TFHD_FLAG_BASE_DATA_OFFSET)) { this.base_data_offset = stream.readUint64(); readBytes += 8; } else { this.base_data_offset = 0; } if (this.size > readBytes && (this.flags & BoxParser.TFHD_FLAG_SAMPLE_DESC)) { this.default_sample_description_index = stream.readUint32(); readBytes += 4; } else { this.default_sample_description_index = 0; } if (this.size > readBytes && (this.flags & BoxParser.TFHD_FLAG_SAMPLE_DUR)) { this.default_sample_duration = stream.readUint32(); readBytes += 4; } else { this.default_sample_duration = 0; } if (this.size > readBytes && (this.flags & BoxParser.TFHD_FLAG_SAMPLE_SIZE)) { this.default_sample_size = stream.readUint32(); readBytes += 4; } else { this.default_sample_size = 0; } if (this.size > readBytes && (this.flags & BoxParser.TFHD_FLAG_SAMPLE_FLAGS)) { this.default_sample_flags = stream.readUint32(); readBytes += 4; } else { this.default_sample_flags = 0; } } BoxParser.TRUN_FLAGS_DATA_OFFSET = 0x01; BoxParser.TRUN_FLAGS_FIRST_FLAG = 0x04; BoxParser.TRUN_FLAGS_DURATION = 0x100; BoxParser.TRUN_FLAGS_SIZE = 0x200; BoxParser.TRUN_FLAGS_FLAGS = 0x400; BoxParser.TRUN_FLAGS_CTS_OFFSET = 0x800; BoxParser.trunBox.prototype.parse = function(stream) { var readBytes = 0; this.parseFullHeader(stream); this.sample_count = stream.readUint32(); readBytes+= 4; if (this.size > readBytes && (this.flags & BoxParser.TRUN_FLAGS_DATA_OFFSET) ) { this.data_offset = stream.readInt32(); //signed readBytes += 4; } else { this.data_offset = 0; } if (this.size > readBytes && (this.flags & BoxParser.TRUN_FLAGS_FIRST_FLAG) ) { this.first_sample_flags = stream.readUint32(); readBytes += 4; } else { this.first_sample_flags = 0; } this.sample_duration = []; this.sample_size = []; this.sample_flags = []; this.sample_composition_time_offset = []; if (this.size > readBytes) { for (var i = 0; i < this.sample_count; i++) { if (this.flags & BoxParser.TRUN_FLAGS_DURATION) { this.sample_duration[i] = stream.readUint32(); } if (this.flags & BoxParser.TRUN_FLAGS_SIZE) { this.sample_size[i] = stream.readUint32(); } if (this.flags & BoxParser.TRUN_FLAGS_FLAGS) { this.sample_flags[i] = stream.readUint32(); } if (this.flags & BoxParser.TRUN_FLAGS_CTS_OFFSET) { if (this.version === 0) { this.sample_composition_time_offset[i] = stream.readUint32(); } else { this.sample_composition_time_offset[i] = stream.readInt32(); //signed } } } } } BoxParser.tfdtBox.prototype.parse = function(stream) { this.parseFullHeader(stream); if (this.version == 1) { this.baseMediaDecodeTime = stream.readUint64(); } else { this.baseMediaDecodeTime = stream.readUint32(); } } BoxParser.paylBox.prototype.parse = function(stream) { this.text = stream.readString(this.size); } BoxParser.subsBox.prototype.parse = function(stream) { var i,j; var entry_count; var subsample_count; this.parseFullHeader(stream); entry_count = stream.readUint32(); this.samples = []; for (i = 0; i < entry_count; i++) { var sampleInfo = {}; this.samples[i] = sampleInfo; sampleInfo.sample_delta = stream.readUint32(); sampleInfo.subsamples = []; subsample_count = stream.readUint16(); if (subsample_count>0) { for (j = 0; j < subsample_count; j++) { var subsample = {}; sampleInfo.subsamples.push(subsample); if (this.version == 1) { subsample.size = stream.readUint32(); } else { subsample.size = stream.readUint16(); } subsample.priority = stream.readUint8(); subsample.discardable = stream.readUint8(); subsample.reserved = stream.readUint32(); } } } } BoxParser.Box.prototype.writeHeader = function(stream, msg) { this.size += 8; if (this.size > DataStream.MAX_SIZE) { this.size += 8; } Log.d("BoxWriter", "Writing box "+this.type+" of size: "+this.size+" at position "+stream.position+(msg || "")); if (this.size > DataStream.MAX_SIZE) { stream.writeUint32(1); } else { this.sizePosition = stream.position; stream.writeUint32(this.size); } stream.writeString(this.type, null, 4); if (this.size > DataStream.MAX_SIZE) { stream.writeUint64(this.size); } } BoxParser.FullBox.prototype.writeHeader = function(stream) { this.size += 4; BoxParser.Box.prototype.writeHeader.call(this, stream, " v="+this.version+" f="+this.flags); stream.writeUint8(this.version); stream.writeUint24(this.flags); } BoxParser.Box.prototype.write = function(stream) { if (this.type === "mdat") { /* TODO: fix this */ if (this.data) { this.size = this.data.length; this.writeHeader(stream); stream.writeUint8Array(this.data); } } else { this.size = this.data.length; this.writeHeader(stream); stream.writeUint8Array(this.data); } } BoxParser.ContainerBox.prototype.write = function(stream) { this.size = 0; this.writeHeader(stream); for (var i=0; i<this.boxes.length; i++) { if (this.boxes[i]) { this.boxes[i].write(stream); this.size += this.boxes[i].size; } } /* adjusting the size, now that all sub-boxes are known */ Log.d("BoxWriter", "Adjusting box "+this.type+" with new size "+this.size); stream.adjustUint32(this.sizePosition, this.size); } BoxParser.TrackReferenceTypeBox.prototype.write = function(stream) { this.size = this.track_ids.length*4; this.writeHeader(stream); stream.writeUint32Array(this.track_ids); } BoxParser.ftypBox.prototype.write = function(stream) { this.size = 8+4*this.compatible_brands.length; this.writeHeader(stream); stream.writeString(this.major_brand, null, 4); stream.writeUint32(this.minor_version); for (var i = 0; i < this.compatible_brands.length; i++) { stream.writeString(this.compatible_brands[i], null, 4); } } BoxParser.mvhdBox.prototype.write = function(stream) { this.version = 0; this.flags = 0; this.size = 23*4+2*2; this.writeHeader(stream); stream.writeUint32(this.creation_time); stream.writeUint32(this.modification_time); stream.writeUint32(this.timescale); stream.writeUint32(this.duration); stream.writeUint32(this.rate); stream.writeUint16(this.volume<<8); stream.writeUint16(0); stream.writeUint32(0); stream.writeUint32(0); stream.writeUint32Array(this.matrix); stream.writeUint32(0); stream.writeUint32(0); stream.writeUint32(0); stream.writeUint32(0); stream.writeUint32(0); stream.writeUint32(0); stream.writeUint32(this.next_track_id); } BoxParser.tkhdBox.prototype.write = function(stream) { this.version = 0; //this.flags = 0; this.size = 4*18+2*4; this.writeHeader(stream); stream.writeUint32(this.creation_time); stream.writeUint32(this.modification_time); stream.writeUint32(this.track_id); stream.writeUint32(0); stream.writeUint32(this.duration); stream.writeUint32(0); stream.writeUint32(0); stream.writeInt16(this.layer); stream.writeInt16(this.alternate_group); stream.writeInt16(this.volume<<8); stream.writeUint16(0); stream.writeInt32Array(this.matrix); stream.writeUint32(this.width); stream.writeUint32(this.height); } BoxParser.mdhdBox.prototype.write = function(stream) { this.size = 4*4+2*2; this.flags = 0; this.version = 0; this.writeHeader(stream); stream.writeUint32(this.creation_time); stream.writeUint32(this.modification_time); stream.writeUint32(this.timescale); stream.writeUint32(this.duration); stream.writeUint16(this.language); stream.writeUint16(0); } BoxParser.hdlrBox.prototype.write = function(stream) { this.size = 5*4+this.name.length+1; this.version = 0; this.flags = 0; this.writeHeader(stream); stream.writeUint32(0); stream.writeString(this.handler, null, 4); stream.writeUint32(0); stream.writeUint32(0); stream.writeUint32(0); stream.writeCString(this.name); } BoxParser.stsdBox.prototype.write = function(stream) { var i; this.version = 0; this.flags = 0; this.size = 0; this.writeHeader(stream); stream.writeUint32(this.entries.length); this.size += 4; for (i = 0; i < this.entries.length; i++) { this.entries[i].write(stream); this.size += this.entries[i].size; } /* adjusting the size, now that all sub-boxes are known */ Log.d("BoxWriter", "Adjusting box "+this.type+" with new size "+this.size); stream.adjustUint32(this.sizePosition, this.size); } BoxParser.SampleEntry.prototype.writeHeader = function(stream) { this.size = 8; BoxParser.Box.prototype.writeHeader.call(this, stream); stream.writeUint8(0); stream.writeUint8(0); stream.writeUint8(0); stream.writeUint8(0); stream.writeUint8(0); stream.writeUint8(0); stream.writeUint16(this.data_reference_index); } BoxParser.SampleEntry.prototype.writeFooter = function(stream) { for (var i=0; i<this.boxes.length; i++) { this.boxes[i].write(stream); this.size += this.boxes[i].size; } Log.d("BoxWriter", "Adjusting box "+this.type+" with new size "+this.size); stream.adjustUint32(this.sizePosition, this.size); } BoxParser.SampleEntry.prototype.write = function(stream) { this.writeHeader(stream); this.writeFooter(stream); } BoxParser.VisualSampleEntry.prototype.write = function(stream) { this.writeHeader(stream); this.size += 2*7+6*4+32; stream.writeUint16(0); stream.writeUint16(0); stream.writeUint32(0); stream.writeUint32(0); stream.writeUint32(0); stream.writeUint16(this.width - this.width % 2); stream.writeUint16(this.height - this.height % 2); stream.writeUint32(this.horizresolution); stream.writeUint32(this.vertresolution); stream.writeUint32(0); stream.writeUint16(this.frame_count); stream.writeString(this.compressorname, null, 32); stream.writeUint16(this.depth); stream.writeInt16(-1); this.writeFooter(stream); } BoxParser.AudioSampleEntry.prototype.write = function(stream) { this.writeHeader(stream); this.size += 2*4+3*4; stream.writeUint32(0); stream.writeUint32(0); stream.writeUint16(this.channel_count); stream.writeUint16(this.samplesize); stream.writeUint16(0); stream.writeUint16(0); stream.writeUint32(this.samplerate<<16); this.writeFooter(stream); } BoxParser.avcCBox.prototype.write = function(stream) { var i; this.size = 7; for (i = 0; i < this.SPS.length; i++) { this.size += 2+this.SPS[i].length; } for (i = 0; i < this.PPS.length; i++) { this.size += 2+this.PPS[i].length; } if (this.ext) { this.size += this.ext.length; } this.writeHeader(stream); stream.writeUint8(this.configurationVersion); stream.writeUint8(this.AVCProfileIndication); stream.writeUint8(this.profile_compatibility); stream.writeUint8(this.AVCLevelIndication); stream.writeUint8(this.lengthSizeMinusOne + (63<<2)); stream.writeUint8(this.SPS.length + (7<<5)); for (i = 0; i < this.SPS.length; i++) { stream.writeUint16(this.SPS[i].length); stream.writeUint8Array(this.SPS[i]); } stream.writeUint8(this.PPS.length); for (i = 0; i < this.PPS.length; i++) { stream.writeUint16(this.PPS[i].length); stream.writeUint8Array(this.PPS[i]); } if (this.ext) { stream.writeUint8Array(this.ext); } } BoxParser.cttsBox.prototype.write = function(stream) { var i; this.version = 1; this.flags = 0; this.size = 4+8*this.sample_counts.length; this.writeHeader(stream); stream.writeUint32(this.sample_counts.length); for(i=0; i<this.sample_counts.length; i++) { stream.writeUint32(this.sample_counts[i]); stream.writeInt32(this.sample_offsets[i]); /* signed */ } } BoxParser.cslgBox.prototype.write = function(stream) { var i; this.version = 0; this.flags = 0; this.size = 4*5; this.writeHeader(stream); stream.writeInt32(this.compositionToDTSShift); stream.writeInt32(this.leastDecodeToDisplayDelta); stream.writeInt32(this.greatestDecodeToDisplayDelta); stream.writeInt32(this.compositionStartTime); stream.writeInt32(this.compositionEndTime); } BoxParser.sttsBox.prototype.write = function(stream) { var i; this.version = 0; this.flags = 0; this.size = 4+8*this.sample_counts.length; this.writeHeader(stream); stream.writeUint32(this.sample_counts.length); for(i=0; i<this.sample_counts.length; i++) { stream.writeUint32(this.sample_counts[i]); stream.writeUint32(this.sample_deltas[i]); } } BoxParser.stssBox.prototype.write = function(stream) { this.version = 0; this.flags = 0; this.size = 4+4*this.sample_numbers.length; this.writeHeader(stream); stream.writeUint32(this.sample_numbers.length); stream.writeUint32Array(this.sample_numbers); } BoxParser.stshBox.prototype.write = function(stream) { var i; this.version = 0; this.flags = 0; this.size = 4+8*this.shadowed_sample_numbers.length; this.writeHeader(stream); stream.writeUint32(this.shadowed_sample_numbers.length); for(i=0; i<this.shadowed_sample_numbers.length; i++) { stream.writeUint32(this.shadowed_sample_numbers[i]); stream.writeUint32(this.sync_sample_numbers[i]); } } BoxParser.stcoBox.prototype.write = function(stream) { this.version = 0; this.flags = 0; this.size = 4+4*this.chunk_offsets.length; this.writeHeader(stream); stream.writeUint32(this.chunk_offsets.length); stream.writeUint32Array(this.chunk_offsets); } BoxParser.co64Box.prototype.write = function(stream) { var i; this.version = 0; this.flags = 0; this.size = 4+8*this.chunk_offsets.length; this.writeHeader(stream); stream.writeUint32(this.chunk_offsets.length); for(i=0; i<this.chunk_offsets.length; i++) { stream.writeUint64(this.chunk_offsets[i]); } } BoxParser.stscBox.prototype.write = function(stream) { var i; this.version = 0; this.flags = 0; this.size = 4+12*this.first_chunk.length; this.writeHeader(stream); stream.writeUint32(this.first_chunk.length); for(i=0; i<this.first_chunk.length; i++) { stream.writeUint32(this.first_chunk[i]); stream.writeUint32(this.samples_per_chunk[i]); stream.writeUint32(this.sample_description_index[i]); } } BoxParser.stszBox.prototype.write = function(stream) { var i; this.version = 0; this.flags = 0; this.size = 8+12*this.sample_sizes.length; this.writeHeader(stream); stream.writeUint32(0); stream.writeUint32(this.sample_sizes.length); stream.writeUint32Array(this.sample_sizes); } BoxParser.mehdBox.prototype.write = function(stream) { this.version = 0; this.flags = 0; this.size = 4; this.writeHeader(stream); stream.writeUint32(this.fragment_duration); } BoxParser.trexBox.prototype.write = function(stream) { this.version = 0; this.flags = 0; this.size = 4*5; this.writeHeader(stream); stream.writeUint32(this.track_id); stream.writeUint32(this.default_sample_description_index); stream.writeUint32(this.default_sample_duration); stream.writeUint32(this.default_sample_size); stream.writeUint32(this.default_sample_flags); } BoxParser.mfhdBox.prototype.write = function(stream) { this.version = 0; this.flags = 0; this.size = 4; this.writeHeader(stream); stream.writeUint32(this.sequence_number); } BoxParser.tfhdBox.prototype.write = function(stream) { this.version = 0; this.size = 4; if (this.flags & BoxParser.TFHD_FLAG_BASE_OFFSET) { this.size += 8; } if (this.flags & BoxParser.TFHD_FLAG_SAMPLE_DESC) { this.size += 4; } if (this.flags & BoxParser.TFHD_FLAG_SAMPLE_DUR) { this.size += 4; } if (this.flags & BoxParser.TFHD_FLAG_SAMPLE_SIZE) { this.size += 4; } if (this.flags & BoxParser.TFHD_FLAG_SAMPLE_FLAGS) { this.size += 4; } this.writeHeader(stream); stream.writeUint32(this.track_id); if (this.flags & BoxParser.TFHD_FLAG_BASE_OFFSET) { stream.writeUint64(this.base_data_offset); } if (this.flags & BoxParser.TFHD_FLAG_SAMPLE_DESC) { stream.writeUint32(this.default_sample_description_index); } if (this.flags & BoxParser.TFHD_FLAG_SAMPLE_DUR) { stream.writeUint32(this.default_sample_duration); } if (this.flags & BoxParser.TFHD_FLAG_SAMPLE_SIZE) { stream.writeUint32(this.default_sample_size); } if (this.flags & BoxParser.TFHD_FLAG_SAMPLE_FLAGS) { stream.writeUint32(this.default_sample_flags); } } BoxParser.trunBox.prototype.write = function(stream) { this.version = 0; this.size = 4; if (this.flags & BoxParser.TRUN_FLAGS_DATA_OFFSET) { this.size += 4; } if (this.flags & BoxParser.TRUN_FLAGS_FIRST_FLAG) { this.size += 4; } if (this.flags & BoxParser.TRUN_FLAGS_DURATION) { this.size += 4*this.sample_duration.length; } if (this.flags & BoxParser.TRUN_FLAGS_SIZE) { this.size += 4*this.sample_size.length; } if (this.flags & BoxParser.TRUN_FLAGS_FLAGS) { this.size += 4*this.sample_flags.length; } if (this.flags & BoxParser.TRUN_FLAGS_CTS_OFFSET) { this.size += 4*this.sample_composition_time_offset.length; } this.writeHeader(stream); stream.writeUint32(this.sample_count); if (this.flags & BoxParser.TRUN_FLAGS_DATA_OFFSET) { this.data_offset_position = stream.position; stream.writeInt32(this.data_offset); //signed } if (this.flags & BoxParser.TRUN_FLAGS_FIRST_FLAG) { stream.writeUint32(this.first_sample_flags); } for (var i = 0; i < this.sample_count; i++) { if (this.flags & BoxParser.TRUN_FLAGS_DURATION) { stream.writeUint32(this.sample_duration[i]); } if (this.flags & BoxParser.TRUN_FLAGS_SIZE) { stream.writeUint32(this.sample_size[i]); } if (this.flags & BoxParser.TRUN_FLAGS_FLAGS) { stream.writeUint32(this.sample_flags[i]); } if (this.flags & BoxParser.TRUN_FLAGS_CTS_OFFSET) { if (this.version === 0) { stream.writeUint32(this.sample_composition_time_offset[i]); } else { stream.writeInt32(this.sample_composition_time_offset[i]); //signed } } } } BoxParser.tfdtBox.prototype.write = function(stream) { this.version = 0; this.flags = 0; this.size = 4; this.writeHeader(stream); if (this.version == 1) { stream.writeUint64(this.baseMediaDecodeTime); } else { stream.writeUint32(this.baseMediaDecodeTime); } }