Add inspector support for moof boxes
Create test cases for a moof box and sample mfhd, traf, and tfhd sub-boxes. Fix the source buffer codec so that the debug code on the mp4 muxer page works. The issue was that specifying an audio codec but then failing to include an audio track in the init segment generated a media decode error.
Showing
3 changed files
with
147 additions
and
41 deletions
... | @@ -622,6 +622,53 @@ test('can parse an stsz', function() { | ... | @@ -622,6 +622,53 @@ test('can parse an stsz', function() { |
622 | }]); | 622 | }]); |
623 | }); | 623 | }); |
624 | 624 | ||
625 | test('can parse a moof', function() { | ||
626 | var data = box('moof', | ||
627 | box('mfhd', | ||
628 | 0x00, // version | ||
629 | 0x00, 0x00, 0x00, // flags | ||
630 | 0x00, 0x00, 0x00, 0x04), // sequence_number | ||
631 | box('traf', | ||
632 | box('tfhd', | ||
633 | 0x00, // version | ||
634 | 0x00, 0x00, 0x3b, // flags | ||
635 | 0x00, 0x00, 0x00, 0x01, // track_ID | ||
636 | 0x00, 0x00, 0x00, 0x00, | ||
637 | 0x00, 0x00, 0x00, 0x01, // base_data_offset | ||
638 | 0x00, 0x00, 0x00, 0x02, // sample_description_index | ||
639 | 0x00, 0x00, 0x00, 0x03, // default_sample_duration, | ||
640 | 0x00, 0x00, 0x00, 0x04, // default_sample_size | ||
641 | 0x00, 0x00, 0x00, 0x05))); // default_sample_flags | ||
642 | deepEqual(videojs.inspectMp4(new Uint8Array(data)), | ||
643 | [{ | ||
644 | type: 'moof', | ||
645 | size: 72, | ||
646 | boxes: [{ | ||
647 | type: 'mfhd', | ||
648 | size: 16, | ||
649 | version: 0, | ||
650 | flags: new Uint8Array([0, 0, 0]), | ||
651 | sequenceNumber: 4 | ||
652 | }, | ||
653 | { | ||
654 | type: 'traf', | ||
655 | size: 48, | ||
656 | boxes: [{ | ||
657 | type: 'tfhd', | ||
658 | version: 0, | ||
659 | size: 40, | ||
660 | flags: new Uint8Array([0, 0, 0x3b]), | ||
661 | trackId: 1, | ||
662 | baseDataOffset: 1, | ||
663 | sampleDescriptionIndex: 2, | ||
664 | defaultSampleDuration: 3, | ||
665 | defaultSampleSize: 4, | ||
666 | defaultSampleFlags: 5 | ||
667 | }] | ||
668 | }] | ||
669 | }]); | ||
670 | }); | ||
671 | |||
625 | test('can parse a series of boxes', function() { | 672 | test('can parse a series of boxes', function() { |
626 | var ftyp = [ | 673 | var ftyp = [ |
627 | 0x00, 0x00, 0x00, 0x18 // size 4 * 6 = 24 | 674 | 0x00, 0x00, 0x00, 0x18 // size 4 * 6 = 24 | ... | ... |
... | @@ -185,11 +185,26 @@ var | ... | @@ -185,11 +185,26 @@ var |
185 | boxes: videojs.inspectMp4(data) | 185 | boxes: videojs.inspectMp4(data) |
186 | }; | 186 | }; |
187 | }, | 187 | }, |
188 | mfhd: function(data) { | ||
189 | return { | ||
190 | version: data[0], | ||
191 | flags: new Uint8Array(data.subarray(1, 4)), | ||
192 | sequenceNumber: (data[4] << 24) | | ||
193 | (data[5] << 16) | | ||
194 | (data[6] << 8) | | ||
195 | (data[7]) | ||
196 | }; | ||
197 | }, | ||
188 | minf: function(data) { | 198 | minf: function(data) { |
189 | return { | 199 | return { |
190 | boxes: videojs.inspectMp4(data) | 200 | boxes: videojs.inspectMp4(data) |
191 | }; | 201 | }; |
192 | }, | 202 | }, |
203 | moof: function(data) { | ||
204 | return { | ||
205 | boxes: videojs.inspectMp4(data) | ||
206 | }; | ||
207 | }, | ||
193 | moov: function(data) { | 208 | moov: function(data) { |
194 | return { | 209 | return { |
195 | boxes: videojs.inspectMp4(data) | 210 | boxes: videojs.inspectMp4(data) |
... | @@ -251,28 +266,6 @@ var | ... | @@ -251,28 +266,6 @@ var |
251 | initialDelay: view.getUint32(8) | 266 | initialDelay: view.getUint32(8) |
252 | }; | 267 | }; |
253 | }, | 268 | }, |
254 | trak: function(data) { | ||
255 | return { | ||
256 | boxes: videojs.inspectMp4(data) | ||
257 | }; | ||
258 | }, | ||
259 | trex: function(data) { | ||
260 | var view = new DataView(data.buffer, data.byteOffset, data.byteLength); | ||
261 | return { | ||
262 | version: data[0], | ||
263 | flags: new Uint8Array(data.subarray(1, 4)), | ||
264 | trackId: view.getUint32(4), | ||
265 | defaultSampleDescriptionIndex: view.getUint32(8), | ||
266 | defaultSampleDuration: view.getUint32(12), | ||
267 | defaultSampleSize: view.getUint32(16), | ||
268 | sampleDependsOn: data[20] & 0x03, | ||
269 | sampleIsDependedOn: (data[21] & 0xc0) >> 6, | ||
270 | sampleHasRedundancy: (data[21] & 0x30) >> 4, | ||
271 | samplePaddingValue: (data[21] & 0x0e) >> 1, | ||
272 | sampleIsDifferenceSample: !!(data[21] & 0x01), | ||
273 | sampleDegradationPriority: view.getUint16(22) | ||
274 | }; | ||
275 | }, | ||
276 | stbl: function(data) { | 269 | stbl: function(data) { |
277 | return { | 270 | return { |
278 | boxes: videojs.inspectMp4(data) | 271 | boxes: videojs.inspectMp4(data) |
... | @@ -353,6 +346,44 @@ var | ... | @@ -353,6 +346,44 @@ var |
353 | } | 346 | } |
354 | return result; | 347 | return result; |
355 | }, | 348 | }, |
349 | tfhd: function(data) { | ||
350 | var | ||
351 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
352 | result = { | ||
353 | version: data[0], | ||
354 | flags: new Uint8Array(data.subarray(1, 4)), | ||
355 | trackId: view.getUint32(4) | ||
356 | }, | ||
357 | baseDataOffsetPresent = result.flags[2] & 0x01, | ||
358 | sampleDescriptionIndexPresent = result.flags[2] & 0x02, | ||
359 | defaultSampleDurationPresent = result.flags[2] & 0x08, | ||
360 | defaultSampleSizePresent = result.flags[2] & 0x10, | ||
361 | defaultSampleFlagsPresent = result.flags[2] & 0x20, | ||
362 | i; | ||
363 | |||
364 | i = 8; | ||
365 | if (baseDataOffsetPresent) { | ||
366 | i += 4; // truncate top 4 bytes | ||
367 | result.baseDataOffset = view.getUint32(12); | ||
368 | i += 4; | ||
369 | } | ||
370 | if (sampleDescriptionIndexPresent) { | ||
371 | result.sampleDescriptionIndex = view.getUint32(i); | ||
372 | i += 4; | ||
373 | } | ||
374 | if (defaultSampleDurationPresent) { | ||
375 | result.defaultSampleDuration = view.getUint32(i); | ||
376 | i += 4; | ||
377 | } | ||
378 | if (defaultSampleSizePresent) { | ||
379 | result.defaultSampleSize = view.getUint32(i); | ||
380 | i += 4; | ||
381 | } | ||
382 | if (defaultSampleFlagsPresent) { | ||
383 | result.defaultSampleFlags = view.getUint32(i); | ||
384 | } | ||
385 | return result; | ||
386 | }, | ||
356 | tkhd: function(data) { | 387 | tkhd: function(data) { |
357 | var | 388 | var |
358 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | 389 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), |
... | @@ -398,6 +429,33 @@ var | ... | @@ -398,6 +429,33 @@ var |
398 | result.height = view.getUint16(i) + (view.getUint16(i + 2) / 16); | 429 | result.height = view.getUint16(i) + (view.getUint16(i + 2) / 16); |
399 | return result; | 430 | return result; |
400 | }, | 431 | }, |
432 | traf: function(data) { | ||
433 | return { | ||
434 | boxes: videojs.inspectMp4(data) | ||
435 | }; | ||
436 | }, | ||
437 | trak: function(data) { | ||
438 | return { | ||
439 | boxes: videojs.inspectMp4(data) | ||
440 | }; | ||
441 | }, | ||
442 | trex: function(data) { | ||
443 | var view = new DataView(data.buffer, data.byteOffset, data.byteLength); | ||
444 | return { | ||
445 | version: data[0], | ||
446 | flags: new Uint8Array(data.subarray(1, 4)), | ||
447 | trackId: view.getUint32(4), | ||
448 | defaultSampleDescriptionIndex: view.getUint32(8), | ||
449 | defaultSampleDuration: view.getUint32(12), | ||
450 | defaultSampleSize: view.getUint32(16), | ||
451 | sampleDependsOn: data[20] & 0x03, | ||
452 | sampleIsDependedOn: (data[21] & 0xc0) >> 6, | ||
453 | sampleHasRedundancy: (data[21] & 0x30) >> 4, | ||
454 | samplePaddingValue: (data[21] & 0x0e) >> 1, | ||
455 | sampleIsDifferenceSample: !!(data[21] & 0x01), | ||
456 | sampleDegradationPriority: view.getUint16(22) | ||
457 | }; | ||
458 | }, | ||
401 | 'url ': function(data) { | 459 | 'url ': function(data) { |
402 | return { | 460 | return { |
403 | version: data[0], | 461 | version: data[0], | ... | ... |
... | @@ -146,6 +146,26 @@ | ... | @@ -146,6 +146,26 @@ |
146 | 146 | ||
147 | // XXX media source testing | 147 | // XXX media source testing |
148 | 148 | ||
149 | var video = document.createElement('video'); | ||
150 | var mediaSource = new MediaSource(); | ||
151 | mediaSource.addEventListener('sourceopen', function() { | ||
152 | var buffer = mediaSource.addSourceBuffer('video/mp4;codecs=avc1.4d400d'); | ||
153 | buffer.addEventListener('updatestart', console.log.bind(console)); | ||
154 | buffer.addEventListener('updateend', console.log.bind(console)); | ||
155 | buffer.addEventListener('error', console.log.bind(console)); | ||
156 | buffer.appendBuffer(videojs.mp4.initSegment()); | ||
157 | console.log('done'); | ||
158 | window.vjsMediaSource = mediaSource; | ||
159 | window.vjsSourceBuffer = buffer; | ||
160 | window.vjsVideo = video; | ||
161 | }); | ||
162 | mediaSource.addEventListener('error', console.log.bind(console)); | ||
163 | mediaSource.addEventListener('opened', console.log.bind(console)); | ||
164 | mediaSource.addEventListener('closed', console.log.bind(console)); | ||
165 | mediaSource.addEventListener('sourceended', console.log.bind(console)); | ||
166 | video.src = URL.createObjectURL(mediaSource); | ||
167 | video.addEventListener('error', console.log.bind(console)); | ||
168 | |||
149 | vjsBoxes.innerHTML = JSON.stringify(videojs.inspectMp4(videojs.mp4.initSegment()), null, ' '); | 169 | vjsBoxes.innerHTML = JSON.stringify(videojs.inspectMp4(videojs.mp4.initSegment()), null, ' '); |
150 | }); | 170 | }); |
151 | reader.readAsArrayBuffer(this.files[0]); | 171 | reader.readAsArrayBuffer(this.files[0]); |
... | @@ -168,25 +188,6 @@ | ... | @@ -168,25 +188,6 @@ |
168 | 188 | ||
169 | // XXX Media Sources Testing | 189 | // XXX Media Sources Testing |
170 | 190 | ||
171 | var video = document.createElement('video'); | ||
172 | var mediaSource = new MediaSource(); | ||
173 | mediaSource.addEventListener('sourceopen', function() { | ||
174 | var buffer = mediaSource.addSourceBuffer('video/mp4;codecs=avc1.4d0020,mp4a.40.2'); | ||
175 | buffer.addEventListener('updatestart', console.log.bind(console)); | ||
176 | buffer.addEventListener('updateend', console.log.bind(console)); | ||
177 | buffer.addEventListener('error', console.log.bind(console)); | ||
178 | buffer.appendBuffer(bytes); | ||
179 | console.log('done'); | ||
180 | window.vjsMediaSource = mediaSource; | ||
181 | window.vjsSourceBuffer = buffer; | ||
182 | window.vjsVideo = video; | ||
183 | }); | ||
184 | mediaSource.addEventListener('error', console.log.bind(console)); | ||
185 | mediaSource.addEventListener('opened', console.log.bind(console)); | ||
186 | mediaSource.addEventListener('closed', console.log.bind(console)); | ||
187 | mediaSource.addEventListener('sourceended', console.log.bind(console)); | ||
188 | video.src = URL.createObjectURL(mediaSource); | ||
189 | video.addEventListener('error', console.log.bind(console)); | ||
190 | }); | 191 | }); |
191 | reader.readAsArrayBuffer(this.files[0]); | 192 | reader.readAsArrayBuffer(this.files[0]); |
192 | }, false); | 193 | }, false); | ... | ... |
-
Please register or sign in to post a comment