Add an sdtp box to each traf
Working examples of segmented mp4s have included an sdtp box, even though it's duplicative of information in the trun box. It's easy to generate however, so add it to the inspector and generator, and update the transmuxer tests to expect sane output.
Showing
5 changed files
with
80 additions
and
23 deletions
... | @@ -2,10 +2,10 @@ | ... | @@ -2,10 +2,10 @@ |
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, traf, trex, trun, types, | 5 | tkhd, mdia, mdhd, hdlr, sdtp, stbl, stsd, styp, traf, trex, trun, |
6 | MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR, AUDIO_HDLR, | 6 | types, MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR, |
7 | HDLR_TYPES, VMHD, DREF, STCO, STSC, STSZ, STTS, Uint8Array, | 7 | AUDIO_HDLR, HDLR_TYPES, VMHD, DREF, STCO, STSC, STSZ, STTS, |
8 | DataView; | 8 | Uint8Array, DataView; |
9 | 9 | ||
10 | Uint8Array = window.Uint8Array; | 10 | Uint8Array = window.Uint8Array; |
11 | DataView = window.DataView; | 11 | DataView = window.DataView; |
... | @@ -30,6 +30,7 @@ DataView = window.DataView; | ... | @@ -30,6 +30,7 @@ DataView = window.DataView; |
30 | moov: [], | 30 | moov: [], |
31 | mvex: [], | 31 | mvex: [], |
32 | mvhd: [], | 32 | mvhd: [], |
33 | sdtp: [], | ||
33 | stbl: [], | 34 | stbl: [], |
34 | stco: [], | 35 | stco: [], |
35 | stsc: [], | 36 | stsc: [], |
... | @@ -276,6 +277,27 @@ mvhd = function(duration) { | ... | @@ -276,6 +277,27 @@ mvhd = function(duration) { |
276 | return box(types.mvhd, bytes); | 277 | return box(types.mvhd, bytes); |
277 | }; | 278 | }; |
278 | 279 | ||
280 | sdtp = function(track) { | ||
281 | var | ||
282 | samples = track.samples || [], | ||
283 | bytes = new Uint8Array(4 + samples.length), | ||
284 | sample, | ||
285 | i; | ||
286 | |||
287 | // leave the full box header (4 bytes) all zero | ||
288 | |||
289 | // write the sample table | ||
290 | for (i = 0; i < samples.length; i++) { | ||
291 | sample = samples[i]; | ||
292 | bytes[i + 4] = (sample.flags.dependsOn << 4) | | ||
293 | (sample.flags.isDependedOn << 2) | | ||
294 | (sample.flags.hasRedundancy); | ||
295 | } | ||
296 | |||
297 | return box(types.sdtp, | ||
298 | bytes); | ||
299 | }; | ||
300 | |||
279 | stbl = function(track) { | 301 | stbl = function(track) { |
280 | return box(types.stbl, | 302 | return box(types.stbl, |
281 | stsd(track), | 303 | stsd(track), |
... | @@ -414,7 +436,8 @@ traf = function(track) { | ... | @@ -414,7 +436,8 @@ traf = function(track) { |
414 | 0x00, 0x00, 0x00, // flags | 436 | 0x00, 0x00, 0x00, // flags |
415 | 0x00, 0x00, 0x00, 0x00 // baseMediaDecodeTime | 437 | 0x00, 0x00, 0x00, 0x00 // baseMediaDecodeTime |
416 | ])), | 438 | ])), |
417 | trun(track)); | 439 | trun(track), |
440 | sdtp(track)); | ||
418 | }; | 441 | }; |
419 | 442 | ||
420 | /** | 443 | /** | ... | ... |
... | @@ -307,8 +307,8 @@ test('generates a minimal moof', function() { | ... | @@ -307,8 +307,8 @@ test('generates a minimal moof', function() { |
307 | size: 10, | 307 | size: 10, |
308 | flags: { | 308 | flags: { |
309 | isLeading: 0, | 309 | isLeading: 0, |
310 | dependsOn: 0, | 310 | dependsOn: 2, |
311 | isDependedOn: 0, | 311 | isDependedOn: 1, |
312 | hasRedundancy: 0, | 312 | hasRedundancy: 0, |
313 | paddingValue: 0, | 313 | paddingValue: 0, |
314 | isNonSyncSample: 0, | 314 | isNonSyncSample: 0, |
... | @@ -320,7 +320,7 @@ test('generates a minimal moof', function() { | ... | @@ -320,7 +320,7 @@ test('generates a minimal moof', function() { |
320 | size: 11, | 320 | size: 11, |
321 | flags: { | 321 | flags: { |
322 | isLeading: 0, | 322 | isLeading: 0, |
323 | dependsOn: 0, | 323 | dependsOn: 1, |
324 | isDependedOn: 0, | 324 | isDependedOn: 0, |
325 | hasRedundancy: 0, | 325 | hasRedundancy: 0, |
326 | paddingValue: 0, | 326 | paddingValue: 0, |
... | @@ -331,7 +331,8 @@ test('generates a minimal moof', function() { | ... | @@ -331,7 +331,8 @@ test('generates a minimal moof', function() { |
331 | }] | 331 | }] |
332 | }]), | 332 | }]), |
333 | moof = videojs.inspectMp4(data), | 333 | moof = videojs.inspectMp4(data), |
334 | trun; | 334 | trun, |
335 | sdtp; | ||
335 | 336 | ||
336 | equal(moof.length, 1, 'generated one box'); | 337 | equal(moof.length, 1, 'generated one box'); |
337 | equal(moof[0].type, 'moof', 'generated a moof box'); | 338 | equal(moof[0].type, 'moof', 'generated a moof box'); |
... | @@ -339,7 +340,7 @@ test('generates a minimal moof', function() { | ... | @@ -339,7 +340,7 @@ test('generates a minimal moof', function() { |
339 | equal(moof[0].boxes[0].type, 'mfhd', 'generated an mfhd box'); | 340 | equal(moof[0].boxes[0].type, 'mfhd', 'generated an mfhd box'); |
340 | equal(moof[0].boxes[0].sequenceNumber, 7, 'included the sequence_number'); | 341 | equal(moof[0].boxes[0].sequenceNumber, 7, 'included the sequence_number'); |
341 | equal(moof[0].boxes[1].type, 'traf', 'generated a traf box'); | 342 | equal(moof[0].boxes[1].type, 'traf', 'generated a traf box'); |
342 | equal(moof[0].boxes[1].boxes.length, 3, 'generated track fragment info'); | 343 | equal(moof[0].boxes[1].boxes.length, 4, 'generated track fragment info'); |
343 | equal(moof[0].boxes[1].boxes[0].type, 'tfhd', 'generated a tfhd box'); | 344 | equal(moof[0].boxes[1].boxes[0].type, 'tfhd', 'generated a tfhd box'); |
344 | equal(moof[0].boxes[1].boxes[0].trackId, 17, 'wrote the first track id'); | 345 | equal(moof[0].boxes[1].boxes[0].trackId, 17, 'wrote the first track id'); |
345 | equal(moof[0].boxes[1].boxes[0].baseDataOffset, undefined, 'did not set a base data offset'); | 346 | equal(moof[0].boxes[1].boxes[0].baseDataOffset, undefined, 'did not set a base data offset'); |
... | @@ -358,8 +359,8 @@ test('generates a minimal moof', function() { | ... | @@ -358,8 +359,8 @@ test('generates a minimal moof', function() { |
358 | equal(trun.samples[0].size, 10, 'wrote a sample size'); | 359 | equal(trun.samples[0].size, 10, 'wrote a sample size'); |
359 | deepEqual(trun.samples[0].flags, { | 360 | deepEqual(trun.samples[0].flags, { |
360 | isLeading: 0, | 361 | isLeading: 0, |
361 | dependsOn: 0, | 362 | dependsOn: 2, |
362 | isDependedOn: 0, | 363 | isDependedOn: 1, |
363 | hasRedundancy: 0, | 364 | hasRedundancy: 0, |
364 | paddingValue: 0, | 365 | paddingValue: 0, |
365 | isNonSyncSample: 0, | 366 | isNonSyncSample: 0, |
... | @@ -371,7 +372,7 @@ test('generates a minimal moof', function() { | ... | @@ -371,7 +372,7 @@ test('generates a minimal moof', function() { |
371 | equal(trun.samples[1].size, 11, 'wrote a sample size'); | 372 | equal(trun.samples[1].size, 11, 'wrote a sample size'); |
372 | deepEqual(trun.samples[1].flags, { | 373 | deepEqual(trun.samples[1].flags, { |
373 | isLeading: 0, | 374 | isLeading: 0, |
374 | dependsOn: 0, | 375 | dependsOn: 1, |
375 | isDependedOn: 0, | 376 | isDependedOn: 0, |
376 | hasRedundancy: 0, | 377 | hasRedundancy: 0, |
377 | paddingValue: 0, | 378 | paddingValue: 0, |
... | @@ -379,6 +380,20 @@ test('generates a minimal moof', function() { | ... | @@ -379,6 +380,20 @@ test('generates a minimal moof', function() { |
379 | degradationPriority: 9 | 380 | degradationPriority: 9 |
380 | }, 'wrote the sample flags'); | 381 | }, 'wrote the sample flags'); |
381 | equal(trun.samples[1].compositionTimeOffset, 1000, 'wrote the composition time offset'); | 382 | equal(trun.samples[1].compositionTimeOffset, 1000, 'wrote the composition time offset'); |
383 | |||
384 | sdtp = moof[0].boxes[1].boxes[3]; | ||
385 | equal(sdtp.type, 'sdtp', 'generated an sdtp box'); | ||
386 | equal(sdtp.samples.length, 2, 'wrote two samples'); | ||
387 | deepEqual(sdtp.samples[0], { | ||
388 | dependsOn: 2, | ||
389 | isDependedOn: 1, | ||
390 | hasRedundancy: 0 | ||
391 | }, 'wrote the sample data table'); | ||
392 | deepEqual(sdtp.samples[1], { | ||
393 | dependsOn: 1, | ||
394 | isDependedOn: 0, | ||
395 | hasRedundancy: 0 | ||
396 | }, 'wrote the sample data table'); | ||
382 | }); | 397 | }); |
383 | 398 | ||
384 | test('can generate a traf without samples', function() { | 399 | test('can generate a traf without samples', function() { | ... | ... |
... | @@ -782,13 +782,13 @@ test('can parse an sdtp', function() { | ... | @@ -782,13 +782,13 @@ test('can parse an sdtp', function() { |
782 | flags: new Uint8Array([0, 0, 0]), | 782 | flags: new Uint8Array([0, 0, 0]), |
783 | size: 14, | 783 | size: 14, |
784 | samples: [{ | 784 | samples: [{ |
785 | sampleDependsOn: 1, | 785 | dependsOn: 1, |
786 | sampleIsDependedOn: 1, | 786 | isDependedOn: 1, |
787 | sampleHasRedundancy: 1 | 787 | hasRedundancy: 1 |
788 | }, { | 788 | }, { |
789 | sampleDependsOn: 2, | 789 | dependsOn: 2, |
790 | sampleIsDependedOn: 1, | 790 | isDependedOn: 1, |
791 | sampleHasRedundancy: 3 | 791 | hasRedundancy: 3 |
792 | }] | 792 | }] |
793 | }]); | 793 | }]); |
794 | }); | 794 | }); | ... | ... |
... | @@ -287,9 +287,9 @@ var | ... | @@ -287,9 +287,9 @@ var |
287 | 287 | ||
288 | for (i = 4; i < data.byteLength; i++) { | 288 | for (i = 4; i < data.byteLength; i++) { |
289 | result.samples.push({ | 289 | result.samples.push({ |
290 | sampleDependsOn: (data[i] & 0x30) >> 4, | 290 | dependsOn: (data[i] & 0x30) >> 4, |
291 | sampleIsDependedOn: (data[i] & 0x0c) >> 2, | 291 | isDependedOn: (data[i] & 0x0c) >> 2, |
292 | sampleHasRedundancy: data[i] & 0x03 | 292 | hasRedundancy: data[i] & 0x03 |
293 | }); | 293 | }); |
294 | } | 294 | } |
295 | return result; | 295 | return result; | ... | ... |
... | @@ -874,8 +874,9 @@ validateTrack = function(track, metadata) { | ... | @@ -874,8 +874,9 @@ validateTrack = function(track, metadata) { |
874 | }; | 874 | }; |
875 | 875 | ||
876 | validateTrackFragment = function(track, metadata) { | 876 | validateTrackFragment = function(track, metadata) { |
877 | var tfhd, trun, i, sample; | 877 | var tfhd, trun, sdtp, i, sample; |
878 | equal(track.type, 'traf', 'wrote a track fragment'); | 878 | equal(track.type, 'traf', 'wrote a track fragment'); |
879 | equal(track.boxes.length, 4, 'wrote four track fragment children'); | ||
879 | tfhd = track.boxes[0]; | 880 | tfhd = track.boxes[0]; |
880 | equal(tfhd.type, 'tfhd', 'wrote a track fragment header'); | 881 | equal(tfhd.type, 'tfhd', 'wrote a track fragment header'); |
881 | equal(tfhd.trackId, metadata.trackId, 'wrote the track id'); | 882 | equal(tfhd.trackId, metadata.trackId, 'wrote the track id'); |
... | @@ -902,6 +903,24 @@ validateTrackFragment = function(track, metadata) { | ... | @@ -902,6 +903,24 @@ validateTrackFragment = function(track, metadata) { |
902 | equal(sample.flags.hasRedundancy, 0, 'sample redundancy is unknown'); | 903 | equal(sample.flags.hasRedundancy, 0, 'sample redundancy is unknown'); |
903 | equal(sample.flags.degradationPriority, 0, 'sample degradation priority is zero'); | 904 | equal(sample.flags.degradationPriority, 0, 'sample degradation priority is zero'); |
904 | } | 905 | } |
906 | |||
907 | sdtp = track.boxes[3]; | ||
908 | equal(trun.samples.length, | ||
909 | sdtp.samples.length, | ||
910 | 'wrote an equal number of trun and sdtp samples'); | ||
911 | for (i = 0; i < sdtp.samples.length; i++) { | ||
912 | sample = sdtp.samples[i]; | ||
913 | notEqual(sample.dependsOn, 0, 'sample dependency is not unknown'); | ||
914 | equal(trun.samples[i].flags.dependsOn, | ||
915 | sample.dependsOn, | ||
916 | 'wrote a consistent dependsOn'); | ||
917 | equal(trun.samples[i].flags.isDependedOn, | ||
918 | sample.isDependedOn, | ||
919 | 'wrote a consistent isDependedOn'); | ||
920 | equal(trun.samples[i].flags.hasRedundancy, | ||
921 | sample.hasRedundancy, | ||
922 | 'wrote a consistent hasRedundancy'); | ||
923 | } | ||
905 | }; | 924 | }; |
906 | 925 | ||
907 | test('parses an example mp2t file and generates media segments', function() { | 926 | test('parses an example mp2t file and generates media segments', function() { | ... | ... |
-
Please register or sign in to post a comment