cb74d9c0 by David LaPalomento

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.
1 parent 23a2bf27
...@@ -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);
......