Home Reference Source

src/remux/mp4-generator.ts

  1. /**
  2. * Generate MP4 Box
  3. */
  4.  
  5. type HdlrTypes = {
  6. video: Uint8Array;
  7. audio: Uint8Array;
  8. };
  9.  
  10. const UINT32_MAX = Math.pow(2, 32) - 1;
  11.  
  12. class MP4 {
  13. public static types: Record<string, number[]>;
  14. private static HDLR_TYPES: HdlrTypes;
  15. private static STTS: Uint8Array;
  16. private static STSC: Uint8Array;
  17. private static STCO: Uint8Array;
  18. private static STSZ: Uint8Array;
  19. private static VMHD: Uint8Array;
  20. private static SMHD: Uint8Array;
  21. private static STSD: Uint8Array;
  22. private static FTYP: Uint8Array;
  23. private static DINF: Uint8Array;
  24.  
  25. static init() {
  26. MP4.types = {
  27. avc1: [], // codingname
  28. avcC: [],
  29. btrt: [],
  30. dinf: [],
  31. dref: [],
  32. esds: [],
  33. ftyp: [],
  34. hdlr: [],
  35. mdat: [],
  36. mdhd: [],
  37. mdia: [],
  38. mfhd: [],
  39. minf: [],
  40. moof: [],
  41. moov: [],
  42. mp4a: [],
  43. '.mp3': [],
  44. mvex: [],
  45. mvhd: [],
  46. pasp: [],
  47. sdtp: [],
  48. stbl: [],
  49. stco: [],
  50. stsc: [],
  51. stsd: [],
  52. stsz: [],
  53. stts: [],
  54. tfdt: [],
  55. tfhd: [],
  56. traf: [],
  57. trak: [],
  58. trun: [],
  59. trex: [],
  60. tkhd: [],
  61. vmhd: [],
  62. smhd: [],
  63. };
  64.  
  65. let i: string;
  66. for (i in MP4.types) {
  67. if (MP4.types.hasOwnProperty(i)) {
  68. MP4.types[i] = [
  69. i.charCodeAt(0),
  70. i.charCodeAt(1),
  71. i.charCodeAt(2),
  72. i.charCodeAt(3),
  73. ];
  74. }
  75. }
  76.  
  77. const videoHdlr = new Uint8Array([
  78. 0x00, // version 0
  79. 0x00,
  80. 0x00,
  81. 0x00, // flags
  82. 0x00,
  83. 0x00,
  84. 0x00,
  85. 0x00, // pre_defined
  86. 0x76,
  87. 0x69,
  88. 0x64,
  89. 0x65, // handler_type: 'vide'
  90. 0x00,
  91. 0x00,
  92. 0x00,
  93. 0x00, // reserved
  94. 0x00,
  95. 0x00,
  96. 0x00,
  97. 0x00, // reserved
  98. 0x00,
  99. 0x00,
  100. 0x00,
  101. 0x00, // reserved
  102. 0x56,
  103. 0x69,
  104. 0x64,
  105. 0x65,
  106. 0x6f,
  107. 0x48,
  108. 0x61,
  109. 0x6e,
  110. 0x64,
  111. 0x6c,
  112. 0x65,
  113. 0x72,
  114. 0x00, // name: 'VideoHandler'
  115. ]);
  116.  
  117. const audioHdlr = new Uint8Array([
  118. 0x00, // version 0
  119. 0x00,
  120. 0x00,
  121. 0x00, // flags
  122. 0x00,
  123. 0x00,
  124. 0x00,
  125. 0x00, // pre_defined
  126. 0x73,
  127. 0x6f,
  128. 0x75,
  129. 0x6e, // handler_type: 'soun'
  130. 0x00,
  131. 0x00,
  132. 0x00,
  133. 0x00, // reserved
  134. 0x00,
  135. 0x00,
  136. 0x00,
  137. 0x00, // reserved
  138. 0x00,
  139. 0x00,
  140. 0x00,
  141. 0x00, // reserved
  142. 0x53,
  143. 0x6f,
  144. 0x75,
  145. 0x6e,
  146. 0x64,
  147. 0x48,
  148. 0x61,
  149. 0x6e,
  150. 0x64,
  151. 0x6c,
  152. 0x65,
  153. 0x72,
  154. 0x00, // name: 'SoundHandler'
  155. ]);
  156.  
  157. MP4.HDLR_TYPES = {
  158. video: videoHdlr,
  159. audio: audioHdlr,
  160. };
  161.  
  162. const dref = new Uint8Array([
  163. 0x00, // version 0
  164. 0x00,
  165. 0x00,
  166. 0x00, // flags
  167. 0x00,
  168. 0x00,
  169. 0x00,
  170. 0x01, // entry_count
  171. 0x00,
  172. 0x00,
  173. 0x00,
  174. 0x0c, // entry_size
  175. 0x75,
  176. 0x72,
  177. 0x6c,
  178. 0x20, // 'url' type
  179. 0x00, // version 0
  180. 0x00,
  181. 0x00,
  182. 0x01, // entry_flags
  183. ]);
  184.  
  185. const stco = new Uint8Array([
  186. 0x00, // version
  187. 0x00,
  188. 0x00,
  189. 0x00, // flags
  190. 0x00,
  191. 0x00,
  192. 0x00,
  193. 0x00, // entry_count
  194. ]);
  195.  
  196. MP4.STTS = MP4.STSC = MP4.STCO = stco;
  197.  
  198. MP4.STSZ = new Uint8Array([
  199. 0x00, // version
  200. 0x00,
  201. 0x00,
  202. 0x00, // flags
  203. 0x00,
  204. 0x00,
  205. 0x00,
  206. 0x00, // sample_size
  207. 0x00,
  208. 0x00,
  209. 0x00,
  210. 0x00, // sample_count
  211. ]);
  212. MP4.VMHD = new Uint8Array([
  213. 0x00, // version
  214. 0x00,
  215. 0x00,
  216. 0x01, // flags
  217. 0x00,
  218. 0x00, // graphicsmode
  219. 0x00,
  220. 0x00,
  221. 0x00,
  222. 0x00,
  223. 0x00,
  224. 0x00, // opcolor
  225. ]);
  226. MP4.SMHD = new Uint8Array([
  227. 0x00, // version
  228. 0x00,
  229. 0x00,
  230. 0x00, // flags
  231. 0x00,
  232. 0x00, // balance
  233. 0x00,
  234. 0x00, // reserved
  235. ]);
  236.  
  237. MP4.STSD = new Uint8Array([
  238. 0x00, // version 0
  239. 0x00,
  240. 0x00,
  241. 0x00, // flags
  242. 0x00,
  243. 0x00,
  244. 0x00,
  245. 0x01,
  246. ]); // entry_count
  247.  
  248. const majorBrand = new Uint8Array([105, 115, 111, 109]); // isom
  249. const avc1Brand = new Uint8Array([97, 118, 99, 49]); // avc1
  250. const minorVersion = new Uint8Array([0, 0, 0, 1]);
  251.  
  252. MP4.FTYP = MP4.box(
  253. MP4.types.ftyp,
  254. majorBrand,
  255. minorVersion,
  256. majorBrand,
  257. avc1Brand
  258. );
  259. MP4.DINF = MP4.box(MP4.types.dinf, MP4.box(MP4.types.dref, dref));
  260. }
  261.  
  262. static box(type, ...payload: Uint8Array[]) {
  263. let size = 8;
  264. let i = payload.length;
  265. const len = i;
  266. // calculate the total size we need to allocate
  267. while (i--) {
  268. size += payload[i].byteLength;
  269. }
  270.  
  271. const result = new Uint8Array(size);
  272. result[0] = (size >> 24) & 0xff;
  273. result[1] = (size >> 16) & 0xff;
  274. result[2] = (size >> 8) & 0xff;
  275. result[3] = size & 0xff;
  276. result.set(type, 4);
  277. // copy the payload into the result
  278. for (i = 0, size = 8; i < len; i++) {
  279. // copy payload[i] array @ offset size
  280. result.set(payload[i], size);
  281. size += payload[i].byteLength;
  282. }
  283. return result;
  284. }
  285.  
  286. static hdlr(type) {
  287. return MP4.box(MP4.types.hdlr, MP4.HDLR_TYPES[type]);
  288. }
  289.  
  290. static mdat(data) {
  291. return MP4.box(MP4.types.mdat, data);
  292. }
  293.  
  294. static mdhd(timescale, duration) {
  295. duration *= timescale;
  296. const upperWordDuration = Math.floor(duration / (UINT32_MAX + 1));
  297. const lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1));
  298. return MP4.box(
  299. MP4.types.mdhd,
  300. new Uint8Array([
  301. 0x01, // version 1
  302. 0x00,
  303. 0x00,
  304. 0x00, // flags
  305. 0x00,
  306. 0x00,
  307. 0x00,
  308. 0x00,
  309. 0x00,
  310. 0x00,
  311. 0x00,
  312. 0x02, // creation_time
  313. 0x00,
  314. 0x00,
  315. 0x00,
  316. 0x00,
  317. 0x00,
  318. 0x00,
  319. 0x00,
  320. 0x03, // modification_time
  321. (timescale >> 24) & 0xff,
  322. (timescale >> 16) & 0xff,
  323. (timescale >> 8) & 0xff,
  324. timescale & 0xff, // timescale
  325. upperWordDuration >> 24,
  326. (upperWordDuration >> 16) & 0xff,
  327. (upperWordDuration >> 8) & 0xff,
  328. upperWordDuration & 0xff,
  329. lowerWordDuration >> 24,
  330. (lowerWordDuration >> 16) & 0xff,
  331. (lowerWordDuration >> 8) & 0xff,
  332. lowerWordDuration & 0xff,
  333. 0x55,
  334. 0xc4, // 'und' language (undetermined)
  335. 0x00,
  336. 0x00,
  337. ])
  338. );
  339. }
  340.  
  341. static mdia(track) {
  342. return MP4.box(
  343. MP4.types.mdia,
  344. MP4.mdhd(track.timescale, track.duration),
  345. MP4.hdlr(track.type),
  346. MP4.minf(track)
  347. );
  348. }
  349.  
  350. static mfhd(sequenceNumber) {
  351. return MP4.box(
  352. MP4.types.mfhd,
  353. new Uint8Array([
  354. 0x00,
  355. 0x00,
  356. 0x00,
  357. 0x00, // flags
  358. sequenceNumber >> 24,
  359. (sequenceNumber >> 16) & 0xff,
  360. (sequenceNumber >> 8) & 0xff,
  361. sequenceNumber & 0xff, // sequence_number
  362. ])
  363. );
  364. }
  365.  
  366. static minf(track) {
  367. if (track.type === 'audio') {
  368. return MP4.box(
  369. MP4.types.minf,
  370. MP4.box(MP4.types.smhd, MP4.SMHD),
  371. MP4.DINF,
  372. MP4.stbl(track)
  373. );
  374. } else {
  375. return MP4.box(
  376. MP4.types.minf,
  377. MP4.box(MP4.types.vmhd, MP4.VMHD),
  378. MP4.DINF,
  379. MP4.stbl(track)
  380. );
  381. }
  382. }
  383.  
  384. static moof(sn, baseMediaDecodeTime, track) {
  385. return MP4.box(
  386. MP4.types.moof,
  387. MP4.mfhd(sn),
  388. MP4.traf(track, baseMediaDecodeTime)
  389. );
  390. }
  391.  
  392. /**
  393. * @param tracks... (optional) {array} the tracks associated with this movie
  394. */
  395. static moov(tracks) {
  396. let i = tracks.length;
  397. const boxes: Uint8Array[] = [];
  398.  
  399. while (i--) {
  400. boxes[i] = MP4.trak(tracks[i]);
  401. }
  402.  
  403. return MP4.box.apply(
  404. null,
  405. [MP4.types.moov, MP4.mvhd(tracks[0].timescale, tracks[0].duration)]
  406. .concat(boxes)
  407. .concat(MP4.mvex(tracks))
  408. );
  409. }
  410.  
  411. static mvex(tracks) {
  412. let i = tracks.length;
  413. const boxes: Uint8Array[] = [];
  414.  
  415. while (i--) {
  416. boxes[i] = MP4.trex(tracks[i]);
  417. }
  418.  
  419. return MP4.box.apply(null, [MP4.types.mvex, ...boxes]);
  420. }
  421.  
  422. static mvhd(timescale, duration) {
  423. duration *= timescale;
  424. const upperWordDuration = Math.floor(duration / (UINT32_MAX + 1));
  425. const lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1));
  426. const bytes = new Uint8Array([
  427. 0x01, // version 1
  428. 0x00,
  429. 0x00,
  430. 0x00, // flags
  431. 0x00,
  432. 0x00,
  433. 0x00,
  434. 0x00,
  435. 0x00,
  436. 0x00,
  437. 0x00,
  438. 0x02, // creation_time
  439. 0x00,
  440. 0x00,
  441. 0x00,
  442. 0x00,
  443. 0x00,
  444. 0x00,
  445. 0x00,
  446. 0x03, // modification_time
  447. (timescale >> 24) & 0xff,
  448. (timescale >> 16) & 0xff,
  449. (timescale >> 8) & 0xff,
  450. timescale & 0xff, // timescale
  451. upperWordDuration >> 24,
  452. (upperWordDuration >> 16) & 0xff,
  453. (upperWordDuration >> 8) & 0xff,
  454. upperWordDuration & 0xff,
  455. lowerWordDuration >> 24,
  456. (lowerWordDuration >> 16) & 0xff,
  457. (lowerWordDuration >> 8) & 0xff,
  458. lowerWordDuration & 0xff,
  459. 0x00,
  460. 0x01,
  461. 0x00,
  462. 0x00, // 1.0 rate
  463. 0x01,
  464. 0x00, // 1.0 volume
  465. 0x00,
  466. 0x00, // reserved
  467. 0x00,
  468. 0x00,
  469. 0x00,
  470. 0x00, // reserved
  471. 0x00,
  472. 0x00,
  473. 0x00,
  474. 0x00, // reserved
  475. 0x00,
  476. 0x01,
  477. 0x00,
  478. 0x00,
  479. 0x00,
  480. 0x00,
  481. 0x00,
  482. 0x00,
  483. 0x00,
  484. 0x00,
  485. 0x00,
  486. 0x00,
  487. 0x00,
  488. 0x00,
  489. 0x00,
  490. 0x00,
  491. 0x00,
  492. 0x01,
  493. 0x00,
  494. 0x00,
  495. 0x00,
  496. 0x00,
  497. 0x00,
  498. 0x00,
  499. 0x00,
  500. 0x00,
  501. 0x00,
  502. 0x00,
  503. 0x00,
  504. 0x00,
  505. 0x00,
  506. 0x00,
  507. 0x40,
  508. 0x00,
  509. 0x00,
  510. 0x00, // transformation: unity matrix
  511. 0x00,
  512. 0x00,
  513. 0x00,
  514. 0x00,
  515. 0x00,
  516. 0x00,
  517. 0x00,
  518. 0x00,
  519. 0x00,
  520. 0x00,
  521. 0x00,
  522. 0x00,
  523. 0x00,
  524. 0x00,
  525. 0x00,
  526. 0x00,
  527. 0x00,
  528. 0x00,
  529. 0x00,
  530. 0x00,
  531. 0x00,
  532. 0x00,
  533. 0x00,
  534. 0x00, // pre_defined
  535. 0xff,
  536. 0xff,
  537. 0xff,
  538. 0xff, // next_track_ID
  539. ]);
  540. return MP4.box(MP4.types.mvhd, bytes);
  541. }
  542.  
  543. static sdtp(track) {
  544. const samples = track.samples || [];
  545. const bytes = new Uint8Array(4 + samples.length);
  546. let i;
  547. let flags;
  548. // leave the full box header (4 bytes) all zero
  549. // write the sample table
  550. for (i = 0; i < samples.length; i++) {
  551. flags = samples[i].flags;
  552. bytes[i + 4] =
  553. (flags.dependsOn << 4) |
  554. (flags.isDependedOn << 2) |
  555. flags.hasRedundancy;
  556. }
  557.  
  558. return MP4.box(MP4.types.sdtp, bytes);
  559. }
  560.  
  561. static stbl(track) {
  562. return MP4.box(
  563. MP4.types.stbl,
  564. MP4.stsd(track),
  565. MP4.box(MP4.types.stts, MP4.STTS),
  566. MP4.box(MP4.types.stsc, MP4.STSC),
  567. MP4.box(MP4.types.stsz, MP4.STSZ),
  568. MP4.box(MP4.types.stco, MP4.STCO)
  569. );
  570. }
  571.  
  572. static avc1(track) {
  573. let sps: number[] = [];
  574. let pps: number[] = [];
  575. let i;
  576. let data;
  577. let len;
  578. // assemble the SPSs
  579.  
  580. for (i = 0; i < track.sps.length; i++) {
  581. data = track.sps[i];
  582. len = data.byteLength;
  583. sps.push((len >>> 8) & 0xff);
  584. sps.push(len & 0xff);
  585.  
  586. // SPS
  587. sps = sps.concat(Array.prototype.slice.call(data));
  588. }
  589.  
  590. // assemble the PPSs
  591. for (i = 0; i < track.pps.length; i++) {
  592. data = track.pps[i];
  593. len = data.byteLength;
  594. pps.push((len >>> 8) & 0xff);
  595. pps.push(len & 0xff);
  596.  
  597. pps = pps.concat(Array.prototype.slice.call(data));
  598. }
  599.  
  600. const avcc = MP4.box(
  601. MP4.types.avcC,
  602. new Uint8Array(
  603. [
  604. 0x01, // version
  605. sps[3], // profile
  606. sps[4], // profile compat
  607. sps[5], // level
  608. 0xfc | 3, // lengthSizeMinusOne, hard-coded to 4 bytes
  609. 0xe0 | track.sps.length, // 3bit reserved (111) + numOfSequenceParameterSets
  610. ]
  611. .concat(sps)
  612. .concat([
  613. track.pps.length, // numOfPictureParameterSets
  614. ])
  615. .concat(pps)
  616. )
  617. ); // "PPS"
  618. const width = track.width;
  619. const height = track.height;
  620. const hSpacing = track.pixelRatio[0];
  621. const vSpacing = track.pixelRatio[1];
  622.  
  623. return MP4.box(
  624. MP4.types.avc1,
  625. new Uint8Array([
  626. 0x00,
  627. 0x00,
  628. 0x00, // reserved
  629. 0x00,
  630. 0x00,
  631. 0x00, // reserved
  632. 0x00,
  633. 0x01, // data_reference_index
  634. 0x00,
  635. 0x00, // pre_defined
  636. 0x00,
  637. 0x00, // reserved
  638. 0x00,
  639. 0x00,
  640. 0x00,
  641. 0x00,
  642. 0x00,
  643. 0x00,
  644. 0x00,
  645. 0x00,
  646. 0x00,
  647. 0x00,
  648. 0x00,
  649. 0x00, // pre_defined
  650. (width >> 8) & 0xff,
  651. width & 0xff, // width
  652. (height >> 8) & 0xff,
  653. height & 0xff, // height
  654. 0x00,
  655. 0x48,
  656. 0x00,
  657. 0x00, // horizresolution
  658. 0x00,
  659. 0x48,
  660. 0x00,
  661. 0x00, // vertresolution
  662. 0x00,
  663. 0x00,
  664. 0x00,
  665. 0x00, // reserved
  666. 0x00,
  667. 0x01, // frame_count
  668. 0x12,
  669. 0x64,
  670. 0x61,
  671. 0x69,
  672. 0x6c, // dailymotion/hls.js
  673. 0x79,
  674. 0x6d,
  675. 0x6f,
  676. 0x74,
  677. 0x69,
  678. 0x6f,
  679. 0x6e,
  680. 0x2f,
  681. 0x68,
  682. 0x6c,
  683. 0x73,
  684. 0x2e,
  685. 0x6a,
  686. 0x73,
  687. 0x00,
  688. 0x00,
  689. 0x00,
  690. 0x00,
  691. 0x00,
  692. 0x00,
  693. 0x00,
  694. 0x00,
  695. 0x00,
  696. 0x00,
  697. 0x00,
  698. 0x00,
  699. 0x00, // compressorname
  700. 0x00,
  701. 0x18, // depth = 24
  702. 0x11,
  703. 0x11,
  704. ]), // pre_defined = -1
  705. avcc,
  706. MP4.box(
  707. MP4.types.btrt,
  708. new Uint8Array([
  709. 0x00,
  710. 0x1c,
  711. 0x9c,
  712. 0x80, // bufferSizeDB
  713. 0x00,
  714. 0x2d,
  715. 0xc6,
  716. 0xc0, // maxBitrate
  717. 0x00,
  718. 0x2d,
  719. 0xc6,
  720. 0xc0,
  721. ])
  722. ), // avgBitrate
  723. MP4.box(
  724. MP4.types.pasp,
  725. new Uint8Array([
  726. hSpacing >> 24, // hSpacing
  727. (hSpacing >> 16) & 0xff,
  728. (hSpacing >> 8) & 0xff,
  729. hSpacing & 0xff,
  730. vSpacing >> 24, // vSpacing
  731. (vSpacing >> 16) & 0xff,
  732. (vSpacing >> 8) & 0xff,
  733. vSpacing & 0xff,
  734. ])
  735. )
  736. );
  737. }
  738.  
  739. static esds(track) {
  740. const configlen = track.config.length;
  741. return new Uint8Array(
  742. [
  743. 0x00, // version 0
  744. 0x00,
  745. 0x00,
  746. 0x00, // flags
  747.  
  748. 0x03, // descriptor_type
  749. 0x17 + configlen, // length
  750. 0x00,
  751. 0x01, // es_id
  752. 0x00, // stream_priority
  753.  
  754. 0x04, // descriptor_type
  755. 0x0f + configlen, // length
  756. 0x40, // codec : mpeg4_audio
  757. 0x15, // stream_type
  758. 0x00,
  759. 0x00,
  760. 0x00, // buffer_size
  761. 0x00,
  762. 0x00,
  763. 0x00,
  764. 0x00, // maxBitrate
  765. 0x00,
  766. 0x00,
  767. 0x00,
  768. 0x00, // avgBitrate
  769.  
  770. 0x05, // descriptor_type
  771. ]
  772. .concat([configlen])
  773. .concat(track.config)
  774. .concat([0x06, 0x01, 0x02])
  775. ); // GASpecificConfig)); // length + audio config descriptor
  776. }
  777.  
  778. static mp4a(track) {
  779. const samplerate = track.samplerate;
  780. return MP4.box(
  781. MP4.types.mp4a,
  782. new Uint8Array([
  783. 0x00,
  784. 0x00,
  785. 0x00, // reserved
  786. 0x00,
  787. 0x00,
  788. 0x00, // reserved
  789. 0x00,
  790. 0x01, // data_reference_index
  791. 0x00,
  792. 0x00,
  793. 0x00,
  794. 0x00,
  795. 0x00,
  796. 0x00,
  797. 0x00,
  798. 0x00, // reserved
  799. 0x00,
  800. track.channelCount, // channelcount
  801. 0x00,
  802. 0x10, // sampleSize:16bits
  803. 0x00,
  804. 0x00,
  805. 0x00,
  806. 0x00, // reserved2
  807. (samplerate >> 8) & 0xff,
  808. samplerate & 0xff, //
  809. 0x00,
  810. 0x00,
  811. ]),
  812. MP4.box(MP4.types.esds, MP4.esds(track))
  813. );
  814. }
  815.  
  816. static mp3(track) {
  817. const samplerate = track.samplerate;
  818. return MP4.box(
  819. MP4.types['.mp3'],
  820. new Uint8Array([
  821. 0x00,
  822. 0x00,
  823. 0x00, // reserved
  824. 0x00,
  825. 0x00,
  826. 0x00, // reserved
  827. 0x00,
  828. 0x01, // data_reference_index
  829. 0x00,
  830. 0x00,
  831. 0x00,
  832. 0x00,
  833. 0x00,
  834. 0x00,
  835. 0x00,
  836. 0x00, // reserved
  837. 0x00,
  838. track.channelCount, // channelcount
  839. 0x00,
  840. 0x10, // sampleSize:16bits
  841. 0x00,
  842. 0x00,
  843. 0x00,
  844. 0x00, // reserved2
  845. (samplerate >> 8) & 0xff,
  846. samplerate & 0xff, //
  847. 0x00,
  848. 0x00,
  849. ])
  850. );
  851. }
  852.  
  853. static stsd(track) {
  854. if (track.type === 'audio') {
  855. if (!track.isAAC && track.codec === 'mp3') {
  856. return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp3(track));
  857. }
  858.  
  859. return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
  860. } else {
  861. return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
  862. }
  863. }
  864.  
  865. static tkhd(track) {
  866. const id = track.id;
  867. const duration = track.duration * track.timescale;
  868. const width = track.width;
  869. const height = track.height;
  870. const upperWordDuration = Math.floor(duration / (UINT32_MAX + 1));
  871. const lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1));
  872. return MP4.box(
  873. MP4.types.tkhd,
  874. new Uint8Array([
  875. 0x01, // version 1
  876. 0x00,
  877. 0x00,
  878. 0x07, // flags
  879. 0x00,
  880. 0x00,
  881. 0x00,
  882. 0x00,
  883. 0x00,
  884. 0x00,
  885. 0x00,
  886. 0x02, // creation_time
  887. 0x00,
  888. 0x00,
  889. 0x00,
  890. 0x00,
  891. 0x00,
  892. 0x00,
  893. 0x00,
  894. 0x03, // modification_time
  895. (id >> 24) & 0xff,
  896. (id >> 16) & 0xff,
  897. (id >> 8) & 0xff,
  898. id & 0xff, // track_ID
  899. 0x00,
  900. 0x00,
  901. 0x00,
  902. 0x00, // reserved
  903. upperWordDuration >> 24,
  904. (upperWordDuration >> 16) & 0xff,
  905. (upperWordDuration >> 8) & 0xff,
  906. upperWordDuration & 0xff,
  907. lowerWordDuration >> 24,
  908. (lowerWordDuration >> 16) & 0xff,
  909. (lowerWordDuration >> 8) & 0xff,
  910. lowerWordDuration & 0xff,
  911. 0x00,
  912. 0x00,
  913. 0x00,
  914. 0x00,
  915. 0x00,
  916. 0x00,
  917. 0x00,
  918. 0x00, // reserved
  919. 0x00,
  920. 0x00, // layer
  921. 0x00,
  922. 0x00, // alternate_group
  923. 0x00,
  924. 0x00, // non-audio track volume
  925. 0x00,
  926. 0x00, // reserved
  927. 0x00,
  928. 0x01,
  929. 0x00,
  930. 0x00,
  931. 0x00,
  932. 0x00,
  933. 0x00,
  934. 0x00,
  935. 0x00,
  936. 0x00,
  937. 0x00,
  938. 0x00,
  939. 0x00,
  940. 0x00,
  941. 0x00,
  942. 0x00,
  943. 0x00,
  944. 0x01,
  945. 0x00,
  946. 0x00,
  947. 0x00,
  948. 0x00,
  949. 0x00,
  950. 0x00,
  951. 0x00,
  952. 0x00,
  953. 0x00,
  954. 0x00,
  955. 0x00,
  956. 0x00,
  957. 0x00,
  958. 0x00,
  959. 0x40,
  960. 0x00,
  961. 0x00,
  962. 0x00, // transformation: unity matrix
  963. (width >> 8) & 0xff,
  964. width & 0xff,
  965. 0x00,
  966. 0x00, // width
  967. (height >> 8) & 0xff,
  968. height & 0xff,
  969. 0x00,
  970. 0x00, // height
  971. ])
  972. );
  973. }
  974.  
  975. static traf(track, baseMediaDecodeTime) {
  976. const sampleDependencyTable = MP4.sdtp(track);
  977. const id = track.id;
  978. const upperWordBaseMediaDecodeTime = Math.floor(
  979. baseMediaDecodeTime / (UINT32_MAX + 1)
  980. );
  981. const lowerWordBaseMediaDecodeTime = Math.floor(
  982. baseMediaDecodeTime % (UINT32_MAX + 1)
  983. );
  984. return MP4.box(
  985. MP4.types.traf,
  986. MP4.box(
  987. MP4.types.tfhd,
  988. new Uint8Array([
  989. 0x00, // version 0
  990. 0x00,
  991. 0x00,
  992. 0x00, // flags
  993. id >> 24,
  994. (id >> 16) & 0xff,
  995. (id >> 8) & 0xff,
  996. id & 0xff, // track_ID
  997. ])
  998. ),
  999. MP4.box(
  1000. MP4.types.tfdt,
  1001. new Uint8Array([
  1002. 0x01, // version 1
  1003. 0x00,
  1004. 0x00,
  1005. 0x00, // flags
  1006. upperWordBaseMediaDecodeTime >> 24,
  1007. (upperWordBaseMediaDecodeTime >> 16) & 0xff,
  1008. (upperWordBaseMediaDecodeTime >> 8) & 0xff,
  1009. upperWordBaseMediaDecodeTime & 0xff,
  1010. lowerWordBaseMediaDecodeTime >> 24,
  1011. (lowerWordBaseMediaDecodeTime >> 16) & 0xff,
  1012. (lowerWordBaseMediaDecodeTime >> 8) & 0xff,
  1013. lowerWordBaseMediaDecodeTime & 0xff,
  1014. ])
  1015. ),
  1016. MP4.trun(
  1017. track,
  1018. sampleDependencyTable.length +
  1019. 16 + // tfhd
  1020. 20 + // tfdt
  1021. 8 + // traf header
  1022. 16 + // mfhd
  1023. 8 + // moof header
  1024. 8
  1025. ), // mdat header
  1026. sampleDependencyTable
  1027. );
  1028. }
  1029.  
  1030. /**
  1031. * Generate a track box.
  1032. * @param track {object} a track definition
  1033. * @return {Uint8Array} the track box
  1034. */
  1035. static trak(track) {
  1036. track.duration = track.duration || 0xffffffff;
  1037. return MP4.box(MP4.types.trak, MP4.tkhd(track), MP4.mdia(track));
  1038. }
  1039.  
  1040. static trex(track) {
  1041. const id = track.id;
  1042. return MP4.box(
  1043. MP4.types.trex,
  1044. new Uint8Array([
  1045. 0x00, // version 0
  1046. 0x00,
  1047. 0x00,
  1048. 0x00, // flags
  1049. id >> 24,
  1050. (id >> 16) & 0xff,
  1051. (id >> 8) & 0xff,
  1052. id & 0xff, // track_ID
  1053. 0x00,
  1054. 0x00,
  1055. 0x00,
  1056. 0x01, // default_sample_description_index
  1057. 0x00,
  1058. 0x00,
  1059. 0x00,
  1060. 0x00, // default_sample_duration
  1061. 0x00,
  1062. 0x00,
  1063. 0x00,
  1064. 0x00, // default_sample_size
  1065. 0x00,
  1066. 0x01,
  1067. 0x00,
  1068. 0x01, // default_sample_flags
  1069. ])
  1070. );
  1071. }
  1072.  
  1073. static trun(track, offset) {
  1074. const samples = track.samples || [];
  1075. const len = samples.length;
  1076. const arraylen = 12 + 16 * len;
  1077. const array = new Uint8Array(arraylen);
  1078. let i;
  1079. let sample;
  1080. let duration;
  1081. let size;
  1082. let flags;
  1083. let cts;
  1084. offset += 8 + arraylen;
  1085. array.set(
  1086. [
  1087. 0x00, // version 0
  1088. 0x00,
  1089. 0x0f,
  1090. 0x01, // flags
  1091. (len >>> 24) & 0xff,
  1092. (len >>> 16) & 0xff,
  1093. (len >>> 8) & 0xff,
  1094. len & 0xff, // sample_count
  1095. (offset >>> 24) & 0xff,
  1096. (offset >>> 16) & 0xff,
  1097. (offset >>> 8) & 0xff,
  1098. offset & 0xff, // data_offset
  1099. ],
  1100. 0
  1101. );
  1102. for (i = 0; i < len; i++) {
  1103. sample = samples[i];
  1104. duration = sample.duration;
  1105. size = sample.size;
  1106. flags = sample.flags;
  1107. cts = sample.cts;
  1108. array.set(
  1109. [
  1110. (duration >>> 24) & 0xff,
  1111. (duration >>> 16) & 0xff,
  1112. (duration >>> 8) & 0xff,
  1113. duration & 0xff, // sample_duration
  1114. (size >>> 24) & 0xff,
  1115. (size >>> 16) & 0xff,
  1116. (size >>> 8) & 0xff,
  1117. size & 0xff, // sample_size
  1118. (flags.isLeading << 2) | flags.dependsOn,
  1119. (flags.isDependedOn << 6) |
  1120. (flags.hasRedundancy << 4) |
  1121. (flags.paddingValue << 1) |
  1122. flags.isNonSync,
  1123. flags.degradPrio & (0xf0 << 8),
  1124. flags.degradPrio & 0x0f, // sample_flags
  1125. (cts >>> 24) & 0xff,
  1126. (cts >>> 16) & 0xff,
  1127. (cts >>> 8) & 0xff,
  1128. cts & 0xff, // sample_composition_time_offset
  1129. ],
  1130. 12 + 16 * i
  1131. );
  1132. }
  1133. return MP4.box(MP4.types.trun, array);
  1134. }
  1135.  
  1136. static initSegment(tracks) {
  1137. if (!MP4.types) {
  1138. MP4.init();
  1139. }
  1140.  
  1141. const movie = MP4.moov(tracks);
  1142. const result = new Uint8Array(MP4.FTYP.byteLength + movie.byteLength);
  1143. result.set(MP4.FTYP);
  1144. result.set(movie, MP4.FTYP.byteLength);
  1145. return result;
  1146. }
  1147. }
  1148.  
  1149. export default MP4;