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.
Showing
4 changed files
with
357 additions
and
28 deletions
... | @@ -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 | }; | ... | ... |
test/muxer/mp4.html
0 → 100644
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> |
-
Please register or sign in to post a comment