cb74d9c0 by David LaPalomento

Add inspector support for moof boxes

Create test cases for a moof box and sample mfhd, traf, and tfhd sub-boxes. Fix the source buffer codec so that the debug code on the mp4 muxer page works. The issue was that specifying an audio codec but then failing to include an audio track in the init segment generated a media decode error.
1 parent 23a2bf27
......@@ -622,6 +622,53 @@ test('can parse an stsz', function() {
}]);
});
test('can parse a moof', function() {
var data = box('moof',
box('mfhd',
0x00, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x04), // sequence_number
box('traf',
box('tfhd',
0x00, // version
0x00, 0x00, 0x3b, // flags
0x00, 0x00, 0x00, 0x01, // track_ID
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, // base_data_offset
0x00, 0x00, 0x00, 0x02, // sample_description_index
0x00, 0x00, 0x00, 0x03, // default_sample_duration,
0x00, 0x00, 0x00, 0x04, // default_sample_size
0x00, 0x00, 0x00, 0x05))); // default_sample_flags
deepEqual(videojs.inspectMp4(new Uint8Array(data)),
[{
type: 'moof',
size: 72,
boxes: [{
type: 'mfhd',
size: 16,
version: 0,
flags: new Uint8Array([0, 0, 0]),
sequenceNumber: 4
},
{
type: 'traf',
size: 48,
boxes: [{
type: 'tfhd',
version: 0,
size: 40,
flags: new Uint8Array([0, 0, 0x3b]),
trackId: 1,
baseDataOffset: 1,
sampleDescriptionIndex: 2,
defaultSampleDuration: 3,
defaultSampleSize: 4,
defaultSampleFlags: 5
}]
}]
}]);
});
test('can parse a series of boxes', function() {
var ftyp = [
0x00, 0x00, 0x00, 0x18 // size 4 * 6 = 24
......
......@@ -185,11 +185,26 @@ var
boxes: videojs.inspectMp4(data)
};
},
mfhd: function(data) {
return {
version: data[0],
flags: new Uint8Array(data.subarray(1, 4)),
sequenceNumber: (data[4] << 24) |
(data[5] << 16) |
(data[6] << 8) |
(data[7])
};
},
minf: function(data) {
return {
boxes: videojs.inspectMp4(data)
};
},
moof: function(data) {
return {
boxes: videojs.inspectMp4(data)
};
},
moov: function(data) {
return {
boxes: videojs.inspectMp4(data)
......@@ -251,28 +266,6 @@ var
initialDelay: view.getUint32(8)
};
},
trak: function(data) {
return {
boxes: videojs.inspectMp4(data)
};
},
trex: function(data) {
var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
return {
version: data[0],
flags: new Uint8Array(data.subarray(1, 4)),
trackId: view.getUint32(4),
defaultSampleDescriptionIndex: view.getUint32(8),
defaultSampleDuration: view.getUint32(12),
defaultSampleSize: view.getUint32(16),
sampleDependsOn: data[20] & 0x03,
sampleIsDependedOn: (data[21] & 0xc0) >> 6,
sampleHasRedundancy: (data[21] & 0x30) >> 4,
samplePaddingValue: (data[21] & 0x0e) >> 1,
sampleIsDifferenceSample: !!(data[21] & 0x01),
sampleDegradationPriority: view.getUint16(22)
};
},
stbl: function(data) {
return {
boxes: videojs.inspectMp4(data)
......@@ -353,6 +346,44 @@ var
}
return result;
},
tfhd: function(data) {
var
view = new DataView(data.buffer, data.byteOffset, data.byteLength),
result = {
version: data[0],
flags: new Uint8Array(data.subarray(1, 4)),
trackId: view.getUint32(4)
},
baseDataOffsetPresent = result.flags[2] & 0x01,
sampleDescriptionIndexPresent = result.flags[2] & 0x02,
defaultSampleDurationPresent = result.flags[2] & 0x08,
defaultSampleSizePresent = result.flags[2] & 0x10,
defaultSampleFlagsPresent = result.flags[2] & 0x20,
i;
i = 8;
if (baseDataOffsetPresent) {
i += 4; // truncate top 4 bytes
result.baseDataOffset = view.getUint32(12);
i += 4;
}
if (sampleDescriptionIndexPresent) {
result.sampleDescriptionIndex = view.getUint32(i);
i += 4;
}
if (defaultSampleDurationPresent) {
result.defaultSampleDuration = view.getUint32(i);
i += 4;
}
if (defaultSampleSizePresent) {
result.defaultSampleSize = view.getUint32(i);
i += 4;
}
if (defaultSampleFlagsPresent) {
result.defaultSampleFlags = view.getUint32(i);
}
return result;
},
tkhd: function(data) {
var
view = new DataView(data.buffer, data.byteOffset, data.byteLength),
......@@ -398,6 +429,33 @@ var
result.height = view.getUint16(i) + (view.getUint16(i + 2) / 16);
return result;
},
traf: function(data) {
return {
boxes: videojs.inspectMp4(data)
};
},
trak: function(data) {
return {
boxes: videojs.inspectMp4(data)
};
},
trex: function(data) {
var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
return {
version: data[0],
flags: new Uint8Array(data.subarray(1, 4)),
trackId: view.getUint32(4),
defaultSampleDescriptionIndex: view.getUint32(8),
defaultSampleDuration: view.getUint32(12),
defaultSampleSize: view.getUint32(16),
sampleDependsOn: data[20] & 0x03,
sampleIsDependedOn: (data[21] & 0xc0) >> 6,
sampleHasRedundancy: (data[21] & 0x30) >> 4,
samplePaddingValue: (data[21] & 0x0e) >> 1,
sampleIsDifferenceSample: !!(data[21] & 0x01),
sampleDegradationPriority: view.getUint16(22)
};
},
'url ': function(data) {
return {
version: data[0],
......
......@@ -146,6 +146,26 @@
// XXX media source testing
var video = document.createElement('video');
var mediaSource = new MediaSource();
mediaSource.addEventListener('sourceopen', function() {
var buffer = mediaSource.addSourceBuffer('video/mp4;codecs=avc1.4d400d');
buffer.addEventListener('updatestart', console.log.bind(console));
buffer.addEventListener('updateend', console.log.bind(console));
buffer.addEventListener('error', console.log.bind(console));
buffer.appendBuffer(videojs.mp4.initSegment());
console.log('done');
window.vjsMediaSource = mediaSource;
window.vjsSourceBuffer = buffer;
window.vjsVideo = video;
});
mediaSource.addEventListener('error', console.log.bind(console));
mediaSource.addEventListener('opened', console.log.bind(console));
mediaSource.addEventListener('closed', console.log.bind(console));
mediaSource.addEventListener('sourceended', console.log.bind(console));
video.src = URL.createObjectURL(mediaSource);
video.addEventListener('error', console.log.bind(console));
vjsBoxes.innerHTML = JSON.stringify(videojs.inspectMp4(videojs.mp4.initSegment()), null, ' ');
});
reader.readAsArrayBuffer(this.files[0]);
......@@ -168,25 +188,6 @@
// XXX Media Sources Testing
var video = document.createElement('video');
var mediaSource = new MediaSource();
mediaSource.addEventListener('sourceopen', function() {
var buffer = mediaSource.addSourceBuffer('video/mp4;codecs=avc1.4d0020,mp4a.40.2');
buffer.addEventListener('updatestart', console.log.bind(console));
buffer.addEventListener('updateend', console.log.bind(console));
buffer.addEventListener('error', console.log.bind(console));
buffer.appendBuffer(bytes);
console.log('done');
window.vjsMediaSource = mediaSource;
window.vjsSourceBuffer = buffer;
window.vjsVideo = video;
});
mediaSource.addEventListener('error', console.log.bind(console));
mediaSource.addEventListener('opened', console.log.bind(console));
mediaSource.addEventListener('closed', console.log.bind(console));
mediaSource.addEventListener('sourceended', console.log.bind(console));
video.src = URL.createObjectURL(mediaSource);
video.addEventListener('error', console.log.bind(console));
});
reader.readAsArrayBuffer(this.files[0]);
}, false);
......