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 ...@@ -67,7 +67,55 @@ var
67 0, 0, 0, 0, 67 0, 0, 0, 0,
68 0, 0, 0, 0, 68 0, 0, 0, 0,
69 0x40, 0, 0, 0 69 0x40, 0, 0, 0
70 ]; 70 ],
71
72 mvhd0 = box('mvhd',
73 0x00, // version 0
74 0x00, 0x00, 0x00, // flags
75 0x00, 0x00, 0x00, 0x01, // creation_time
76 0x00, 0x00, 0x00, 0x02, // modification_time
77 0x00, 0x00, 0x00, 0x3c, // timescale
78 0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
79 0x00, 0x01, 0x00, 0x00, // 1.0 rate
80 0x01, 0x00, // 1.0 volume
81 0x00, 0x00, // reserved
82 0x00, 0x00, 0x00, 0x00, // reserved
83 0x00, 0x00, 0x00, 0x00, // reserved
84 unityMatrix,
85 0x00, 0x00, 0x00, 0x00,
86 0x00, 0x00, 0x00, 0x00,
87 0x00, 0x00, 0x00, 0x00,
88 0x00, 0x00, 0x00, 0x00,
89 0x00, 0x00, 0x00, 0x00,
90 0x00, 0x00, 0x00, 0x00, // pre_defined
91 0x00, 0x00, 0x00, 0x02),
92
93 tkhd0 = box('tkhd',
94 0x00, // version 0
95 0x00, 0x00, 0x00, // flags
96 0x00, 0x00, 0x00, 0x02, // creation_time
97 0x00, 0x00, 0x00, 0x03, // modification_time
98 0x00, 0x00, 0x00, 0x01, // track_ID
99 0x00, 0x00, 0x00, 0x00, // reserved
100 0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
101 0x00, 0x00, 0x00, 0x00,
102 0x00, 0x00, 0x00, 0x00, // reserved
103 0x00, 0x00, // layer
104 0x00, 0x00, // alternate_group
105 0x00, 0x00, // non-audio track volume
106 0x00, 0x00, // reserved
107 unityMatrix,
108 0x00, 0x00, 0x01, 0x2c, // 300 = 0x12c width
109 0x00, 0x00, 0x00, 0x96), // 150 = 0x96 height
110 mdhd0 = box('mdhd',
111 0x00, // version 1
112 0x00, 0x00, 0x00, // flags
113 0x00, 0x00, 0x00, 0x02, // creation_time
114 0x00, 0x00, 0x00, 0x03, // modification_time
115 0x00, 0x00, 0x00, 0x3c, // timescale
116 0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
117 0x15, 0xc7, // 'eng' language
118 0x00, 0x00);
71 119
72 module('MP4 Inspector'); 120 module('MP4 Inspector');
73 121
...@@ -125,7 +173,7 @@ test('can parse an mdat', function() { ...@@ -125,7 +173,7 @@ test('can parse an mdat', function() {
125 deepEqual(videojs.inspectMp4(mdat), [{ 173 deepEqual(videojs.inspectMp4(mdat), [{
126 size: 12, 174 size: 12,
127 type: 'mdat', 175 type: 'mdat',
128 data: mdat.subarray(mdat.byteLength - 4) 176 byteLength: 4
129 }], 'parsed an mdat'); 177 }], 'parsed an mdat');
130 }); 178 });
131 179
...@@ -148,6 +196,56 @@ test('can parse a free or skip', function() { ...@@ -148,6 +196,56 @@ test('can parse a free or skip', function() {
148 }], 'parsed a skip'); 196 }], 'parsed a skip');
149 }); 197 });
150 198
199 test('can parse a version 0 mvhd', function() {
200 deepEqual(videojs.inspectMp4(new Uint8Array(mvhd0)), [{
201 type: 'mvhd',
202 version: 0,
203 flags: new Uint8Array([0, 0, 0]),
204 creationTime: 1,
205 modificationTime: 2,
206 timescale: 60,
207 duration: 600,
208 rate: 1,
209 volume: 1,
210 matrix: new Uint32Array(unityMatrix),
211 size: 108,
212 nextTrackId: 2
213 }]);
214 });
215
216 test('can parse a version 0 tkhd', function() {
217 deepEqual(videojs.inspectMp4(new Uint8Array(tkhd0)), [{
218 type: 'tkhd',
219 version: 0,
220 flags: new Uint8Array([0, 0, 0]),
221 creationTime: 2,
222 modificationTime: 3,
223 size: 92,
224 trackId: 1,
225 duration: 600,
226 layer: 0,
227 alternateGroup: 0,
228 volume: 0,
229 matrix: new Uint32Array(unityMatrix),
230 width: 300,
231 height: 150
232 }]);
233 });
234
235 test('can parse a version 0 mdhd', function() {
236 deepEqual(videojs.inspectMp4(new Uint8Array(mdhd0)), [{
237 type: 'mdhd',
238 version: 0,
239 flags: new Uint8Array([0, 0, 0]),
240 creationTime: 2,
241 modificationTime: 3,
242 size: 32,
243 timescale: 60,
244 duration: 600,
245 language: 'eng'
246 }]);
247 });
248
151 test('can parse a moov', function() { 249 test('can parse a moov', function() {
152 var data = 250 var data =
153 box('moov', 251 box('moov',
......
...@@ -39,6 +39,11 @@ ...@@ -39,6 +39,11 @@
39 with a command like this: 39 with a command like this:
40 <pre>ffmpeg -i input.ts -acodec copy -vcodec copy output.flv</pre> 40 <pre>ffmpeg -i input.ts -acodec copy -vcodec copy output.flv</pre>
41 </p> 41 </p>
42 <p>
43 This page only compares FLVs files. There is
44 a <a href="mp4.html">similar utility</a> for testing the mp4
45 conversion.
46 </p>
42 </header> 47 </header>
43 <section> 48 <section>
44 <h2>Inputs</h2> 49 <h2>Inputs</h2>
......
...@@ -71,9 +71,15 @@ var ...@@ -71,9 +71,15 @@ var
71 71
72 return result; 72 return result;
73 }, 73 },
74 mdat: function(data) {
75 return {
76 byteLength: data.byteLength
77 };
78 },
74 mdhd: function(data) { 79 mdhd: function(data) {
75 var 80 var
76 view = new DataView(data.buffer, data.byteOffset, data.byteLength), 81 view = new DataView(data.buffer, data.byteOffset, data.byteLength),
82 i = 4,
77 language, 83 language,
78 result = { 84 result = {
79 version: view.getUint8(0), 85 version: view.getUint8(0),
...@@ -81,14 +87,27 @@ var ...@@ -81,14 +87,27 @@ var
81 language: '' 87 language: ''
82 }; 88 };
83 if (result.version === 1) { 89 if (result.version === 1) {
84 result.creationTime = view.getUint32(8); // truncating top 4 bytes 90 i += 4;
85 result.modificationTime = view.getUint32(16); // truncating top 4 bytes 91 result.creationTime = view.getUint32(i); // truncating top 4 bytes
86 result.timescale = view.getUint32(20); 92 i += 8;
87 result.duration = view.getUint32(28); // truncating top 4 bytes 93 result.modificationTime = view.getUint32(i); // truncating top 4 bytes
94 i += 4;
95 result.timescale = view.getUint32(i);
96 i += 8;
97 result.duration = view.getUint32(i); // truncating top 4 bytes
98 } else {
99 result.creationTime = view.getUint32(i);
100 i += 4;
101 result.modificationTime = view.getUint32(i);
102 i += 4;
103 result.timescale = view.getUint32(i);
104 i += 4;
105 result.duration = view.getUint32(i);
88 } 106 }
107 i += 4;
89 // language is stored as an ISO-639-2/T code in an array of three 5-bit fields 108 // language is stored as an ISO-639-2/T code in an array of three 5-bit fields
90 // each field is the packed difference between its ASCII value and 0x60 109 // each field is the packed difference between its ASCII value and 0x60
91 language = view.getUint16(32); 110 language = view.getUint16(i);
92 result.language += String.fromCharCode((language >> 10) + 0x60); 111 result.language += String.fromCharCode((language >> 10) + 0x60);
93 result.language += String.fromCharCode(((language & 0x03c0) >> 5) + 0x60); 112 result.language += String.fromCharCode(((language & 0x03c0) >> 5) + 0x60);
94 result.language += String.fromCharCode((language & 0x1f) + 0x60); 113 result.language += String.fromCharCode((language & 0x1f) + 0x60);
...@@ -113,21 +132,43 @@ var ...@@ -113,21 +132,43 @@ var
113 mvhd: function(data) { 132 mvhd: function(data) {
114 var 133 var
115 view = new DataView(data.buffer, data.byteOffset, data.byteLength), 134 view = new DataView(data.buffer, data.byteOffset, data.byteLength),
135 i = 4,
116 result = { 136 result = {
117 version: view.getUint8(0), 137 version: view.getUint8(0),
118 flags: new Uint8Array(data.subarray(1, 4)), 138 flags: new Uint8Array(data.subarray(1, 4))
119 // convert fixed-point, base 16 back to a number
120 rate: view.getUint16(32) + (view.getUint16(34) / 16),
121 volume: view.getUint8(36) + (view.getUint8(37) / 8),
122 matrix: new Uint32Array(data.subarray(48, 84)),
123 nextTrackId: view.getUint32(108)
124 }; 139 };
140
125 if (result.version === 1) { 141 if (result.version === 1) {
126 result.creationTime = view.getUint32(8); // truncating top 4 bytes 142 i += 4;
127 result.modificationTime = view.getUint32(16); // truncating top 4 bytes 143 result.creationTime = view.getUint32(i); // truncating top 4 bytes
128 result.timescale = view.getUint32(20); 144 i += 8;
129 result.duration = view.getUint32(28); // truncating top 4 bytes 145 result.modificationTime = view.getUint32(i); // truncating top 4 bytes
146 i += 4;
147 result.timescale = view.getUint32(i);
148 i += 8
149 result.duration = view.getUint32(i); // truncating top 4 bytes
150 } else {
151 result.creationTime = view.getUint32(i);
152 i += 4;
153 result.modificationTime = view.getUint32(i);
154 i += 4;
155 result.timescale = view.getUint32(i);
156 i += 4;
157 result.duration = view.getUint32(i);
130 } 158 }
159 i += 4;
160
161 // convert fixed-point, base 16 back to a number
162 result.rate = view.getUint16(i) + (view.getUint16(i + 2) / 16);
163 i += 4;
164 result.volume = view.getUint8(i) + (view.getUint8(i + 1) / 8);
165 i += 2;
166 i += 2;
167 i += 2 * 4;
168 result.matrix = new Uint32Array(data.subarray(i, i + (9 * 4)));
169 i += 9 * 4;
170 i += 6 * 4;
171 result.nextTrackId = view.getUint32(i);
131 return result; 172 return result;
132 }, 173 },
133 pdin: function(data) { 174 pdin: function(data) {
...@@ -172,23 +213,46 @@ var ...@@ -172,23 +213,46 @@ var
172 tkhd: function(data) { 213 tkhd: function(data) {
173 var 214 var
174 view = new DataView(data.buffer, data.byteOffset, data.byteLength), 215 view = new DataView(data.buffer, data.byteOffset, data.byteLength),
216 i = 4,
175 result = { 217 result = {
176 version: view.getUint8(0), 218 version: view.getUint8(0),
177 flags: new Uint8Array(data.subarray(1, 4)), 219 flags: new Uint8Array(data.subarray(1, 4)),
178 layer: view.getUint16(44),
179 alternateGroup: view.getUint16(46),
180 // convert fixed-point, base 16 back to a number
181 volume: view.getUint8(48) + (view.getUint8(49) / 8),
182 matrix: new Uint32Array(data.subarray(52, 88)),
183 width: view.getUint32(88),
184 height: view.getUint32(92)
185 }; 220 };
186 if (result.version === 1) { 221 if (result.version === 1) {
187 result.creationTime = view.getUint32(8); // truncating top 4 bytes 222 i += 4;
188 result.modificationTime = view.getUint32(16); // truncating top 4 bytes 223 result.creationTime = view.getUint32(i); // truncating top 4 bytes
189 result.trackId = view.getUint32(20); 224 i += 8;
190 result.duration = view.getUint32(32); // truncating top 4 bytes 225 result.modificationTime = view.getUint32(i); // truncating top 4 bytes
226 i += 4;
227 result.trackId = view.getUint32(i);
228 i += 4;
229 i += 8;
230 result.duration = view.getUint32(i); // truncating top 4 bytes
231 } else {
232 result.creationTime = view.getUint32(i);
233 i += 4;
234 result.modificationTime = view.getUint32(i);
235 i += 4;
236 result.trackId = view.getUint32(i);
237 i += 4;
238 i += 4;
239 result.duration = view.getUint32(i);
191 } 240 }
241 i += 4;
242 i += 2 * 4;
243 result.layer = view.getUint16(i);
244 i += 2;
245 result.alternateGroup = view.getUint16(i);
246 i += 2;
247 // convert fixed-point, base 16 back to a number
248 result.volume = view.getUint8(i) + (view.getUint8(i + 1) / 8);
249 i += 2;
250 i += 2;
251 result.matrix = new Uint32Array(data.subarray(i, i + (9 * 4)));
252 i += 9 * 4;
253 result.width = view.getUint32(i);
254 i += 4;
255 result.height = view.getUint32(i);
192 return result; 256 return result;
193 } 257 }
194 }; 258 };
......
1 <!DOCTYPE html>
2 <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
3 <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
4 <!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
5 <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
6 <head>
7 <meta charset="utf-8">
8 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
9 <title></title>
10 <meta name="description" content="">
11 <meta name="viewport" content="width=device-width">
12
13 <link rel="stylesheet" href="css/normalize.min.css">
14 <link rel="stylesheet" href="css/main.css">
15
16 <script src="js/vendor/modernizr-2.6.2.min.js"></script>
17 </head>
18 <body>
19 <!--[if lt IE 7]>
20 <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>
21 <![endif]-->
22
23 <div class="header-container">
24 <header class="wrapper clearfix">
25 <h1 class="title">Transmux Analyzer</h1>
26 </header>
27 </div>
28
29 <div class="main-container">
30 <div class="main wrapper clearfix">
31
32 <article>
33 <header>
34 <p>
35 This page can help you inspect the results of the
36 transmuxing to mp4 files performed by videojs-contrib-hls.
37 </p>
38 <p>
39 Looking for the FLV tool? Check out
40 the <a href="index.html">FLV utility</a>.
41 </p>
42 </header>
43 <section>
44 <h2>Inputs</h2>
45 <form id="inputs">
46 <label>
47 Your original MP2T segment:
48 <input type="file" id="original">
49 </label>
50 <label>
51 A working, MP4 version of the underlying stream
52 produced by another tool:
53 <input type="file" id="working">
54 </label>
55 </form>
56 </section>
57 <section>
58 <h2>Comparison</h2>
59 <div class="result-wrapper">
60 <h3>videojs-contrib-hls</h3>
61 <ol class="vjs-boxes">
62 </ol>
63 </div>
64 <div class="result-wrapper">
65 <h3>Working</h3>
66 <pre class="working-boxes">
67 </pre>
68 </div>
69 </section>
70 <section>
71 <h2>Results</h2>
72 <div class="result-wrapper">
73 <h3>videojs-contrib-hls</h3>
74 <div class="vjs-hls-output result">
75 <p>
76 The results of transmuxing your input file with
77 videojs-contrib-hls will show up here.
78 </p>
79 </div>
80 </div>
81 <div class="result-wrapper">
82 <h3>Working</h3>
83 <div class="working-output result">
84 <p>
85 The "good" version of the file will show up here.
86 </p>
87 </div>
88 </div>
89 </section>
90 </article>
91
92 </div> <!-- #main -->
93 </div> <!-- #main-container -->
94
95 <div class="footer-container">
96 <footer class="wrapper">
97 <h3>footer</h3>
98 </footer>
99 </div>
100
101
102 <script>
103 window.videojs = window.videojs || {
104 Hls: {}
105 };
106 </script>
107 <script src="js/mp4-inspector.js"></script>
108
109 <script src="../../src/bin-utils.js"></script>
110 <script>
111 var inputs = document.getElementById('inputs'),
112 original = document.getElementById('original'),
113 working = document.getElementById('working'),
114
115 vjsBoxes = document.querySelector('.vjs-boxes'),
116 workingBoxes = document.querySelector('.working-boxes'),
117
118 vjsOutput = document.querySelector('.vjs-hls-output'),
119 workingOutput = document.querySelector('.working-output'),
120
121 tagTypes = {
122 0x08: 'audio',
123 0x09: 'video',
124 0x12: 'metadata'
125 };
126
127 videojs.log = console.log.bind(console);
128
129 original.addEventListener('change', function() {
130 var reader = new FileReader();
131 reader.addEventListener('loadend', function() {
132
133 var mp2t = new Uint8Array(reader.result);
134
135 // clear old boxes info
136 vjsBoxes.innerHTML = '';
137
138 // write out the result
139 // vjsOutput.innerHTML = hex;
140 });
141 reader.readAsArrayBuffer(this.files[0]);
142 }, false);
143
144 working.addEventListener('change', function() {
145 var reader = new FileReader();
146 reader.addEventListener('loadend', function() {
147 var hex = '<pre>',
148 bytes = new Uint8Array(reader.result);
149
150 // clear old box info
151 workingBoxes.innerHTML = JSON.stringify(videojs.inspectMp4(bytes), null, ' ');
152
153 // output the hex dump
154 hex += videojs.Hls.utils.hexDump(bytes);
155 hex += '</pre>';
156 workingOutput.innerHTML = hex;
157 });
158 reader.readAsArrayBuffer(this.files[0]);
159 }, false);
160 </script>
161 </body>
162 </html>