5e95014b by David LaPalomento

Add support for version 0 FullBoxes

Modify the mp4 inspector to use the right field widths when parsing version 0 mp4s. Add a test page to inspect mp4s from the file system. Don't reference the data bytes of an mdat box so that the resulting data structure isn't too big to be reasonably output.
1 parent e9ae4872
......@@ -67,7 +67,55 @@ var
0, 0, 0, 0,
0, 0, 0, 0,
0x40, 0, 0, 0
];
],
mvhd0 = box('mvhd',
0x00, // version 0
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // creation_time
0x00, 0x00, 0x00, 0x02, // modification_time
0x00, 0x00, 0x00, 0x3c, // timescale
0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
0x00, 0x01, 0x00, 0x00, // 1.0 rate
0x01, 0x00, // 1.0 volume
0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // reserved
unityMatrix,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, // pre_defined
0x00, 0x00, 0x00, 0x02),
tkhd0 = box('tkhd',
0x00, // version 0
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x02, // creation_time
0x00, 0x00, 0x00, 0x03, // modification_time
0x00, 0x00, 0x00, 0x01, // track_ID
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, // layer
0x00, 0x00, // alternate_group
0x00, 0x00, // non-audio track volume
0x00, 0x00, // reserved
unityMatrix,
0x00, 0x00, 0x01, 0x2c, // 300 = 0x12c width
0x00, 0x00, 0x00, 0x96), // 150 = 0x96 height
mdhd0 = box('mdhd',
0x00, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x02, // creation_time
0x00, 0x00, 0x00, 0x03, // modification_time
0x00, 0x00, 0x00, 0x3c, // timescale
0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
0x15, 0xc7, // 'eng' language
0x00, 0x00);
module('MP4 Inspector');
......@@ -125,7 +173,7 @@ test('can parse an mdat', function() {
deepEqual(videojs.inspectMp4(mdat), [{
size: 12,
type: 'mdat',
data: mdat.subarray(mdat.byteLength - 4)
byteLength: 4
}], 'parsed an mdat');
});
......@@ -148,6 +196,56 @@ test('can parse a free or skip', function() {
}], 'parsed a skip');
});
test('can parse a version 0 mvhd', function() {
deepEqual(videojs.inspectMp4(new Uint8Array(mvhd0)), [{
type: 'mvhd',
version: 0,
flags: new Uint8Array([0, 0, 0]),
creationTime: 1,
modificationTime: 2,
timescale: 60,
duration: 600,
rate: 1,
volume: 1,
matrix: new Uint32Array(unityMatrix),
size: 108,
nextTrackId: 2
}]);
});
test('can parse a version 0 tkhd', function() {
deepEqual(videojs.inspectMp4(new Uint8Array(tkhd0)), [{
type: 'tkhd',
version: 0,
flags: new Uint8Array([0, 0, 0]),
creationTime: 2,
modificationTime: 3,
size: 92,
trackId: 1,
duration: 600,
layer: 0,
alternateGroup: 0,
volume: 0,
matrix: new Uint32Array(unityMatrix),
width: 300,
height: 150
}]);
});
test('can parse a version 0 mdhd', function() {
deepEqual(videojs.inspectMp4(new Uint8Array(mdhd0)), [{
type: 'mdhd',
version: 0,
flags: new Uint8Array([0, 0, 0]),
creationTime: 2,
modificationTime: 3,
size: 32,
timescale: 60,
duration: 600,
language: 'eng'
}]);
});
test('can parse a moov', function() {
var data =
box('moov',
......
......@@ -39,6 +39,11 @@
with a command like this:
<pre>ffmpeg -i input.ts -acodec copy -vcodec copy output.flv</pre>
</p>
<p>
This page only compares FLVs files. There is
a <a href="mp4.html">similar utility</a> for testing the mp4
conversion.
</p>
</header>
<section>
<h2>Inputs</h2>
......
......@@ -71,9 +71,15 @@ var
return result;
},
mdat: function(data) {
return {
byteLength: data.byteLength
};
},
mdhd: function(data) {
var
view = new DataView(data.buffer, data.byteOffset, data.byteLength),
i = 4,
language,
result = {
version: view.getUint8(0),
......@@ -81,14 +87,27 @@ var
language: ''
};
if (result.version === 1) {
result.creationTime = view.getUint32(8); // truncating top 4 bytes
result.modificationTime = view.getUint32(16); // truncating top 4 bytes
result.timescale = view.getUint32(20);
result.duration = view.getUint32(28); // truncating top 4 bytes
i += 4;
result.creationTime = view.getUint32(i); // truncating top 4 bytes
i += 8;
result.modificationTime = view.getUint32(i); // truncating top 4 bytes
i += 4;
result.timescale = view.getUint32(i);
i += 8;
result.duration = view.getUint32(i); // truncating top 4 bytes
} else {
result.creationTime = view.getUint32(i);
i += 4;
result.modificationTime = view.getUint32(i);
i += 4;
result.timescale = view.getUint32(i);
i += 4;
result.duration = view.getUint32(i);
}
i += 4;
// language is stored as an ISO-639-2/T code in an array of three 5-bit fields
// each field is the packed difference between its ASCII value and 0x60
language = view.getUint16(32);
language = view.getUint16(i);
result.language += String.fromCharCode((language >> 10) + 0x60);
result.language += String.fromCharCode(((language & 0x03c0) >> 5) + 0x60);
result.language += String.fromCharCode((language & 0x1f) + 0x60);
......@@ -113,21 +132,43 @@ var
mvhd: function(data) {
var
view = new DataView(data.buffer, data.byteOffset, data.byteLength),
i = 4,
result = {
version: view.getUint8(0),
flags: new Uint8Array(data.subarray(1, 4)),
// convert fixed-point, base 16 back to a number
rate: view.getUint16(32) + (view.getUint16(34) / 16),
volume: view.getUint8(36) + (view.getUint8(37) / 8),
matrix: new Uint32Array(data.subarray(48, 84)),
nextTrackId: view.getUint32(108)
flags: new Uint8Array(data.subarray(1, 4))
};
if (result.version === 1) {
result.creationTime = view.getUint32(8); // truncating top 4 bytes
result.modificationTime = view.getUint32(16); // truncating top 4 bytes
result.timescale = view.getUint32(20);
result.duration = view.getUint32(28); // truncating top 4 bytes
i += 4;
result.creationTime = view.getUint32(i); // truncating top 4 bytes
i += 8;
result.modificationTime = view.getUint32(i); // truncating top 4 bytes
i += 4;
result.timescale = view.getUint32(i);
i += 8
result.duration = view.getUint32(i); // truncating top 4 bytes
} else {
result.creationTime = view.getUint32(i);
i += 4;
result.modificationTime = view.getUint32(i);
i += 4;
result.timescale = view.getUint32(i);
i += 4;
result.duration = view.getUint32(i);
}
i += 4;
// convert fixed-point, base 16 back to a number
result.rate = view.getUint16(i) + (view.getUint16(i + 2) / 16);
i += 4;
result.volume = view.getUint8(i) + (view.getUint8(i + 1) / 8);
i += 2;
i += 2;
i += 2 * 4;
result.matrix = new Uint32Array(data.subarray(i, i + (9 * 4)));
i += 9 * 4;
i += 6 * 4;
result.nextTrackId = view.getUint32(i);
return result;
},
pdin: function(data) {
......@@ -172,23 +213,46 @@ var
tkhd: function(data) {
var
view = new DataView(data.buffer, data.byteOffset, data.byteLength),
i = 4,
result = {
version: view.getUint8(0),
flags: new Uint8Array(data.subarray(1, 4)),
layer: view.getUint16(44),
alternateGroup: view.getUint16(46),
// convert fixed-point, base 16 back to a number
volume: view.getUint8(48) + (view.getUint8(49) / 8),
matrix: new Uint32Array(data.subarray(52, 88)),
width: view.getUint32(88),
height: view.getUint32(92)
};
if (result.version === 1) {
result.creationTime = view.getUint32(8); // truncating top 4 bytes
result.modificationTime = view.getUint32(16); // truncating top 4 bytes
result.trackId = view.getUint32(20);
result.duration = view.getUint32(32); // truncating top 4 bytes
i += 4;
result.creationTime = view.getUint32(i); // truncating top 4 bytes
i += 8;
result.modificationTime = view.getUint32(i); // truncating top 4 bytes
i += 4;
result.trackId = view.getUint32(i);
i += 4;
i += 8;
result.duration = view.getUint32(i); // truncating top 4 bytes
} else {
result.creationTime = view.getUint32(i);
i += 4;
result.modificationTime = view.getUint32(i);
i += 4;
result.trackId = view.getUint32(i);
i += 4;
i += 4;
result.duration = view.getUint32(i);
}
i += 4;
i += 2 * 4;
result.layer = view.getUint16(i);
i += 2;
result.alternateGroup = view.getUint16(i);
i += 2;
// convert fixed-point, base 16 back to a number
result.volume = view.getUint8(i) + (view.getUint8(i + 1) / 8);
i += 2;
i += 2;
result.matrix = new Uint32Array(data.subarray(i, i + (9 * 4)));
i += 9 * 4;
result.width = view.getUint32(i);
i += 4;
result.height = view.getUint32(i);
return result;
}
};
......
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="css/normalize.min.css">
<link rel="stylesheet" href="css/main.css">
<script src="js/vendor/modernizr-2.6.2.min.js"></script>
</head>
<body>
<!--[if lt IE 7]>
<p class="chromeframe">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> or <a href="http://www.google.com/chromeframe/?redirect=true">activate Google Chrome Frame</a> to improve your experience.</p>
<![endif]-->
<div class="header-container">
<header class="wrapper clearfix">
<h1 class="title">Transmux Analyzer</h1>
</header>
</div>
<div class="main-container">
<div class="main wrapper clearfix">
<article>
<header>
<p>
This page can help you inspect the results of the
transmuxing to mp4 files performed by videojs-contrib-hls.
</p>
<p>
Looking for the FLV tool? Check out
the <a href="index.html">FLV utility</a>.
</p>
</header>
<section>
<h2>Inputs</h2>
<form id="inputs">
<label>
Your original MP2T segment:
<input type="file" id="original">
</label>
<label>
A working, MP4 version of the underlying stream
produced by another tool:
<input type="file" id="working">
</label>
</form>
</section>
<section>
<h2>Comparison</h2>
<div class="result-wrapper">
<h3>videojs-contrib-hls</h3>
<ol class="vjs-boxes">
</ol>
</div>
<div class="result-wrapper">
<h3>Working</h3>
<pre class="working-boxes">
</pre>
</div>
</section>
<section>
<h2>Results</h2>
<div class="result-wrapper">
<h3>videojs-contrib-hls</h3>
<div class="vjs-hls-output result">
<p>
The results of transmuxing your input file with
videojs-contrib-hls will show up here.
</p>
</div>
</div>
<div class="result-wrapper">
<h3>Working</h3>
<div class="working-output result">
<p>
The "good" version of the file will show up here.
</p>
</div>
</div>
</section>
</article>
</div> <!-- #main -->
</div> <!-- #main-container -->
<div class="footer-container">
<footer class="wrapper">
<h3>footer</h3>
</footer>
</div>
<script>
window.videojs = window.videojs || {
Hls: {}
};
</script>
<script src="js/mp4-inspector.js"></script>
<script src="../../src/bin-utils.js"></script>
<script>
var inputs = document.getElementById('inputs'),
original = document.getElementById('original'),
working = document.getElementById('working'),
vjsBoxes = document.querySelector('.vjs-boxes'),
workingBoxes = document.querySelector('.working-boxes'),
vjsOutput = document.querySelector('.vjs-hls-output'),
workingOutput = document.querySelector('.working-output'),
tagTypes = {
0x08: 'audio',
0x09: 'video',
0x12: 'metadata'
};
videojs.log = console.log.bind(console);
original.addEventListener('change', function() {
var reader = new FileReader();
reader.addEventListener('loadend', function() {
var mp2t = new Uint8Array(reader.result);
// clear old boxes info
vjsBoxes.innerHTML = '';
// write out the result
// vjsOutput.innerHTML = hex;
});
reader.readAsArrayBuffer(this.files[0]);
}, false);
working.addEventListener('change', function() {
var reader = new FileReader();
reader.addEventListener('loadend', function() {
var hex = '<pre>',
bytes = new Uint8Array(reader.result);
// clear old box info
workingBoxes.innerHTML = JSON.stringify(videojs.inspectMp4(bytes), null, ' ');
// output the hex dump
hex += videojs.Hls.utils.hexDump(bytes);
hex += '</pre>';
workingOutput.innerHTML = hex;
});
reader.readAsArrayBuffer(this.files[0]);
}, false);
</script>
</body>
</html>