mp4box
Version:
JavaScript version of GPAC's MP4Box tool
618 lines (580 loc) • 22.3 kB
JavaScript
/* Index of the last moof box received */
ISOFile.prototype.lastMoofIndex = 0;
/* size of the buffers allocated for samples */
ISOFile.prototype.samplesDataSize = 0;
/* Resets all sample tables */
ISOFile.prototype.resetTables = function () {
var i;
var trak, stco, stsc, stsz, stts, ctts, stss;
this.initial_duration = this.moov.mvhd.duration;
this.moov.mvhd.duration = 0;
for (i = 0; i < this.moov.traks.length; i++) {
trak = this.moov.traks[i];
trak.tkhd.duration = 0;
trak.mdia.mdhd.duration = 0;
stco = trak.mdia.minf.stbl.stco || trak.mdia.minf.stbl.co64;
stco.chunk_offsets = [];
stsc = trak.mdia.minf.stbl.stsc;
stsc.first_chunk = [];
stsc.samples_per_chunk = [];
stsc.sample_description_index = [];
stsz = trak.mdia.minf.stbl.stsz || trak.mdia.minf.stbl.stz2;
stsz.sample_sizes = [];
stts = trak.mdia.minf.stbl.stts;
stts.sample_counts = [];
stts.sample_deltas = [];
ctts = trak.mdia.minf.stbl.ctts;
if (ctts) {
ctts.sample_counts = [];
ctts.sample_offsets = [];
}
stss = trak.mdia.minf.stbl.stss;
var k = trak.mdia.minf.stbl.boxes.indexOf(stss);
if (k != -1) trak.mdia.minf.stbl.boxes[k] = null;
}
}
ISOFile.initSampleGroups = function(trak, traf, sbgps, trak_sgpds, traf_sgpds) {
var l;
var k;
var sample_groups_info;
var sample_group_info;
var sample_group_key;
function SampleGroupInfo(_type, _parameter, _sbgp) {
this.grouping_type = _type;
this.grouping_type_parameter = _parameter;
this.sbgp = _sbgp;
this.last_sample_in_run = -1;
this.entry_index = -1;
}
if (traf) {
traf.sample_groups_info = [];
}
if (!trak.sample_groups_info) {
trak.sample_groups_info = [];
}
for (k = 0; k < sbgps.length; k++) {
sample_group_key = sbgps[k].grouping_type +"/"+ sbgps[k].grouping_type_parameter;
sample_group_info = new SampleGroupInfo(sbgps[k].grouping_type, sbgps[k].grouping_type_parameter, sbgps[k]);
if (traf) {
traf.sample_groups_info[sample_group_key] = sample_group_info;
}
if (!trak.sample_groups_info[sample_group_key]) {
trak.sample_groups_info[sample_group_key] = sample_group_info;
}
for (l=0; l <trak_sgpds.length; l++) {
if (trak_sgpds[l].grouping_type === sbgps[k].grouping_type) {
sample_group_info.description = trak_sgpds[l];
sample_group_info.description.used = true;
}
}
if (traf_sgpds) {
for (l=0; l <traf_sgpds.length; l++) {
if (traf_sgpds[l].grouping_type === sbgps[k].grouping_type) {
sample_group_info.fragment_description = traf_sgpds[l];
sample_group_info.fragment_description.used = true;
sample_group_info.is_fragment = true;
}
}
}
}
if (!traf) {
for (k = 0; k < trak_sgpds.length; k++) {
if (!trak_sgpds[k].used && trak_sgpds[k].version >= 2) {
sample_group_key = trak_sgpds[k].grouping_type +"/0";
sample_group_info = new SampleGroupInfo(trak_sgpds[k].grouping_type, 0);
if (!trak.sample_groups_info[sample_group_key]) {
trak.sample_groups_info[sample_group_key] = sample_group_info;
}
}
}
} else {
if (traf_sgpds) {
for (k = 0; k < traf_sgpds.length; k++) {
if (!traf_sgpds[k].used && traf_sgpds[k].version >= 2) {
sample_group_key = traf_sgpds[k].grouping_type +"/0";
sample_group_info = new SampleGroupInfo(traf_sgpds[k].grouping_type, 0);
sample_group_info.is_fragment = true;
if (!traf.sample_groups_info[sample_group_key]) {
traf.sample_groups_info[sample_group_key] = sample_group_info;
}
}
}
}
}
}
ISOFile.setSampleGroupProperties = function(trak, sample, sample_number, sample_groups_info) {
var k;
var index;
sample.sample_groups = [];
for (k in sample_groups_info) {
sample.sample_groups[k] = {};
sample.sample_groups[k].grouping_type = sample_groups_info[k].grouping_type;
sample.sample_groups[k].grouping_type_parameter = sample_groups_info[k].grouping_type_parameter;
if (sample_number >= sample_groups_info[k].last_sample_in_run) {
if (sample_groups_info[k].last_sample_in_run < 0) {
sample_groups_info[k].last_sample_in_run = 0;
}
sample_groups_info[k].entry_index++;
if (sample_groups_info[k].entry_index <= sample_groups_info[k].sbgp.entries.length - 1) {
sample_groups_info[k].last_sample_in_run += sample_groups_info[k].sbgp.entries[sample_groups_info[k].entry_index].sample_count;
}
}
if (sample_groups_info[k].entry_index <= sample_groups_info[k].sbgp.entries.length - 1) {
sample.sample_groups[k].group_description_index = sample_groups_info[k].sbgp.entries[sample_groups_info[k].entry_index].group_description_index;
} else {
sample.sample_groups[k].group_description_index = -1; // special value for not defined
}
if (sample.sample_groups[k].group_description_index !== 0) {
var description;
if (sample_groups_info[k].fragment_description) {
description = sample_groups_info[k].fragment_description;
} else {
description = sample_groups_info[k].description;
}
if (sample.sample_groups[k].group_description_index > 0) {
if (sample.sample_groups[k].group_description_index > 65535) {
index = (sample.sample_groups[k].group_description_index >> 16)-1;
} else {
index = sample.sample_groups[k].group_description_index-1;
}
if (description && index >= 0) {
sample.sample_groups[k].description = description.entries[index];
}
} else {
if (description && description.version >= 2) {
if (description.default_group_description_index > 0) {
sample.sample_groups[k].description = description.entries[description.default_group_description_index-1];
}
}
}
}
}
}
ISOFile.process_sdtp = function (sdtp, sample, number) {
if (!sample) {
return;
}
if (sdtp) {
sample.is_leading = sdtp.is_leading[number];
sample.depends_on = sdtp.sample_depends_on[number];
sample.is_depended_on = sdtp.sample_is_depended_on[number];
sample.has_redundancy = sdtp.sample_has_redundancy[number];
} else {
sample.is_leading = 0;
sample.depends_on = 0;
sample.is_depended_on = 0
sample.has_redundancy = 0;
}
}
/* Build initial sample list from sample tables */
ISOFile.prototype.buildSampleLists = function() {
var i;
var trak;
for (i = 0; i < this.moov.traks.length; i++) {
trak = this.moov.traks[i];
this.buildTrakSampleLists(trak);
}
}
ISOFile.prototype.buildTrakSampleLists = function(trak) {
var j, k;
var stco, stsc, stsz, stts, ctts, stss, stsd, subs, sbgps, sgpds, stdp;
var chunk_run_index, chunk_index, last_chunk_in_run, offset_in_chunk, last_sample_in_chunk;
var last_sample_in_stts_run, stts_run_index, last_sample_in_ctts_run, ctts_run_index, last_stss_index, last_subs_index, subs_entry_index, last_subs_sample_index;
trak.samples = [];
trak.samples_duration = 0;
trak.samples_size = 0;
stco = trak.mdia.minf.stbl.stco || trak.mdia.minf.stbl.co64;
stsc = trak.mdia.minf.stbl.stsc;
stsz = trak.mdia.minf.stbl.stsz || trak.mdia.minf.stbl.stz2;
stts = trak.mdia.minf.stbl.stts;
ctts = trak.mdia.minf.stbl.ctts;
stss = trak.mdia.minf.stbl.stss;
stsd = trak.mdia.minf.stbl.stsd;
subs = trak.mdia.minf.stbl.subs;
stdp = trak.mdia.minf.stbl.stdp;
sbgps = trak.mdia.minf.stbl.sbgps;
sgpds = trak.mdia.minf.stbl.sgpds;
last_sample_in_stts_run = -1;
stts_run_index = -1;
last_sample_in_ctts_run = -1;
ctts_run_index = -1;
last_stss_index = 0;
subs_entry_index = 0;
last_subs_sample_index = 0;
ISOFile.initSampleGroups(trak, null, sbgps, sgpds);
if (typeof stsz === "undefined") {
return;
}
/* we build the samples one by one and compute their properties */
for (j = 0; j < stsz.sample_sizes.length; j++) {
var sample = {};
sample.number = j;
sample.track_id = trak.tkhd.track_id;
sample.timescale = trak.mdia.mdhd.timescale;
sample.alreadyRead = 0;
trak.samples[j] = sample;
/* size can be known directly */
sample.size = stsz.sample_sizes[j];
trak.samples_size += sample.size;
/* computing chunk-based properties (offset, sample description index)*/
if (j === 0) {
chunk_index = 1; /* the first sample is in the first chunk (chunk indexes are 1-based) */
chunk_run_index = 0; /* the first chunk is the first entry in the first_chunk table */
sample.chunk_index = chunk_index;
sample.chunk_run_index = chunk_run_index;
last_sample_in_chunk = stsc.samples_per_chunk[chunk_run_index];
offset_in_chunk = 0;
/* Is there another entry in the first_chunk table ? */
if (chunk_run_index + 1 < stsc.first_chunk.length) {
/* The last chunk in the run is the chunk before the next first chunk */
last_chunk_in_run = stsc.first_chunk[chunk_run_index+1]-1;
} else {
/* There is only one entry in the table, it is valid for all future chunks*/
last_chunk_in_run = Infinity;
}
} else {
if (j < last_sample_in_chunk) {
/* the sample is still in the current chunk */
sample.chunk_index = chunk_index;
sample.chunk_run_index = chunk_run_index;
} else {
/* the sample is in the next chunk */
chunk_index++;
sample.chunk_index = chunk_index;
/* reset the accumulated offset in the chunk */
offset_in_chunk = 0;
if (chunk_index <= last_chunk_in_run) {
/* stay in the same entry of the first_chunk table */
/* chunk_run_index unmodified */
} else {
chunk_run_index++;
/* Is there another entry in the first_chunk table ? */
if (chunk_run_index + 1 < stsc.first_chunk.length) {
/* The last chunk in the run is the chunk before the next first chunk */
last_chunk_in_run = stsc.first_chunk[chunk_run_index+1]-1;
} else {
/* There is only one entry in the table, it is valid for all future chunks*/
last_chunk_in_run = Infinity;
}
}
sample.chunk_run_index = chunk_run_index;
last_sample_in_chunk += stsc.samples_per_chunk[chunk_run_index];
}
}
sample.description_index = stsc.sample_description_index[sample.chunk_run_index]-1;
sample.description = stsd.entries[sample.description_index];
sample.offset = stco.chunk_offsets[sample.chunk_index-1] + offset_in_chunk; /* chunk indexes are 1-based */
offset_in_chunk += sample.size;
/* setting dts, cts, duration and rap flags */
if (j > last_sample_in_stts_run) {
stts_run_index++;
if (last_sample_in_stts_run < 0) {
last_sample_in_stts_run = 0;
}
last_sample_in_stts_run += stts.sample_counts[stts_run_index];
}
if (j > 0) {
trak.samples[j-1].duration = stts.sample_deltas[stts_run_index];
trak.samples_duration += trak.samples[j-1].duration;
sample.dts = trak.samples[j-1].dts + trak.samples[j-1].duration;
} else {
sample.dts = 0;
}
if (ctts) {
if (j >= last_sample_in_ctts_run) {
ctts_run_index++;
if (last_sample_in_ctts_run < 0) {
last_sample_in_ctts_run = 0;
}
last_sample_in_ctts_run += ctts.sample_counts[ctts_run_index];
}
sample.cts = trak.samples[j].dts + ctts.sample_offsets[ctts_run_index];
} else {
sample.cts = sample.dts;
}
if (stss) {
if (j == stss.sample_numbers[last_stss_index] - 1) { // sample numbers are 1-based
sample.is_sync = true;
last_stss_index++;
} else {
sample.is_sync = false;
sample.degradation_priority = 0;
}
if (subs) {
if (subs.entries[subs_entry_index].sample_delta + last_subs_sample_index == j+1) {
sample.subsamples = subs.entries[subs_entry_index].subsamples;
last_subs_sample_index += subs.entries[subs_entry_index].sample_delta;
subs_entry_index++;
}
}
} else {
sample.is_sync = true;
}
ISOFile.process_sdtp(trak.mdia.minf.stbl.sdtp, sample, sample.number);
if (stdp) {
sample.degradation_priority = stdp.priority[j];
} else {
sample.degradation_priority = 0;
}
if (subs) {
if (subs.entries[subs_entry_index].sample_delta + last_subs_sample_index == j) {
sample.subsamples = subs.entries[subs_entry_index].subsamples;
last_subs_sample_index += subs.entries[subs_entry_index].sample_delta;
}
}
if (sbgps.length > 0 || sgpds.length > 0) {
ISOFile.setSampleGroupProperties(trak, sample, j, trak.sample_groups_info);
}
}
if (j>0) {
trak.samples[j-1].duration = Math.max(trak.mdia.mdhd.duration - trak.samples[j-1].dts, 0);
trak.samples_duration += trak.samples[j-1].duration;
}
}
/* Update sample list when new 'moof' boxes are received */
ISOFile.prototype.updateSampleLists = function() {
var i, j, k;
var default_sample_description_index, default_sample_duration, default_sample_size, default_sample_flags;
var last_run_position;
var box, moof, traf, trak, trex;
var sample;
var sample_flags;
if (this.moov === undefined) {
return;
}
/* if the input file is fragmented and fetched in multiple downloads, we need to update the list of samples */
while (this.lastMoofIndex < this.moofs.length) {
box = this.moofs[this.lastMoofIndex];
this.lastMoofIndex++;
if (box.type == "moof") {
moof = box;
for (i = 0; i < moof.trafs.length; i++) {
traf = moof.trafs[i];
trak = this.getTrackById(traf.tfhd.track_id);
trex = this.getTrexById(traf.tfhd.track_id);
if (traf.tfhd.flags & BoxParser.TFHD_FLAG_SAMPLE_DESC) {
default_sample_description_index = traf.tfhd.default_sample_description_index;
} else {
default_sample_description_index = (trex ? trex.default_sample_description_index: 1);
}
if (traf.tfhd.flags & BoxParser.TFHD_FLAG_SAMPLE_DUR) {
default_sample_duration = traf.tfhd.default_sample_duration;
} else {
default_sample_duration = (trex ? trex.default_sample_duration : 0);
}
if (traf.tfhd.flags & BoxParser.TFHD_FLAG_SAMPLE_SIZE) {
default_sample_size = traf.tfhd.default_sample_size;
} else {
default_sample_size = (trex ? trex.default_sample_size : 0);
}
if (traf.tfhd.flags & BoxParser.TFHD_FLAG_SAMPLE_FLAGS) {
default_sample_flags = traf.tfhd.default_sample_flags;
} else {
default_sample_flags = (trex ? trex.default_sample_flags : 0);
}
traf.sample_number = 0;
/* process sample groups */
if (traf.sbgps.length > 0) {
ISOFile.initSampleGroups(trak, traf, traf.sbgps, trak.mdia.minf.stbl.sgpds, traf.sgpds);
}
for (j = 0; j < traf.truns.length; j++) {
var trun = traf.truns[j];
for (k = 0; k < trun.sample_count; k++) {
sample = {};
sample.moof_number = this.lastMoofIndex;
sample.number_in_traf = traf.sample_number;
traf.sample_number++;
sample.number = trak.samples.length;
traf.first_sample_index = trak.samples.length;
trak.samples.push(sample);
sample.track_id = trak.tkhd.track_id;
sample.timescale = trak.mdia.mdhd.timescale;
sample.description_index = default_sample_description_index-1;
sample.description = trak.mdia.minf.stbl.stsd.entries[sample.description_index];
sample.size = default_sample_size;
if (trun.flags & BoxParser.TRUN_FLAGS_SIZE) {
sample.size = trun.sample_size[k];
}
trak.samples_size += sample.size;
sample.duration = default_sample_duration;
if (trun.flags & BoxParser.TRUN_FLAGS_DURATION) {
sample.duration = trun.sample_duration[k];
}
trak.samples_duration += sample.duration;
if (trak.first_traf_merged || k > 0) {
sample.dts = trak.samples[trak.samples.length-2].dts+trak.samples[trak.samples.length-2].duration;
} else {
if (traf.tfdt) {
sample.dts = traf.tfdt.baseMediaDecodeTime;
} else {
sample.dts = 0;
}
trak.first_traf_merged = true;
}
sample.cts = sample.dts;
if (trun.flags & BoxParser.TRUN_FLAGS_CTS_OFFSET) {
sample.cts = sample.dts + trun.sample_composition_time_offset[k];
}
sample_flags = default_sample_flags;
if (trun.flags & BoxParser.TRUN_FLAGS_FLAGS) {
sample_flags = trun.sample_flags[k];
} else if (k === 0 && (trun.flags & BoxParser.TRUN_FLAGS_FIRST_FLAG)) {
sample_flags = trun.first_sample_flags;
}
sample.is_sync = ((sample_flags >> 16 & 0x1) ? false : true);
sample.is_leading = (sample_flags >> 26 & 0x3);
sample.depends_on = (sample_flags >> 24 & 0x3);
sample.is_depended_on = (sample_flags >> 22 & 0x3);
sample.has_redundancy = (sample_flags >> 20 & 0x3);
sample.degradation_priority = (sample_flags & 0xFFFF);
//ISOFile.process_sdtp(traf.sdtp, sample, sample.number_in_traf);
var bdop = (traf.tfhd.flags & BoxParser.TFHD_FLAG_BASE_DATA_OFFSET) ? true : false;
var dbim = (traf.tfhd.flags & BoxParser.TFHD_FLAG_DEFAULT_BASE_IS_MOOF) ? true : false;
var dop = (trun.flags & BoxParser.TRUN_FLAGS_DATA_OFFSET) ? true : false;
var bdo = 0;
if (!bdop) {
if (!dbim) {
if (j === 0) { // the first track in the movie fragment
bdo = moof.start; // the position of the first byte of the enclosing Movie Fragment Box
} else {
bdo = last_run_position; // end of the data defined by the preceding *track* (irrespective of the track id) fragment in the moof
}
} else {
bdo = moof.start;
}
} else {
bdo = traf.tfhd.base_data_offset;
}
if (j === 0 && k === 0) {
if (dop) {
sample.offset = bdo + trun.data_offset; // If the data-offset is present, it is relative to the base-data-offset established in the track fragment header
} else {
sample.offset = bdo; // the data for this run starts the base-data-offset defined by the track fragment header
}
} else {
sample.offset = last_run_position; // this run starts immediately after the data of the previous run
}
last_run_position = sample.offset + sample.size;
if (traf.sbgps.length > 0 || traf.sgpds.length > 0 ||
trak.mdia.minf.stbl.sbgps.length > 0 || trak.mdia.minf.stbl.sgpds.length > 0) {
ISOFile.setSampleGroupProperties(trak, sample, sample.number_in_traf, traf.sample_groups_info);
}
}
}
if (traf.subs) {
trak.has_fragment_subsamples = true;
var sample_index = traf.first_sample_index;
for (j = 0; j < traf.subs.entries.length; j++) {
sample_index += traf.subs.entries[j].sample_delta;
sample = trak.samples[sample_index-1];
sample.subsamples = traf.subs.entries[j].subsamples;
}
}
}
}
}
}
/* Try to get sample data for a given sample:
returns null if not found
returns the same sample if already requested
*/
ISOFile.prototype.getSample = function(trak, sampleNum) {
var buffer;
var sample = trak.samples[sampleNum];
if (!this.moov) {
return null;
}
if (!sample.data) {
/* Not yet fetched */
sample.data = new Uint8Array(sample.size);
sample.alreadyRead = 0;
this.samplesDataSize += sample.size;
Log.debug("ISOFile", "Allocating sample #"+sampleNum+" on track #"+trak.tkhd.track_id+" of size "+sample.size+" (total: "+this.samplesDataSize+")");
} else if (sample.alreadyRead == sample.size) {
/* Already fetched entirely */
return sample;
}
/* The sample has only been partially fetched, we need to check in all buffers */
while(true) {
var index = this.stream.findPosition(true, sample.offset + sample.alreadyRead, false);
if (index > -1) {
buffer = this.stream.buffers[index];
var lengthAfterStart = buffer.byteLength - (sample.offset + sample.alreadyRead - buffer.fileStart);
if (sample.size - sample.alreadyRead <= lengthAfterStart) {
/* the (rest of the) sample is entirely contained in this buffer */
Log.debug("ISOFile","Getting sample #"+sampleNum+" data (alreadyRead: "+sample.alreadyRead+" offset: "+
(sample.offset+sample.alreadyRead - buffer.fileStart)+" read size: "+(sample.size - sample.alreadyRead)+" full size: "+sample.size+")");
DataStream.memcpy(sample.data.buffer, sample.alreadyRead,
buffer, sample.offset+sample.alreadyRead - buffer.fileStart, sample.size - sample.alreadyRead);
/* update the number of bytes used in this buffer and check if it needs to be removed */
buffer.usedBytes += sample.size - sample.alreadyRead;
this.stream.logBufferLevel();
sample.alreadyRead = sample.size;
return sample;
} else {
/* the sample does not end in this buffer */
if (lengthAfterStart === 0) return null;
Log.debug("ISOFile","Getting sample #"+sampleNum+" partial data (alreadyRead: "+sample.alreadyRead+" offset: "+
(sample.offset+sample.alreadyRead - buffer.fileStart)+" read size: "+lengthAfterStart+" full size: "+sample.size+")");
DataStream.memcpy(sample.data.buffer, sample.alreadyRead,
buffer, sample.offset+sample.alreadyRead - buffer.fileStart, lengthAfterStart);
sample.alreadyRead += lengthAfterStart;
/* update the number of bytes used in this buffer and check if it needs to be removed */
buffer.usedBytes += lengthAfterStart;
this.stream.logBufferLevel();
/* keep looking in the next buffer */
}
} else {
return null;
}
}
}
/* Release the memory used to store the data of the sample */
ISOFile.prototype.releaseSample = function(trak, sampleNum) {
var sample = trak.samples[sampleNum];
if (sample.data) {
this.samplesDataSize -= sample.size;
sample.data = null;
sample.alreadyRead = 0;
return sample.size;
} else {
return 0;
}
}
ISOFile.prototype.getAllocatedSampleDataSize = function() {
return this.samplesDataSize;
}
/* Builds the MIME Type 'codecs' sub-parameters for the whole file */
ISOFile.prototype.getCodecs = function() {
var i;
var codecs = "";
for (i = 0; i < this.moov.traks.length; i++) {
var trak = this.moov.traks[i];
if (i>0) {
codecs+=",";
}
codecs += trak.mdia.minf.stbl.stsd.entries[0].getCodec();
}
return codecs;
}
/* Helper function */
ISOFile.prototype.getTrexById = function(id) {
var i;
if (!this.moov || !this.moov.mvex) return null;
for (i = 0; i < this.moov.mvex.trexs.length; i++) {
var trex = this.moov.mvex.trexs[i];
if (trex.track_id == id) return trex;
}
return null;
}
/* Helper function */
ISOFile.prototype.getTrackById = function(id) {
if (this.moov === undefined) {
return null;
}
for (var j = 0; j < this.moov.traks.length; j++) {
var trak = this.moov.traks[j];
if (trak.tkhd.track_id == id) return trak;
}
return null;
}