e9ae4872 by David LaPalomento

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.
1 parent 9fa9fdde
This diff could not be displayed because it is too large.
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: [
......
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() {};
......