Write track fragment metadata
Output real tfhd info. Prepare trun to accept sample information-- this still needs to be generated properly by the transmuxer.
Showing
5 changed files
with
132 additions
and
28 deletions
... | @@ -2,7 +2,7 @@ | ... | @@ -2,7 +2,7 @@ |
2 | 'use strict'; | 2 | 'use strict'; |
3 | 3 | ||
4 | var box, dinf, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd, trak, | 4 | var box, dinf, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd, trak, |
5 | tkhd, mdia, mdhd, hdlr, stbl, stsd, styp, trex, types, | 5 | tkhd, mdia, mdhd, hdlr, stbl, stsd, styp, traf, trex, trun, types, |
6 | MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR, AUDIO_HDLR, | 6 | MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR, AUDIO_HDLR, |
7 | HDLR_TYPES, VMHD, DREF, STCO, STSC, STSZ, STTS, Uint8Array, | 7 | HDLR_TYPES, VMHD, DREF, STCO, STSC, STSZ, STTS, Uint8Array, |
8 | DataView; | 8 | DataView; |
... | @@ -40,6 +40,7 @@ DataView = window.DataView; | ... | @@ -40,6 +40,7 @@ DataView = window.DataView; |
40 | tfhd: [], | 40 | tfhd: [], |
41 | traf: [], | 41 | traf: [], |
42 | trak: [], | 42 | trak: [], |
43 | trun: [], | ||
43 | trex: [], | 44 | trex: [], |
44 | tkhd: [], | 45 | tkhd: [], |
45 | vmhd: [] | 46 | vmhd: [] |
... | @@ -202,23 +203,16 @@ minf = function(track) { | ... | @@ -202,23 +203,16 @@ minf = function(track) { |
202 | }; | 203 | }; |
203 | moof = function(sequenceNumber, tracks) { | 204 | moof = function(sequenceNumber, tracks) { |
204 | var | 205 | var |
205 | trafCall = [], | 206 | trackFragments = [], |
206 | i = tracks.length; | 207 | i = tracks.length; |
207 | // build tfhd boxes for each track fragment | 208 | // build traf boxes for each track fragment |
208 | while (i--) { | 209 | while (i--) { |
209 | trafCall[i] = box(types.tfhd, new Uint8Array([ | 210 | trackFragments[i] = traf(tracks[i]); |
210 | 0x00, // version 0 | ||
211 | 0x00, 0x00, 0x00, // flags | ||
212 | (tracks[i].trackId & 0xFF000000) >> 24, | ||
213 | (tracks[i].trackId & 0xFF0000) >> 16, | ||
214 | (tracks[i].trackId & 0xFF00) >> 8, | ||
215 | (tracks[i].trackId & 0xFF), | ||
216 | ])); | ||
217 | } | 211 | } |
218 | trafCall.unshift(types.traf); | 212 | return box.apply(null, [ |
219 | return box(types.moof, | 213 | types.moof, |
220 | mfhd(sequenceNumber), | 214 | mfhd(sequenceNumber) |
221 | box.apply(null, trafCall)); | 215 | ].concat(trackFragments)); |
222 | }; | 216 | }; |
223 | /** | 217 | /** |
224 | * @param tracks... (optional) {array} the tracks associated with this movie | 218 | * @param tracks... (optional) {array} the tracks associated with this movie |
... | @@ -404,6 +398,19 @@ tkhd = function(track) { | ... | @@ -404,6 +398,19 @@ tkhd = function(track) { |
404 | ])); | 398 | ])); |
405 | }; | 399 | }; |
406 | 400 | ||
401 | traf = function(track) { | ||
402 | return box(types.traf, | ||
403 | box(types.tfhd, new Uint8Array([ | ||
404 | 0x00, // version 0 | ||
405 | 0x00, 0x00, 0x00, // flags | ||
406 | (track.id & 0xFF000000) >> 24, | ||
407 | (track.id & 0xFF0000) >> 16, | ||
408 | (track.id & 0xFF00) >> 8, | ||
409 | (track.id & 0xFF), | ||
410 | ])), | ||
411 | trun(track)); | ||
412 | }; | ||
413 | |||
407 | /** | 414 | /** |
408 | * Generate a track box. | 415 | * Generate a track box. |
409 | * @param track {object} a track definition | 416 | * @param track {object} a track definition |
... | @@ -431,6 +438,44 @@ trex = function(track) { | ... | @@ -431,6 +438,44 @@ trex = function(track) { |
431 | ])); | 438 | ])); |
432 | }; | 439 | }; |
433 | 440 | ||
441 | trun = function(track) { | ||
442 | var bytes, samples, sample, i; | ||
443 | |||
444 | samples = track.samples || []; | ||
445 | |||
446 | bytes = [ | ||
447 | 0x00, // version 0 | ||
448 | 0x00, 0x0f, 0x00, // flags | ||
449 | (samples.length & 0xFF000000) >>> 24, | ||
450 | (samples.length & 0xFF0000) >>> 16, | ||
451 | (samples.length & 0xFF00) >>> 8, | ||
452 | samples.length & 0xFF // sample_count | ||
453 | ]; | ||
454 | |||
455 | for (i = 0; i < samples.length; i++) { | ||
456 | sample = samples[i]; | ||
457 | bytes = bytes.concat([ | ||
458 | (sample.duration & 0xFF000000) >>> 24, | ||
459 | (sample.duration & 0xFF0000) >>> 16, | ||
460 | (sample.duration & 0xFF00) >>> 8, | ||
461 | sample.duration & 0xFF, // sample_duration | ||
462 | (sample.size & 0xFF000000) >>> 24, | ||
463 | (sample.size & 0xFF0000) >>> 16, | ||
464 | (sample.size & 0xFF00) >>> 8, | ||
465 | sample.size & 0xFF, // sample_size | ||
466 | (sample.flags & 0xFF000000) >>> 24, | ||
467 | (sample.flags & 0xFF0000) >>> 16, | ||
468 | (sample.flags & 0xFF00) >>> 8, | ||
469 | sample.flags & 0xFF, // sample_flags | ||
470 | (sample.compositionTimeOffset & 0xFF000000) >>> 24, | ||
471 | (sample.compositionTimeOffset & 0xFF0000) >>> 16, | ||
472 | (sample.compositionTimeOffset & 0xFF00) >>> 8, | ||
473 | sample.compositionTimeOffset & 0xFF, // sample_composition_time_offset | ||
474 | ]); | ||
475 | } | ||
476 | return box(types.trun, new Uint8Array(bytes)); | ||
477 | }; | ||
478 | |||
434 | window.videojs.mp4 = { | 479 | window.videojs.mp4 = { |
435 | ftyp: ftyp, | 480 | ftyp: ftyp, |
436 | mdat: mdat, | 481 | mdat: mdat, | ... | ... |
... | @@ -737,7 +737,7 @@ Transmuxer = function() { | ... | @@ -737,7 +737,7 @@ Transmuxer = function() { |
737 | flushVideo = function() { | 737 | flushVideo = function() { |
738 | var moof, mdat, boxes, i, data; | 738 | var moof, mdat, boxes, i, data; |
739 | 739 | ||
740 | moof = mp4.moof(sequenceNumber, []); | 740 | moof = mp4.moof(sequenceNumber, tracks); |
741 | 741 | ||
742 | // concatenate the video data and construct the mdat | 742 | // concatenate the video data and construct the mdat |
743 | data = new Uint8Array(videoSamplesSize); | 743 | data = new Uint8Array(videoSamplesSize); | ... | ... |
... | @@ -301,11 +301,21 @@ test('generates an initialization segment', function() { | ... | @@ -301,11 +301,21 @@ test('generates an initialization segment', function() { |
301 | test('generates a minimal moof', function() { | 301 | test('generates a minimal moof', function() { |
302 | var | 302 | var |
303 | data = mp4.moof(7, [{ | 303 | data = mp4.moof(7, [{ |
304 | trackId: 1 | 304 | id: 17, |
305 | }, { | 305 | samples: [{ |
306 | trackId: 2 | 306 | duration: 9000, |
307 | size: 10, | ||
308 | flags: 14, | ||
309 | compositionTimeOffset: 500 | ||
310 | }, { | ||
311 | duration: 10000, | ||
312 | size: 11, | ||
313 | flags: 9, | ||
314 | compositionTimeOffset: 1000 | ||
315 | }] | ||
307 | }]), | 316 | }]), |
308 | moof = videojs.inspectMp4(data); | 317 | moof = videojs.inspectMp4(data), |
318 | trun; | ||
309 | 319 | ||
310 | equal(moof.length, 1, 'generated one box'); | 320 | equal(moof.length, 1, 'generated one box'); |
311 | equal(moof[0].type, 'moof', 'generated a moof box'); | 321 | equal(moof[0].type, 'moof', 'generated a moof box'); |
... | @@ -313,10 +323,33 @@ test('generates a minimal moof', function() { | ... | @@ -313,10 +323,33 @@ test('generates a minimal moof', function() { |
313 | equal(moof[0].boxes[0].type, 'mfhd', 'generated an mfhd box'); | 323 | equal(moof[0].boxes[0].type, 'mfhd', 'generated an mfhd box'); |
314 | equal(moof[0].boxes[0].sequenceNumber, 7, 'included the sequence_number'); | 324 | equal(moof[0].boxes[0].sequenceNumber, 7, 'included the sequence_number'); |
315 | equal(moof[0].boxes[1].type, 'traf', 'generated a traf box'); | 325 | equal(moof[0].boxes[1].type, 'traf', 'generated a traf box'); |
316 | equal(moof[0].boxes[1].boxes.length, 2, 'generated two fragment headers'); | 326 | equal(moof[0].boxes[1].boxes.length, 2, 'generated track fragment info'); |
317 | equal(moof[0].boxes[1].boxes[0].type, 'tfhd', 'generated a tfhd box'); | 327 | equal(moof[0].boxes[1].boxes[0].type, 'tfhd', 'generated a tfhd box'); |
318 | equal(moof[0].boxes[1].boxes[0].trackId, 1, 'wrote the first track id'); | 328 | equal(moof[0].boxes[1].boxes[0].trackId, 17, 'wrote the first track id'); |
319 | equal(moof[0].boxes[1].boxes[1].trackId, 2, 'wrote the second track id'); | 329 | equal(moof[0].boxes[1].boxes[0].type, 'tfhd', 'generated a tfhd box'); |
330 | trun = moof[0].boxes[1].boxes[1]; | ||
331 | equal(trun.type, 'trun', 'generated a trun box'); | ||
332 | equal(trun.samples.length, 2, 'wrote two samples'); | ||
333 | |||
334 | equal(trun.samples[0].duration, 9000, 'wrote a sample duration'); | ||
335 | equal(trun.samples[0].size, 10, 'wrote a sample size'); | ||
336 | equal(trun.samples[0].flags, 14, 'wrote the sample flags'); | ||
337 | equal(trun.samples[0].compositionTimeOffset, 500, 'wrote the composition time offset'); | ||
338 | |||
339 | equal(trun.samples[1].duration, 10000, 'wrote a sample duration'); | ||
340 | equal(trun.samples[1].size, 11, 'wrote a sample size'); | ||
341 | equal(trun.samples[1].flags, 9, 'wrote the sample flags'); | ||
342 | equal(trun.samples[1].compositionTimeOffset, 1000, 'wrote the composition time offset'); | ||
343 | }); | ||
344 | |||
345 | test('can generate a traf without samples', function() { | ||
346 | var | ||
347 | data = mp4.moof(8, [{ | ||
348 | trackId: 13 | ||
349 | }]), | ||
350 | moof = videojs.inspectMp4(data); | ||
351 | |||
352 | equal(moof[0].boxes[1].boxes[1].samples.length, 0, 'generated no samples'); | ||
320 | }); | 353 | }); |
321 | 354 | ||
322 | test('generates an mdat', function() { | 355 | test('generates an mdat', function() { | ... | ... |
... | @@ -718,6 +718,32 @@ test('can parse a trun', function() { | ... | @@ -718,6 +718,32 @@ test('can parse a trun', function() { |
718 | }]); | 718 | }]); |
719 | }); | 719 | }); |
720 | 720 | ||
721 | test('can parse a trun with per-sample flags', function() { | ||
722 | var data = box('trun', | ||
723 | 0x00, // version | ||
724 | 0x00, 0x0f, 0x00, // flags | ||
725 | 0x00, 0x00, 0x00, 0x01, // sample_count | ||
726 | |||
727 | 0x00, 0x00, 0x00, 0x09, // sample_duration | ||
728 | 0x00, 0x00, 0x00, 0xff, // sample_size | ||
729 | 0x01, 0x02, 0x03, 0x04, // sample_flags | ||
730 | 0x00, 0x00, 0x00, 0x00); // sample_composition_time_offset | ||
731 | deepEqual(videojs.inspectMp4(new Uint8Array(data)), | ||
732 | [{ | ||
733 | type: 'trun', | ||
734 | version: 0, | ||
735 | size: 32, | ||
736 | flags: new Uint8Array([0, 0x0f, 0x00]), | ||
737 | samples: [{ | ||
738 | duration: 9, | ||
739 | size: 0xff, | ||
740 | flags: 0x01020304, | ||
741 | compositionTimeOffset: 0 | ||
742 | }] | ||
743 | }]); | ||
744 | |||
745 | }); | ||
746 | |||
721 | test('can parse a sidx', function(){ | 747 | test('can parse a sidx', function(){ |
722 | var data = box('sidx', | 748 | var data = box('sidx', |
723 | 0x00, // version | 749 | 0x00, // version |
... | @@ -762,7 +788,7 @@ test('can parse a sidx', function(){ | ... | @@ -762,7 +788,7 @@ test('can parse a sidx', function(){ |
762 | sapType: 1, | 788 | sapType: 1, |
763 | sapDeltaTime: 5 | 789 | sapDeltaTime: 5 |
764 | }] | 790 | }] |
765 | 791 | ||
766 | }]); | 792 | }]); |
767 | }); | 793 | }); |
768 | 794 | ... | ... |
... | @@ -537,10 +537,6 @@ var | ... | @@ -537,10 +537,6 @@ var |
537 | }; | 537 | }; |
538 | while (sampleCount--) { | 538 | while (sampleCount--) { |
539 | sample = {}; | 539 | sample = {}; |
540 | if (sampleFlagsPresent) { | ||
541 | sample.flags = view.getUint32(offset); | ||
542 | offset += 4; | ||
543 | } | ||
544 | if (sampleDurationPresent) { | 540 | if (sampleDurationPresent) { |
545 | sample.duration = view.getUint32(offset); | 541 | sample.duration = view.getUint32(offset); |
546 | offset += 4; | 542 | offset += 4; |
... | @@ -549,6 +545,10 @@ var | ... | @@ -549,6 +545,10 @@ var |
549 | sample.size = view.getUint32(offset); | 545 | sample.size = view.getUint32(offset); |
550 | offset += 4; | 546 | offset += 4; |
551 | } | 547 | } |
548 | if (sampleFlagsPresent) { | ||
549 | sample.flags = view.getUint32(offset); | ||
550 | offset += 4; | ||
551 | } | ||
552 | if (sampleCompositionTimeOffsetPresent) { | 552 | if (sampleCompositionTimeOffsetPresent) { |
553 | sample.compositionTimeOffset = view.getUint32(offset); | 553 | sample.compositionTimeOffset = view.getUint32(offset); |
554 | offset += 4; | 554 | offset += 4; | ... | ... |
-
Please register or sign in to post a comment