Implement mp4 inspector and tests
As preparation for building a tool to transform mp2t files into mp4s, create a javascript tool that parses an mp4 file. Remove the local qunit so that karma and direct qunit testing happens with the same version of the library. Force the tech to run during tests so that Safari doesn't use native HLS.
Showing
8 changed files
with
243 additions
and
2 deletions
test/fixtures/gear4-0.mp4.js
0 → 100644
This diff could not be displayed because it is too large.
test/fixtures/gear4-1.mp4.js
0 → 100644
This diff could not be displayed because it is too large.
... | @@ -93,7 +93,8 @@ module.exports = function(config) { | ... | @@ -93,7 +93,8 @@ module.exports = function(config) { |
93 | '../tmp/expected.js', | 93 | '../tmp/expected.js', |
94 | 'tsSegment-bc.js', | 94 | 'tsSegment-bc.js', |
95 | '../src/bin-utils.js', | 95 | '../src/bin-utils.js', |
96 | '../test/*.js' | 96 | '../test/*.js', |
97 | '../test/muxer/js/mp4-inspector.js' | ||
97 | ], | 98 | ], |
98 | 99 | ||
99 | plugins: [ | 100 | plugins: [ | ... | ... |
... | @@ -58,7 +58,8 @@ module.exports = function(config) { | ... | @@ -58,7 +58,8 @@ module.exports = function(config) { |
58 | '../tmp/expected.js', | 58 | '../tmp/expected.js', |
59 | 'tsSegment-bc.js', | 59 | 'tsSegment-bc.js', |
60 | '../src/bin-utils.js', | 60 | '../src/bin-utils.js', |
61 | '../test/*.js' | 61 | '../test/*.js', |
62 | '../test/muxer/js/mp4-inspector.js' | ||
62 | ], | 63 | ], |
63 | 64 | ||
64 | plugins: [ | 65 | plugins: [ | ... | ... |
test/mp4-inspector_test.js
0 → 100644
This diff is collapsed.
Click to expand it.
test/muxer/js/mp4-inspector.js
0 → 100644
1 | (function(window, videojs) { | ||
2 | 'use strict'; | ||
3 | |||
4 | var | ||
5 | DataView = window.DataView, | ||
6 | /** | ||
7 | * Returns the string representation of an ASCII encoded four byte buffer. | ||
8 | * @param buffer {Uint8Array} a four-byte buffer to translate | ||
9 | * @return {string} the corresponding string | ||
10 | */ | ||
11 | parseType = function(buffer) { | ||
12 | var result = ''; | ||
13 | result += String.fromCharCode(buffer[0]); | ||
14 | result += String.fromCharCode(buffer[1]); | ||
15 | result += String.fromCharCode(buffer[2]); | ||
16 | result += String.fromCharCode(buffer[3]); | ||
17 | return result; | ||
18 | }, | ||
19 | |||
20 | // registry of handlers for individual mp4 box types | ||
21 | parse = { | ||
22 | ftyp: function(data) { | ||
23 | var | ||
24 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
25 | result = { | ||
26 | majorBrand: view.getUint32(0), | ||
27 | minorVersion: view.getUint32(4), | ||
28 | compatibleBrands: [] | ||
29 | }, | ||
30 | i = 8; | ||
31 | while (i < data.byteLength) { | ||
32 | result.compatibleBrands.push(view.getUint32(i)); | ||
33 | i += 4; | ||
34 | } | ||
35 | return result; | ||
36 | }, | ||
37 | dinf: function(data) { | ||
38 | return { | ||
39 | boxes: videojs.inspectMp4(data) | ||
40 | }; | ||
41 | }, | ||
42 | dref: function(data) { | ||
43 | return { | ||
44 | dataReferences: [] | ||
45 | }; | ||
46 | }, | ||
47 | hdlr: function(data) { | ||
48 | var | ||
49 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
50 | language, | ||
51 | result = { | ||
52 | version: view.getUint8(0), | ||
53 | flags: new Uint8Array(data.subarray(1, 4)), | ||
54 | handlerType: parseType(data.subarray(8, 12)), | ||
55 | name: '' | ||
56 | }, | ||
57 | i = 8; | ||
58 | |||
59 | // parse out the name field | ||
60 | for (i = 24; i < data.byteLength; i++) { | ||
61 | if (data[i] === 0x00) { | ||
62 | // the name field is null-terminated | ||
63 | i++; | ||
64 | break; | ||
65 | } | ||
66 | result.name += String.fromCharCode(data[i]); | ||
67 | } | ||
68 | // decode UTF-8 to javascript's internal representation | ||
69 | // see http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html | ||
70 | result.name = window.decodeURIComponent(window.escape(result.name)); | ||
71 | |||
72 | return result; | ||
73 | }, | ||
74 | mdhd: function(data) { | ||
75 | var | ||
76 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
77 | language, | ||
78 | result = { | ||
79 | version: view.getUint8(0), | ||
80 | flags: new Uint8Array(data.subarray(1, 4)), | ||
81 | language: '' | ||
82 | }; | ||
83 | if (result.version === 1) { | ||
84 | result.creationTime = view.getUint32(8); // truncating top 4 bytes | ||
85 | result.modificationTime = view.getUint32(16); // truncating top 4 bytes | ||
86 | result.timescale = view.getUint32(20); | ||
87 | result.duration = view.getUint32(28); // truncating top 4 bytes | ||
88 | } | ||
89 | // 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 | ||
91 | language = view.getUint16(32); | ||
92 | result.language += String.fromCharCode((language >> 10) + 0x60); | ||
93 | result.language += String.fromCharCode(((language & 0x03c0) >> 5) + 0x60); | ||
94 | result.language += String.fromCharCode((language & 0x1f) + 0x60); | ||
95 | |||
96 | return result; | ||
97 | }, | ||
98 | mdia: function(data) { | ||
99 | return { | ||
100 | boxes: videojs.inspectMp4(data) | ||
101 | }; | ||
102 | }, | ||
103 | minf: function(data) { | ||
104 | return { | ||
105 | boxes: videojs.inspectMp4(data) | ||
106 | }; | ||
107 | }, | ||
108 | moov: function(data) { | ||
109 | return { | ||
110 | boxes: videojs.inspectMp4(data) | ||
111 | }; | ||
112 | }, | ||
113 | mvhd: function(data) { | ||
114 | var | ||
115 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
116 | result = { | ||
117 | version: view.getUint8(0), | ||
118 | 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 | }; | ||
125 | if (result.version === 1) { | ||
126 | result.creationTime = view.getUint32(8); // truncating top 4 bytes | ||
127 | result.modificationTime = view.getUint32(16); // truncating top 4 bytes | ||
128 | result.timescale = view.getUint32(20); | ||
129 | result.duration = view.getUint32(28); // truncating top 4 bytes | ||
130 | } | ||
131 | return result; | ||
132 | }, | ||
133 | pdin: function(data) { | ||
134 | var view = new DataView(data.buffer, data.byteOffset, data.byteLength); | ||
135 | return { | ||
136 | version: view.getUint8(0), | ||
137 | flags: new Uint8Array(data.subarray(1, 4)), | ||
138 | rate: view.getUint32(4), | ||
139 | initialDelay: view.getUint32(8) | ||
140 | }; | ||
141 | }, | ||
142 | trak: function(data) { | ||
143 | return { | ||
144 | boxes: videojs.inspectMp4(data) | ||
145 | }; | ||
146 | }, | ||
147 | stbl: function(data) { | ||
148 | return { | ||
149 | boxes: videojs.inspectMp4(data) | ||
150 | }; | ||
151 | }, | ||
152 | stco: function(data) { | ||
153 | return { | ||
154 | chunkOffsets: [] | ||
155 | }; | ||
156 | }, | ||
157 | stsc: function(data) { | ||
158 | return { | ||
159 | sampleToChunks: [] | ||
160 | }; | ||
161 | }, | ||
162 | stsd: function(data) { | ||
163 | return { | ||
164 | sampleDescriptions: [] | ||
165 | }; | ||
166 | }, | ||
167 | stts: function(data) { | ||
168 | return { | ||
169 | timeToSamples: [] | ||
170 | }; | ||
171 | }, | ||
172 | tkhd: function(data) { | ||
173 | var | ||
174 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
175 | result = { | ||
176 | version: view.getUint8(0), | ||
177 | 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 | }; | ||
186 | if (result.version === 1) { | ||
187 | result.creationTime = view.getUint32(8); // truncating top 4 bytes | ||
188 | result.modificationTime = view.getUint32(16); // truncating top 4 bytes | ||
189 | result.trackId = view.getUint32(20); | ||
190 | result.duration = view.getUint32(32); // truncating top 4 bytes | ||
191 | } | ||
192 | return result; | ||
193 | } | ||
194 | }; | ||
195 | |||
196 | /** | ||
197 | * Return a javascript array of box objects parsed from an ISO base | ||
198 | * media file. | ||
199 | * @param data {Uint8Array} the binary data of the media to be inspected | ||
200 | * @return {array} a javascript array of potentially nested box objects | ||
201 | */ | ||
202 | videojs.inspectMp4 = function(data) { | ||
203 | var | ||
204 | i = 0, | ||
205 | result = [], | ||
206 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
207 | size, | ||
208 | type, | ||
209 | end, | ||
210 | box; | ||
211 | |||
212 | while (i < data.byteLength) { | ||
213 | // parse box data | ||
214 | size = view.getUint32(i), | ||
215 | type = parseType(data.subarray(i + 4, i + 8)); | ||
216 | end = size > 1 ? i + size : data.byteLength; | ||
217 | |||
218 | // parse type-specific data | ||
219 | box = (parse[type] || function(data) { | ||
220 | return { | ||
221 | data: data | ||
222 | }; | ||
223 | })(data.subarray(i + 8, end)); | ||
224 | box.size = size; | ||
225 | box.type = type; | ||
226 | |||
227 | // store this box and move to the next | ||
228 | result.push(box); | ||
229 | i = end; | ||
230 | } | ||
231 | return result; | ||
232 | }; | ||
233 | })(window, window.videojs); |
... | @@ -42,6 +42,9 @@ | ... | @@ -42,6 +42,9 @@ |
42 | <script src="tsSegment-bc.js"></script> | 42 | <script src="tsSegment-bc.js"></script> |
43 | <script src="../src/bin-utils.js"></script> | 43 | <script src="../src/bin-utils.js"></script> |
44 | 44 | ||
45 | <!-- mp4 utilities --> | ||
46 | <script src="muxer/js/mp4-inspector.js"></script> | ||
47 | |||
45 | <!-- Test cases --> | 48 | <!-- Test cases --> |
46 | <script> | 49 | <script> |
47 | module('environment'); | 50 | module('environment'); |
... | @@ -60,6 +63,7 @@ | ... | @@ -60,6 +63,7 @@ |
60 | <script src="playlist_test.js"></script> | 63 | <script src="playlist_test.js"></script> |
61 | <script src="playlist-loader_test.js"></script> | 64 | <script src="playlist-loader_test.js"></script> |
62 | <script src="decrypter_test.js"></script> | 65 | <script src="decrypter_test.js"></script> |
66 | <script src="mp4-inspector_test.js"></script> | ||
63 | </head> | 67 | </head> |
64 | <body> | 68 | <body> |
65 | <div id="qunit"></div> | 69 | <div id="qunit"></div> | ... | ... |
... | @@ -212,7 +212,9 @@ module('HLS', { | ... | @@ -212,7 +212,9 @@ module('HLS', { |
212 | oldClearTimeout = window.clearTimeout; | 212 | oldClearTimeout = window.clearTimeout; |
213 | oldGlobalOptions = window.videojs.getGlobalOptions(); | 213 | oldGlobalOptions = window.videojs.getGlobalOptions(); |
214 | 214 | ||
215 | // force the HLS tech to run | ||
215 | oldNativeHlsSupport = videojs.Hls.supportsNativeHls; | 216 | oldNativeHlsSupport = videojs.Hls.supportsNativeHls; |
217 | videojs.Hls.supportsNativeHls = false; | ||
216 | 218 | ||
217 | oldDecrypt = videojs.Hls.Decrypter; | 219 | oldDecrypt = videojs.Hls.Decrypter; |
218 | videojs.Hls.Decrypter = function() {}; | 220 | videojs.Hls.Decrypter = function() {}; | ... | ... |
-
Please register or sign in to post a comment