e19a796f by David LaPalomento

Write track fragment metadata

Output real tfhd info. Prepare trun to accept sample information-- this still needs to be generated properly by the transmuxer.
1 parent 85ee7214
......@@ -2,7 +2,7 @@
'use strict';
var box, dinf, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd, trak,
tkhd, mdia, mdhd, hdlr, stbl, stsd, styp, trex, types,
tkhd, mdia, mdhd, hdlr, stbl, stsd, styp, traf, trex, trun, types,
MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR, AUDIO_HDLR,
HDLR_TYPES, VMHD, DREF, STCO, STSC, STSZ, STTS, Uint8Array,
DataView;
......@@ -40,6 +40,7 @@ DataView = window.DataView;
tfhd: [],
traf: [],
trak: [],
trun: [],
trex: [],
tkhd: [],
vmhd: []
......@@ -202,23 +203,16 @@ minf = function(track) {
};
moof = function(sequenceNumber, tracks) {
var
trafCall = [],
trackFragments = [],
i = tracks.length;
// build tfhd boxes for each track fragment
// build traf boxes for each track fragment
while (i--) {
trafCall[i] = box(types.tfhd, new Uint8Array([
0x00, // version 0
0x00, 0x00, 0x00, // flags
(tracks[i].trackId & 0xFF000000) >> 24,
(tracks[i].trackId & 0xFF0000) >> 16,
(tracks[i].trackId & 0xFF00) >> 8,
(tracks[i].trackId & 0xFF),
]));
trackFragments[i] = traf(tracks[i]);
}
trafCall.unshift(types.traf);
return box(types.moof,
mfhd(sequenceNumber),
box.apply(null, trafCall));
return box.apply(null, [
types.moof,
mfhd(sequenceNumber)
].concat(trackFragments));
};
/**
* @param tracks... (optional) {array} the tracks associated with this movie
......@@ -404,6 +398,19 @@ tkhd = function(track) {
]));
};
traf = function(track) {
return box(types.traf,
box(types.tfhd, new Uint8Array([
0x00, // version 0
0x00, 0x00, 0x00, // flags
(track.id & 0xFF000000) >> 24,
(track.id & 0xFF0000) >> 16,
(track.id & 0xFF00) >> 8,
(track.id & 0xFF),
])),
trun(track));
};
/**
* Generate a track box.
* @param track {object} a track definition
......@@ -431,6 +438,44 @@ trex = function(track) {
]));
};
trun = function(track) {
var bytes, samples, sample, i;
samples = track.samples || [];
bytes = [
0x00, // version 0
0x00, 0x0f, 0x00, // flags
(samples.length & 0xFF000000) >>> 24,
(samples.length & 0xFF0000) >>> 16,
(samples.length & 0xFF00) >>> 8,
samples.length & 0xFF // sample_count
];
for (i = 0; i < samples.length; i++) {
sample = samples[i];
bytes = bytes.concat([
(sample.duration & 0xFF000000) >>> 24,
(sample.duration & 0xFF0000) >>> 16,
(sample.duration & 0xFF00) >>> 8,
sample.duration & 0xFF, // sample_duration
(sample.size & 0xFF000000) >>> 24,
(sample.size & 0xFF0000) >>> 16,
(sample.size & 0xFF00) >>> 8,
sample.size & 0xFF, // sample_size
(sample.flags & 0xFF000000) >>> 24,
(sample.flags & 0xFF0000) >>> 16,
(sample.flags & 0xFF00) >>> 8,
sample.flags & 0xFF, // sample_flags
(sample.compositionTimeOffset & 0xFF000000) >>> 24,
(sample.compositionTimeOffset & 0xFF0000) >>> 16,
(sample.compositionTimeOffset & 0xFF00) >>> 8,
sample.compositionTimeOffset & 0xFF, // sample_composition_time_offset
]);
}
return box(types.trun, new Uint8Array(bytes));
};
window.videojs.mp4 = {
ftyp: ftyp,
mdat: mdat,
......
......@@ -737,7 +737,7 @@ Transmuxer = function() {
flushVideo = function() {
var moof, mdat, boxes, i, data;
moof = mp4.moof(sequenceNumber, []);
moof = mp4.moof(sequenceNumber, tracks);
// concatenate the video data and construct the mdat
data = new Uint8Array(videoSamplesSize);
......
......@@ -301,11 +301,21 @@ test('generates an initialization segment', function() {
test('generates a minimal moof', function() {
var
data = mp4.moof(7, [{
trackId: 1
}, {
trackId: 2
id: 17,
samples: [{
duration: 9000,
size: 10,
flags: 14,
compositionTimeOffset: 500
}, {
duration: 10000,
size: 11,
flags: 9,
compositionTimeOffset: 1000
}]
}]),
moof = videojs.inspectMp4(data);
moof = videojs.inspectMp4(data),
trun;
equal(moof.length, 1, 'generated one box');
equal(moof[0].type, 'moof', 'generated a moof box');
......@@ -313,10 +323,33 @@ test('generates a minimal moof', function() {
equal(moof[0].boxes[0].type, 'mfhd', 'generated an mfhd box');
equal(moof[0].boxes[0].sequenceNumber, 7, 'included the sequence_number');
equal(moof[0].boxes[1].type, 'traf', 'generated a traf box');
equal(moof[0].boxes[1].boxes.length, 2, 'generated two fragment headers');
equal(moof[0].boxes[1].boxes.length, 2, 'generated track fragment info');
equal(moof[0].boxes[1].boxes[0].type, 'tfhd', 'generated a tfhd box');
equal(moof[0].boxes[1].boxes[0].trackId, 1, 'wrote the first track id');
equal(moof[0].boxes[1].boxes[1].trackId, 2, 'wrote the second track id');
equal(moof[0].boxes[1].boxes[0].trackId, 17, 'wrote the first track id');
equal(moof[0].boxes[1].boxes[0].type, 'tfhd', 'generated a tfhd box');
trun = moof[0].boxes[1].boxes[1];
equal(trun.type, 'trun', 'generated a trun box');
equal(trun.samples.length, 2, 'wrote two samples');
equal(trun.samples[0].duration, 9000, 'wrote a sample duration');
equal(trun.samples[0].size, 10, 'wrote a sample size');
equal(trun.samples[0].flags, 14, 'wrote the sample flags');
equal(trun.samples[0].compositionTimeOffset, 500, 'wrote the composition time offset');
equal(trun.samples[1].duration, 10000, 'wrote a sample duration');
equal(trun.samples[1].size, 11, 'wrote a sample size');
equal(trun.samples[1].flags, 9, 'wrote the sample flags');
equal(trun.samples[1].compositionTimeOffset, 1000, 'wrote the composition time offset');
});
test('can generate a traf without samples', function() {
var
data = mp4.moof(8, [{
trackId: 13
}]),
moof = videojs.inspectMp4(data);
equal(moof[0].boxes[1].boxes[1].samples.length, 0, 'generated no samples');
});
test('generates an mdat', function() {
......
......@@ -718,6 +718,32 @@ test('can parse a trun', function() {
}]);
});
test('can parse a trun with per-sample flags', function() {
var data = box('trun',
0x00, // version
0x00, 0x0f, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // sample_count
0x00, 0x00, 0x00, 0x09, // sample_duration
0x00, 0x00, 0x00, 0xff, // sample_size
0x01, 0x02, 0x03, 0x04, // sample_flags
0x00, 0x00, 0x00, 0x00); // sample_composition_time_offset
deepEqual(videojs.inspectMp4(new Uint8Array(data)),
[{
type: 'trun',
version: 0,
size: 32,
flags: new Uint8Array([0, 0x0f, 0x00]),
samples: [{
duration: 9,
size: 0xff,
flags: 0x01020304,
compositionTimeOffset: 0
}]
}]);
});
test('can parse a sidx', function(){
var data = box('sidx',
0x00, // version
......@@ -762,7 +788,7 @@ test('can parse a sidx', function(){
sapType: 1,
sapDeltaTime: 5
}]
}]);
});
......
......@@ -537,10 +537,6 @@ var
};
while (sampleCount--) {
sample = {};
if (sampleFlagsPresent) {
sample.flags = view.getUint32(offset);
offset += 4;
}
if (sampleDurationPresent) {
sample.duration = view.getUint32(offset);
offset += 4;
......@@ -549,6 +545,10 @@ var
sample.size = view.getUint32(offset);
offset += 4;
}
if (sampleFlagsPresent) {
sample.flags = view.getUint32(offset);
offset += 4;
}
if (sampleCompositionTimeOffsetPresent) {
sample.compositionTimeOffset = view.getUint32(offset);
offset += 4;
......