Source: lib/transmuxer/mp4_generator.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.transmuxer.Mp4Generator');
  7. goog.require('goog.asserts');
  8. goog.require('shaka.util.ManifestParserUtils');
  9. goog.require('shaka.util.Platform');
  10. goog.require('shaka.util.Uint8ArrayUtils');
  11. shaka.transmuxer.Mp4Generator = class {
  12. /**
  13. * @param {!Array<shaka.transmuxer.Mp4Generator.StreamInfo>} streamInfos
  14. */
  15. constructor(streamInfos) {
  16. shaka.transmuxer.Mp4Generator.initStaticProperties_();
  17. /** @private {!Array<shaka.transmuxer.Mp4Generator.StreamInfo>} */
  18. this.streamInfos_ = streamInfos;
  19. }
  20. /**
  21. * Generate a Init Segment (MP4).
  22. *
  23. * @return {!Uint8Array}
  24. */
  25. initSegment() {
  26. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  27. const movie = this.moov_();
  28. const length = Mp4Generator.FTYP_.byteLength + movie.byteLength;
  29. const result = new Uint8Array(length);
  30. result.set(Mp4Generator.FTYP_);
  31. result.set(movie, Mp4Generator.FTYP_.byteLength);
  32. return result;
  33. }
  34. /**
  35. * Generate a MOOV box
  36. *
  37. * @return {!Uint8Array}
  38. * @private
  39. */
  40. moov_() {
  41. goog.asserts.assert(this.streamInfos_.length > 0,
  42. 'StreamInfos must have elements');
  43. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  44. const trakArrays = [];
  45. for (const streamInfo of this.streamInfos_) {
  46. trakArrays.push(this.trak_(streamInfo));
  47. }
  48. const traks = shaka.util.Uint8ArrayUtils.concat(...trakArrays);
  49. const firstStreamInfo = this.streamInfos_[0];
  50. return Mp4Generator.box('moov',
  51. this.mvhd_(firstStreamInfo),
  52. traks,
  53. this.mvex_(),
  54. this.pssh_(firstStreamInfo));
  55. }
  56. /**
  57. * Generate a MVHD box
  58. *
  59. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  60. * @return {!Uint8Array}
  61. * @private
  62. */
  63. mvhd_(streamInfo) {
  64. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  65. const duration = streamInfo.duration * streamInfo.timescale;
  66. const upperWordDuration =
  67. Math.floor(duration / (Mp4Generator.UINT32_MAX_ + 1));
  68. const lowerWordDuration =
  69. Math.floor(duration % (Mp4Generator.UINT32_MAX_ + 1));
  70. const bytes = new Uint8Array([
  71. 0x01, // version 1
  72. 0x00, 0x00, 0x00, // flags
  73. 0x00, 0x00, 0x00, 0x00,
  74. 0x00, 0x00, 0x00, 0x02, // creation_time
  75. 0x00, 0x00, 0x00, 0x00,
  76. 0x00, 0x00, 0x00, 0x03, // modification_time
  77. ...this.breakNumberIntoBytes_(streamInfo.timescale, 4), // timescale
  78. ...this.breakNumberIntoBytes_(upperWordDuration, 4),
  79. ...this.breakNumberIntoBytes_(lowerWordDuration, 4), // duration
  80. 0x00, 0x01, 0x00, 0x00, // 1.0 rate
  81. 0x01, 0x00, // 1.0 volume
  82. 0x00, 0x00, // reserved
  83. 0x00, 0x00, 0x00, 0x00, // reserved
  84. 0x00, 0x00, 0x00, 0x00, // reserved
  85. 0x00, 0x01, 0x00, 0x00,
  86. 0x00, 0x00, 0x00, 0x00,
  87. 0x00, 0x00, 0x00, 0x00,
  88. 0x00, 0x00, 0x00, 0x00,
  89. 0x00, 0x01, 0x00, 0x00,
  90. 0x00, 0x00, 0x00, 0x00,
  91. 0x00, 0x00, 0x00, 0x00,
  92. 0x00, 0x00, 0x00, 0x00,
  93. 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
  94. 0x00, 0x00, 0x00, 0x00,
  95. 0x00, 0x00, 0x00, 0x00,
  96. 0x00, 0x00, 0x00, 0x00,
  97. 0x00, 0x00, 0x00, 0x00,
  98. 0x00, 0x00, 0x00, 0x00,
  99. 0x00, 0x00, 0x00, 0x00, // pre_defined
  100. 0xff, 0xff, 0xff, 0xff, // next_track_ID
  101. ]);
  102. return Mp4Generator.box('mvhd', bytes);
  103. }
  104. /**
  105. * Generate a TRAK box
  106. *
  107. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  108. * @return {!Uint8Array}
  109. * @private
  110. */
  111. trak_(streamInfo) {
  112. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  113. return Mp4Generator.box('trak',
  114. this.tkhd_(streamInfo), this.mdia_(streamInfo));
  115. }
  116. /**
  117. * Generate a TKHD box
  118. *
  119. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  120. * @return {!Uint8Array}
  121. * @private
  122. */
  123. tkhd_(streamInfo) {
  124. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  125. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  126. const id = streamInfo.id + 1;
  127. let width = streamInfo.stream.width || 0;
  128. let height = streamInfo.stream.height || 0;
  129. if (streamInfo.type == ContentType.AUDIO) {
  130. width = 0;
  131. height = 0;
  132. }
  133. const duration = streamInfo.duration * streamInfo.timescale;
  134. const upperWordDuration =
  135. Math.floor(duration / (Mp4Generator.UINT32_MAX_ + 1));
  136. const lowerWordDuration =
  137. Math.floor(duration % (Mp4Generator.UINT32_MAX_ + 1));
  138. const bytes = new Uint8Array([
  139. 0x01, // version 1
  140. 0x00, 0x00, 0x07, // flags
  141. 0x00, 0x00, 0x00, 0x00,
  142. 0x00, 0x00, 0x00, 0x02, // creation_time
  143. 0x00, 0x00, 0x00, 0x00,
  144. 0x00, 0x00, 0x00, 0x03, // modification_time
  145. ...this.breakNumberIntoBytes_(id, 4), // track_ID
  146. 0x00, 0x00, 0x00, 0x00, // reserved
  147. ...this.breakNumberIntoBytes_(upperWordDuration, 4),
  148. ...this.breakNumberIntoBytes_(lowerWordDuration, 4), // duration
  149. 0x00, 0x00, 0x00, 0x00,
  150. 0x00, 0x00, 0x00, 0x00, // reserved
  151. 0x00, 0x00, // layer
  152. 0x00, 0x00, // alternate_group
  153. 0x00, 0x00, // non-audio track volume
  154. 0x00, 0x00, // reserved
  155. 0x00, 0x01, 0x00, 0x00,
  156. 0x00, 0x00, 0x00, 0x00,
  157. 0x00, 0x00, 0x00, 0x00,
  158. 0x00, 0x00, 0x00, 0x00,
  159. 0x00, 0x01, 0x00, 0x00,
  160. 0x00, 0x00, 0x00, 0x00,
  161. 0x00, 0x00, 0x00, 0x00,
  162. 0x00, 0x00, 0x00, 0x00,
  163. 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
  164. ...this.breakNumberIntoBytes_(width, 2),
  165. 0x00, 0x00, // width
  166. ...this.breakNumberIntoBytes_(height, 2),
  167. 0x00, 0x00, // height
  168. ]);
  169. return Mp4Generator.box('tkhd', bytes);
  170. }
  171. /**
  172. * Generate a MDIA box
  173. *
  174. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  175. * @return {!Uint8Array}
  176. * @private
  177. */
  178. mdia_(streamInfo) {
  179. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  180. return Mp4Generator.box('mdia', this.mdhd_(streamInfo),
  181. this.hdlr_(streamInfo), this.minf_(streamInfo));
  182. }
  183. /**
  184. * Generate a MDHD box
  185. *
  186. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  187. * @return {!Uint8Array}
  188. * @private
  189. */
  190. mdhd_(streamInfo) {
  191. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  192. const duration = streamInfo.duration * streamInfo.timescale;
  193. const upperWordDuration =
  194. Math.floor(duration / (Mp4Generator.UINT32_MAX_ + 1));
  195. const lowerWordDuration =
  196. Math.floor(duration % (Mp4Generator.UINT32_MAX_ + 1));
  197. const language = streamInfo.stream.language;
  198. const languageNumber = ((language.charCodeAt(0) - 0x60) << 10) |
  199. ((language.charCodeAt(1) - 0x60) << 5) |
  200. ((language.charCodeAt(2) - 0x60));
  201. const bytes = new Uint8Array([
  202. 0x01, // version 1
  203. 0x00, 0x00, 0x00, // flags
  204. 0x00, 0x00, 0x00, 0x00,
  205. 0x00, 0x00, 0x00, 0x02, // creation_time
  206. 0x00, 0x00, 0x00, 0x00,
  207. 0x00, 0x00, 0x00, 0x03, // modification_time
  208. ...this.breakNumberIntoBytes_(streamInfo.timescale, 4), // timescale
  209. ...this.breakNumberIntoBytes_(upperWordDuration, 4),
  210. ...this.breakNumberIntoBytes_(lowerWordDuration, 4), // duration
  211. ...this.breakNumberIntoBytes_(languageNumber, 2), // language
  212. 0x00, 0x00,
  213. ]);
  214. return Mp4Generator.box('mdhd', bytes);
  215. }
  216. /**
  217. * Generate a HDLR box
  218. *
  219. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  220. * @return {!Uint8Array}
  221. * @private
  222. */
  223. hdlr_(streamInfo) {
  224. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  225. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  226. let bytes = new Uint8Array([]);
  227. switch (streamInfo.type) {
  228. case ContentType.VIDEO:
  229. bytes = Mp4Generator.HDLR_TYPES_.video;
  230. break;
  231. case ContentType.AUDIO:
  232. bytes = Mp4Generator.HDLR_TYPES_.audio;
  233. break;
  234. }
  235. return Mp4Generator.box('hdlr', bytes);
  236. }
  237. /**
  238. * Generate a MINF box
  239. *
  240. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  241. * @return {!Uint8Array}
  242. * @private
  243. */
  244. minf_(streamInfo) {
  245. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  246. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  247. switch (streamInfo.type) {
  248. case ContentType.VIDEO:
  249. return Mp4Generator.box(
  250. 'minf', Mp4Generator.box('vmhd', Mp4Generator.VMHD_),
  251. Mp4Generator.DINF_, this.stbl_(streamInfo));
  252. case ContentType.AUDIO:
  253. return Mp4Generator.box(
  254. 'minf', Mp4Generator.box('smhd', Mp4Generator.SMHD_),
  255. Mp4Generator.DINF_, this.stbl_(streamInfo));
  256. }
  257. return new Uint8Array([]);
  258. }
  259. /**
  260. * Generate a STBL box
  261. *
  262. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  263. * @return {!Uint8Array}
  264. * @private
  265. */
  266. stbl_(streamInfo) {
  267. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  268. return Mp4Generator.box(
  269. 'stbl',
  270. this.stsd_(streamInfo),
  271. Mp4Generator.box('stts', Mp4Generator.STTS_),
  272. Mp4Generator.box('stsc', Mp4Generator.STSC_),
  273. Mp4Generator.box('stsz', Mp4Generator.STSZ_),
  274. Mp4Generator.box('stco', Mp4Generator.STCO_));
  275. }
  276. /**
  277. * Generate a STSD box
  278. *
  279. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  280. * @return {!Uint8Array}
  281. * @private
  282. */
  283. stsd_(streamInfo) {
  284. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  285. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  286. let audioCodec = 'aac';
  287. if (streamInfo.codecs.includes('mp3')) {
  288. audioCodec = 'mp3';
  289. } else if (streamInfo.codecs.includes('ac-3')) {
  290. if (shaka.util.Platform.requiresEC3InitSegments()) {
  291. audioCodec = 'ec-3';
  292. } else {
  293. audioCodec = 'ac-3';
  294. }
  295. } else if (streamInfo.codecs.includes('ec-3')) {
  296. audioCodec = 'ec-3';
  297. } else if (streamInfo.codecs.includes('opus')) {
  298. audioCodec = 'opus';
  299. }
  300. let bytes = new Uint8Array([]);
  301. switch (streamInfo.type) {
  302. case ContentType.VIDEO:
  303. if (streamInfo.codecs.includes('avc1')) {
  304. bytes = this.avc1_(streamInfo);
  305. } else if (streamInfo.codecs.includes('hvc1')) {
  306. bytes = this.hvc1_(streamInfo);
  307. }
  308. break;
  309. case ContentType.AUDIO:
  310. if (audioCodec == 'mp3') {
  311. bytes = this.mp3_(streamInfo);
  312. } else if (audioCodec == 'ac-3') {
  313. bytes = this.ac3_(streamInfo);
  314. } else if (audioCodec == 'ec-3') {
  315. bytes = this.ec3_(streamInfo);
  316. } else if (audioCodec == 'opus') {
  317. bytes = this.opus_(streamInfo);
  318. } else {
  319. bytes = this.mp4a_(streamInfo);
  320. }
  321. break;
  322. }
  323. return Mp4Generator.box('stsd', Mp4Generator.STSD_, bytes);
  324. }
  325. /**
  326. * Generate a AVC1 box
  327. *
  328. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  329. * @return {!Uint8Array}
  330. * @private
  331. */
  332. avc1_(streamInfo) {
  333. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  334. const width = streamInfo.stream.width || 0;
  335. const height = streamInfo.stream.height || 0;
  336. let avcCBox;
  337. if (streamInfo.videoConfig.byteLength > 0) {
  338. avcCBox = Mp4Generator.box('avcC', streamInfo.videoConfig);
  339. } else {
  340. avcCBox = Mp4Generator.box('avcC', this.avcC_(streamInfo));
  341. }
  342. const avc1Bytes = new Uint8Array([
  343. 0x00, 0x00, 0x00,
  344. 0x00, 0x00, 0x00, // reserved
  345. 0x00, 0x01, // data_reference_index
  346. 0x00, 0x00, // pre_defined
  347. 0x00, 0x00, // reserved
  348. 0x00, 0x00, 0x00, 0x00,
  349. 0x00, 0x00, 0x00, 0x00,
  350. 0x00, 0x00, 0x00, 0x00, // pre_defined
  351. ...this.breakNumberIntoBytes_(width, 2), // width
  352. ...this.breakNumberIntoBytes_(height, 2), // height
  353. 0x00, 0x48, 0x00, 0x00, // horizontal resolution
  354. 0x00, 0x48, 0x00, 0x00, // vertical resolution
  355. 0x00, 0x00, 0x00, 0x00, // reserved
  356. 0x00, 0x01, // frame_count
  357. 0x13,
  358. 0x76, 0x69, 0x64, 0x65,
  359. 0x6f, 0x6a, 0x73, 0x2d,
  360. 0x63, 0x6f, 0x6e, 0x74,
  361. 0x72, 0x69, 0x62, 0x2d,
  362. 0x68, 0x6c, 0x73, 0x00,
  363. 0x00, 0x00, 0x00, 0x00,
  364. 0x00, 0x00, 0x00, 0x00,
  365. 0x00, 0x00, 0x00, // compressor name
  366. 0x00, 0x18, // depth = 24
  367. 0x11, 0x11, // pre_defined = -1
  368. ]);
  369. let boxName = 'avc1';
  370. const paspBox = this.pasp_(streamInfo);
  371. let sinfBox = new Uint8Array([]);
  372. if (streamInfo.encrypted) {
  373. sinfBox = this.sinf_(streamInfo);
  374. boxName = 'encv';
  375. }
  376. return Mp4Generator.box(boxName, avc1Bytes, avcCBox, paspBox, sinfBox);
  377. }
  378. /**
  379. * Generate a AVCC box
  380. *
  381. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  382. * @return {!Uint8Array}
  383. * @private
  384. */
  385. avcC_(streamInfo) {
  386. const NALU_TYPE_SPS = 7;
  387. const NALU_TYPE_PPS = 8;
  388. // length = 7 by default (0 SPS and 0 PPS)
  389. let avcCLength = 7;
  390. // First get all SPS and PPS from nalus
  391. const sps = [];
  392. const pps = [];
  393. let AVCProfileIndication = 0;
  394. let AVCLevelIndication = 0;
  395. let profileCompatibility = 0;
  396. for (let i = 0; i < streamInfo.videoNalus.length; i++) {
  397. const naluBytes = this.hexStringToBuffer_(streamInfo.videoNalus[i]);
  398. const naluType = naluBytes[0] & 0x1F;
  399. switch (naluType) {
  400. case NALU_TYPE_SPS:
  401. sps.push(naluBytes);
  402. // 2 = sequenceParameterSetLength field length
  403. avcCLength += naluBytes.length + 2;
  404. break;
  405. case NALU_TYPE_PPS:
  406. pps.push(naluBytes);
  407. // 2 = pictureParameterSetLength field length
  408. avcCLength += naluBytes.length + 2;
  409. break;
  410. default:
  411. break;
  412. }
  413. }
  414. // Get profile and level from SPS
  415. if (sps.length > 0) {
  416. AVCProfileIndication = sps[0][1];
  417. profileCompatibility = sps[0][2];
  418. AVCLevelIndication = sps[0][3];
  419. }
  420. // Generate avcC buffer
  421. const avcCBytes = new Uint8Array(avcCLength);
  422. let i = 0;
  423. // configurationVersion = 1
  424. avcCBytes[i++] = 1;
  425. avcCBytes[i++] = AVCProfileIndication;
  426. avcCBytes[i++] = profileCompatibility;
  427. avcCBytes[i++] = AVCLevelIndication;
  428. // '11111' + lengthSizeMinusOne = 3
  429. avcCBytes[i++] = 0xFF;
  430. // '111' + numOfSequenceParameterSets
  431. avcCBytes[i++] = 0xE0 | sps.length;
  432. for (let n = 0; n < sps.length; n++) {
  433. avcCBytes[i++] = (sps[n].length & 0xFF00) >> 8;
  434. avcCBytes[i++] = (sps[n].length & 0x00FF);
  435. avcCBytes.set(sps[n], i);
  436. i += sps[n].length;
  437. }
  438. // numOfPictureParameterSets
  439. avcCBytes[i++] = pps.length;
  440. for (let n = 0; n < pps.length; n++) {
  441. avcCBytes[i++] = (pps[n].length & 0xFF00) >> 8;
  442. avcCBytes[i++] = (pps[n].length & 0x00FF);
  443. avcCBytes.set(pps[n], i);
  444. i += pps[n].length;
  445. }
  446. return avcCBytes;
  447. }
  448. /**
  449. * Generate a HVC1 box
  450. *
  451. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  452. * @return {!Uint8Array}
  453. * @private
  454. */
  455. hvc1_(streamInfo) {
  456. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  457. const width = streamInfo.stream.width || 0;
  458. const height = streamInfo.stream.height || 0;
  459. let hvcCBox = new Uint8Array([]);
  460. if (streamInfo.videoConfig.byteLength > 0) {
  461. hvcCBox = Mp4Generator.box('hvcC', streamInfo.videoConfig);
  462. }
  463. const hvc1Bytes = new Uint8Array([
  464. 0x00, 0x00, 0x00,
  465. 0x00, 0x00, 0x00, // reserved
  466. 0x00, 0x01, // data_reference_index
  467. 0x00, 0x00, // pre_defined
  468. 0x00, 0x00, // reserved
  469. 0x00, 0x00, 0x00, 0x00,
  470. 0x00, 0x00, 0x00, 0x00,
  471. 0x00, 0x00, 0x00, 0x00, // pre_defined
  472. ...this.breakNumberIntoBytes_(width, 2), // width
  473. ...this.breakNumberIntoBytes_(height, 2), // height
  474. 0x00, 0x48, 0x00, 0x00, // horizontal resolution
  475. 0x00, 0x48, 0x00, 0x00, // vertical resolution
  476. 0x00, 0x00, 0x00, 0x00, // reserved
  477. 0x00, 0x01, // frame_count
  478. 0x13,
  479. 0x76, 0x69, 0x64, 0x65,
  480. 0x6f, 0x6a, 0x73, 0x2d,
  481. 0x63, 0x6f, 0x6e, 0x74,
  482. 0x72, 0x69, 0x62, 0x2d,
  483. 0x68, 0x6c, 0x73, 0x00,
  484. 0x00, 0x00, 0x00, 0x00,
  485. 0x00, 0x00, 0x00, 0x00,
  486. 0x00, 0x00, 0x00, // compressor name
  487. 0x00, 0x18, // depth = 24
  488. 0x11, 0x11, // pre_defined = -1
  489. ]);
  490. let boxName = 'hvc1';
  491. const paspBox = this.pasp_(streamInfo);
  492. let sinfBox = new Uint8Array([]);
  493. if (streamInfo.encrypted) {
  494. sinfBox = this.sinf_(streamInfo);
  495. boxName = 'encv';
  496. }
  497. return Mp4Generator.box(boxName, hvc1Bytes, hvcCBox, paspBox, sinfBox);
  498. }
  499. /**
  500. * Generate a PASP box
  501. *
  502. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  503. * @return {!Uint8Array}
  504. * @private
  505. */
  506. pasp_(streamInfo) {
  507. if (!streamInfo.hSpacing && !streamInfo.vSpacing) {
  508. return new Uint8Array([]);
  509. }
  510. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  511. const hSpacing = streamInfo.hSpacing;
  512. const vSpacing = streamInfo.vSpacing;
  513. const bytes = new Uint8Array([
  514. ...this.breakNumberIntoBytes_(hSpacing, 4), // hSpacing
  515. ...this.breakNumberIntoBytes_(vSpacing, 4), // vSpacing
  516. ]);
  517. return Mp4Generator.box('pasp', bytes);
  518. }
  519. /**
  520. * Generate STSD bytes
  521. *
  522. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  523. * @return {!Uint8Array}
  524. * @private
  525. */
  526. audioStsd_(streamInfo) {
  527. const channelsCount = streamInfo.stream.channelsCount || 2;
  528. const audioSamplingRate = streamInfo.stream.audioSamplingRate || 44100;
  529. const bytes = new Uint8Array([
  530. 0x00, 0x00, 0x00, // reserved
  531. 0x00, 0x00, 0x00, // reserved
  532. 0x00, 0x01, // data_reference_index
  533. 0x00, 0x00, 0x00, 0x00,
  534. 0x00, 0x00, 0x00, 0x00, // reserved
  535. 0x00,
  536. channelsCount, // channel count
  537. 0x00, 0x10, // sampleSize:16bits
  538. 0x00, 0x00, 0x00, 0x00, // reserved2
  539. ...this.breakNumberIntoBytes_(audioSamplingRate, 2), // Sample Rate
  540. 0x00, 0x00,
  541. ]);
  542. return bytes;
  543. }
  544. /**
  545. * Generate a .MP3 box
  546. *
  547. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  548. * @return {!Uint8Array}
  549. * @private
  550. */
  551. mp3_(streamInfo) {
  552. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  553. return Mp4Generator.box('.mp3', this.audioStsd_(streamInfo));
  554. }
  555. /**
  556. * Generate a AC-3 box
  557. *
  558. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  559. * @return {!Uint8Array}
  560. * @private
  561. */
  562. ac3_(streamInfo) {
  563. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  564. const dac3Box = Mp4Generator.box('dac3', streamInfo.audioConfig);
  565. let boxName = 'ac-3';
  566. let sinfBox = new Uint8Array([]);
  567. if (streamInfo.encrypted) {
  568. sinfBox = this.sinf_(streamInfo);
  569. boxName = 'enca';
  570. }
  571. return Mp4Generator.box(boxName,
  572. this.audioStsd_(streamInfo), dac3Box, sinfBox);
  573. }
  574. /**
  575. * Generate a EC-3 box
  576. *
  577. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  578. * @return {!Uint8Array}
  579. * @private
  580. */
  581. ec3_(streamInfo) {
  582. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  583. const dec3Box = Mp4Generator.box('dec3', streamInfo.audioConfig);
  584. let boxName = 'ec-3';
  585. let sinfBox = new Uint8Array([]);
  586. if (streamInfo.encrypted) {
  587. sinfBox = this.sinf_(streamInfo);
  588. boxName = 'enca';
  589. }
  590. return Mp4Generator.box(boxName,
  591. this.audioStsd_(streamInfo), dec3Box, sinfBox);
  592. }
  593. /**
  594. * Generate a Opus box
  595. *
  596. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  597. * @return {!Uint8Array}
  598. * @private
  599. */
  600. opus_(streamInfo) {
  601. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  602. const dopsBox = Mp4Generator.box('dOps', streamInfo.audioConfig);
  603. let boxName = 'Opus';
  604. let sinfBox = new Uint8Array([]);
  605. if (streamInfo.encrypted) {
  606. sinfBox = this.sinf_(streamInfo);
  607. boxName = 'enca';
  608. }
  609. return Mp4Generator.box(boxName,
  610. this.audioStsd_(streamInfo), dopsBox, sinfBox);
  611. }
  612. /**
  613. * Generate a MP4A box
  614. *
  615. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  616. * @return {!Uint8Array}
  617. * @private
  618. */
  619. mp4a_(streamInfo) {
  620. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  621. let esdsBox;
  622. if (streamInfo.audioConfig.byteLength > 0) {
  623. esdsBox = Mp4Generator.box('esds', streamInfo.audioConfig);
  624. } else {
  625. esdsBox = Mp4Generator.box('esds', this.esds_(streamInfo));
  626. }
  627. let boxName = 'mp4a';
  628. let sinfBox = new Uint8Array([]);
  629. if (streamInfo.encrypted) {
  630. sinfBox = this.sinf_(streamInfo);
  631. boxName = 'enca';
  632. }
  633. return Mp4Generator.box(boxName,
  634. this.audioStsd_(streamInfo), esdsBox, sinfBox);
  635. }
  636. /**
  637. * Generate a ESDS box
  638. *
  639. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  640. * @return {!Uint8Array}
  641. * @private
  642. */
  643. esds_(streamInfo) {
  644. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  645. const id = streamInfo.id + 1;
  646. const channelsCount = streamInfo.stream.channelsCount || 2;
  647. const audioSamplingRate = streamInfo.stream.audioSamplingRate || 44100;
  648. const audioCodec = shaka.util.ManifestParserUtils.guessCodecs(
  649. ContentType.AUDIO, streamInfo.codecs.split(','));
  650. const samplingFrequencyIndex = {
  651. 96000: 0x0,
  652. 88200: 0x1,
  653. 64000: 0x2,
  654. 48000: 0x3,
  655. 44100: 0x4,
  656. 32000: 0x5,
  657. 24000: 0x6,
  658. 22050: 0x7,
  659. 16000: 0x8,
  660. 12000: 0x9,
  661. 11025: 0xA,
  662. 8000: 0xB,
  663. 7350: 0xC,
  664. };
  665. let indexFreq = samplingFrequencyIndex[audioSamplingRate];
  666. // In HE AAC Sampling frequence equals to SamplingRate * 2
  667. if (audioCodec === 'mp4a.40.5' || audioCodec === 'mp4a.40.29') {
  668. indexFreq = samplingFrequencyIndex[audioSamplingRate * 2];
  669. }
  670. const audioObjectType = parseInt(audioCodec.split('.').pop(), 10);
  671. return new Uint8Array([
  672. 0x00, // version
  673. 0x00, 0x00, 0x00, // flags
  674. // ES_Descriptor
  675. 0x03, // tag, ES_DescriptionTag
  676. 0x19, // length
  677. ...this.breakNumberIntoBytes_(id, 2), // ES_ID
  678. 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority
  679. // DecoderConfigDescriptor
  680. 0x04, // tag, DecoderConfigDescriptionTag
  681. 0x11, // length
  682. 0x40, // object type
  683. 0x15, // streamType
  684. 0x00, 0x00, 0x00, // bufferSizeDB
  685. 0x00, 0x00, 0x00, 0x00, // maxBitrate
  686. 0x00, 0x00, 0x00, 0x00, // avgBitrate
  687. // DecoderSpecificInfo
  688. 0x05, // tag, DecoderSpecificInfoTag
  689. 0x02, // length
  690. // ISO/IEC 14496-3, AudioSpecificConfig
  691. // for samplingFrequencyIndex see
  692. // ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
  693. (audioObjectType << 3) | (indexFreq >>> 1),
  694. (indexFreq << 7) | (channelsCount << 3),
  695. 0x06, 0x01, 0x02, // GASpecificConfig
  696. ]);
  697. }
  698. /**
  699. * Generate a MVEX box
  700. *
  701. * @return {!Uint8Array}
  702. * @private
  703. */
  704. mvex_() {
  705. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  706. const trexArrays = [];
  707. for (const streamInfo of this.streamInfos_) {
  708. trexArrays.push(this.trex_(streamInfo));
  709. }
  710. const trexs = shaka.util.Uint8ArrayUtils.concat(...trexArrays);
  711. return Mp4Generator.box('mvex', trexs);
  712. }
  713. /**
  714. * Generate a TREX box
  715. *
  716. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  717. * @return {!Uint8Array}
  718. * @private
  719. */
  720. trex_(streamInfo) {
  721. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  722. const id = streamInfo.id + 1;
  723. const bytes = new Uint8Array([
  724. 0x00, // version 0
  725. 0x00, 0x00, 0x00, // flags
  726. ...this.breakNumberIntoBytes_(id, 4), // track_ID
  727. 0x00, 0x00, 0x00, 0x01, // default_sample_description_index
  728. 0x00, 0x00, 0x00, 0x00, // default_sample_duration
  729. 0x00, 0x00, 0x00, 0x00, // default_sample_size
  730. 0x00, 0x00, 0x00, 0x00, // default_sample_flags
  731. ]);
  732. return Mp4Generator.box('trex', bytes);
  733. }
  734. /**
  735. * Generate a PSSH box
  736. *
  737. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  738. * @return {!Uint8Array}
  739. * @private
  740. */
  741. pssh_(streamInfo) {
  742. const initDatas = [];
  743. if (!streamInfo.encrypted) {
  744. return new Uint8Array([]);
  745. }
  746. for (const drmInfo of streamInfo.stream.drmInfos) {
  747. if (!drmInfo.initData) {
  748. continue;
  749. }
  750. for (const initData of drmInfo.initData) {
  751. initDatas.push(initData.initData);
  752. }
  753. }
  754. const boxes = shaka.util.Uint8ArrayUtils.concat(...initDatas);
  755. return boxes;
  756. }
  757. /**
  758. * Generate a SINF box
  759. *
  760. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  761. * @return {!Uint8Array}
  762. * @private
  763. */
  764. sinf_(streamInfo) {
  765. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  766. return Mp4Generator.box('sinf',
  767. this.frma_(streamInfo), this.schm_(), this.schi_(streamInfo));
  768. }
  769. /**
  770. * Generate a FRMA box
  771. *
  772. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  773. * @return {!Uint8Array}
  774. * @private
  775. */
  776. frma_(streamInfo) {
  777. const codec = streamInfo.codecs.substring(
  778. 0, streamInfo.codecs.indexOf('.'));
  779. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  780. const codecNumber = this.stringToCharCode_(codec);
  781. const bytes = new Uint8Array([
  782. ...this.breakNumberIntoBytes_(codecNumber, 4),
  783. ]);
  784. return Mp4Generator.box('frma', bytes);
  785. }
  786. /**
  787. * Generate a SCHM box
  788. *
  789. * @return {!Uint8Array}
  790. * @private
  791. */
  792. schm_() {
  793. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  794. const bytes = new Uint8Array([
  795. 0x00, // version 0
  796. 0x00, 0x00, 0x00, // flags
  797. 0x63, 0x65, 0x6e, 0x63, // Scheme: cenc
  798. 0x00, 0x01, 0x00, 0x00, // Scheme version: 1.0
  799. ]);
  800. return Mp4Generator.box('schm', bytes);
  801. }
  802. /**
  803. * Generate a SCHI box
  804. *
  805. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  806. * @return {!Uint8Array}
  807. * @private
  808. */
  809. schi_(streamInfo) {
  810. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  811. return Mp4Generator.box('schi', this.tenc_(streamInfo));
  812. }
  813. /**
  814. * Generate a TENC box
  815. *
  816. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  817. * @return {!Uint8Array}
  818. * @private
  819. */
  820. tenc_(streamInfo) {
  821. // Default key ID: all zeros (dummy)
  822. let defaultKeyId = new Uint8Array([
  823. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  824. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  825. ]);
  826. for (const drmInfo of streamInfo.stream.drmInfos) {
  827. if (drmInfo && drmInfo.keyIds && drmInfo.keyIds.size) {
  828. for (const keyId of drmInfo.keyIds) {
  829. defaultKeyId = this.hexStringToBuffer_(keyId);
  830. }
  831. }
  832. }
  833. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  834. const bytes = new Uint8Array([
  835. 0x00, // version 0
  836. 0x00, 0x00, 0x00, // flags
  837. 0x00, 0x00, // Reserved fields
  838. 0x01, // Default protected: true
  839. 0x08, // Default per-sample IV size: 8
  840. ]);
  841. goog.asserts.assert(defaultKeyId, 'Default KID should be non-null');
  842. return Mp4Generator.box('tenc', bytes, defaultKeyId);
  843. }
  844. /**
  845. * Generate a Segment Data (MP4).
  846. *
  847. * @return {!Uint8Array}
  848. */
  849. segmentData() {
  850. const segmentDataArray = [];
  851. for (const streamInfo of this.streamInfos_) {
  852. segmentDataArray.push(
  853. ...[this.moof_(streamInfo), this.mdat_(streamInfo)]);
  854. }
  855. const result = shaka.util.Uint8ArrayUtils.concat(...segmentDataArray);
  856. return result;
  857. }
  858. /**
  859. * Generate a MOOF box
  860. *
  861. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  862. * @return {!Uint8Array}
  863. * @private
  864. */
  865. moof_(streamInfo) {
  866. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  867. return Mp4Generator.box('moof',
  868. this.mfhd_(streamInfo), this.traf_(streamInfo));
  869. }
  870. /**
  871. * Generate a MOOF box
  872. *
  873. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  874. * @return {!Uint8Array}
  875. * @private
  876. */
  877. mfhd_(streamInfo) {
  878. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  879. const sequenceNumber =
  880. streamInfo.data ? streamInfo.data.sequenceNumber : 0;
  881. const bytes = new Uint8Array([
  882. 0x00, // version 0
  883. 0x00, 0x00, 0x00, // flags
  884. ...this.breakNumberIntoBytes_(sequenceNumber, 4),
  885. ]);
  886. return Mp4Generator.box('mfhd', bytes);
  887. }
  888. /**
  889. * Generate a TRAF box
  890. *
  891. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  892. * @return {!Uint8Array}
  893. * @private
  894. */
  895. traf_(streamInfo) {
  896. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  897. const sampleDependencyTable = this.sdtp_(streamInfo);
  898. const offset = sampleDependencyTable.length +
  899. 32 + // tfhd
  900. 20 + // tfdt
  901. 8 + // traf header
  902. 16 + // mfhd
  903. 8 + // moof header
  904. 8; // mdat header;
  905. return Mp4Generator.box('traf',
  906. this.tfhd_(streamInfo),
  907. this.tfdt_(streamInfo),
  908. this.trun_(streamInfo, offset),
  909. sampleDependencyTable);
  910. }
  911. /**
  912. * Generate a SDTP box
  913. *
  914. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  915. * @return {!Uint8Array}
  916. * @private
  917. */
  918. sdtp_(streamInfo) {
  919. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  920. const samples = streamInfo.data ? streamInfo.data.samples : [];
  921. const bytes = new Uint8Array(4 + samples.length);
  922. // leave the full box header (4 bytes) all zero
  923. // write the sample table
  924. for (let i = 0; i < samples.length; i++) {
  925. const flags = samples[i].flags;
  926. bytes[i + 4] = (flags.dependsOn << 4) |
  927. (flags.isDependedOn << 2) |
  928. flags.hasRedundancy;
  929. }
  930. return Mp4Generator.box('sdtp', bytes);
  931. }
  932. /**
  933. * Generate a TFHD box
  934. *
  935. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  936. * @return {!Uint8Array}
  937. * @private
  938. */
  939. tfhd_(streamInfo) {
  940. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  941. const id = streamInfo.id + 1;
  942. const bytes = new Uint8Array([
  943. 0x00, // version 0
  944. 0x00, 0x00, 0x3a, // flags
  945. ...this.breakNumberIntoBytes_(id, 4), // track_ID
  946. 0x00, 0x00, 0x00, 0x01, // sample_description_index
  947. 0x00, 0x00, 0x00, 0x00, // default_sample_duration
  948. 0x00, 0x00, 0x00, 0x00, // default_sample_size
  949. 0x00, 0x00, 0x00, 0x00, // default_sample_flags
  950. ]);
  951. return Mp4Generator.box('tfhd', bytes);
  952. }
  953. /**
  954. * Generate a TFDT box
  955. *
  956. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  957. * @return {!Uint8Array}
  958. * @private
  959. */
  960. tfdt_(streamInfo) {
  961. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  962. const baseMediaDecodeTime =
  963. streamInfo.data ? streamInfo.data.baseMediaDecodeTime : 0;
  964. const upperWordBaseMediaDecodeTime =
  965. Math.floor(baseMediaDecodeTime / (Mp4Generator.UINT32_MAX_ + 1));
  966. const lowerWordBaseMediaDecodeTime =
  967. Math.floor(baseMediaDecodeTime % (Mp4Generator.UINT32_MAX_ + 1));
  968. const bytes = new Uint8Array([
  969. 0x01, // version 1
  970. 0x00, 0x00, 0x00, // flags
  971. ...this.breakNumberIntoBytes_(upperWordBaseMediaDecodeTime, 4),
  972. ...this.breakNumberIntoBytes_(lowerWordBaseMediaDecodeTime, 4),
  973. ]);
  974. return Mp4Generator.box('tfdt', bytes);
  975. }
  976. /**
  977. * Generate a TRUN box
  978. *
  979. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  980. * @param {number} offset
  981. * @return {!Uint8Array}
  982. * @private
  983. */
  984. trun_(streamInfo, offset) {
  985. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  986. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  987. const samples = streamInfo.data ? streamInfo.data.samples : [];
  988. const samplesLength = samples.length;
  989. const bytesLen = 12 + 16 * samplesLength;
  990. const bytes = new Uint8Array(bytesLen);
  991. offset += 8 + bytesLen;
  992. const isVideo = streamInfo.type === ContentType.VIDEO;
  993. bytes.set(
  994. [
  995. // version 1 for video with signed-int sample_composition_time_offset
  996. isVideo ? 0x01 : 0x00,
  997. 0x00, 0x0f, 0x01, // flags
  998. ...this.breakNumberIntoBytes_(samplesLength, 4), // sample_count
  999. ...this.breakNumberIntoBytes_(offset, 4), // data_offset
  1000. ],
  1001. 0,
  1002. );
  1003. for (let i = 0; i < samplesLength; i++) {
  1004. const sample = samples[i];
  1005. const duration = this.breakNumberIntoBytes_(sample.duration, 4);
  1006. const size = this.breakNumberIntoBytes_(sample.size, 4);
  1007. const flags = sample.flags;
  1008. const cts = this.breakNumberIntoBytes_(sample.cts, 4);
  1009. bytes.set(
  1010. [
  1011. ...duration, // sample_duration
  1012. ...size, // sample_size
  1013. (flags.isLeading << 2) | flags.dependsOn,
  1014. (flags.isDependedOn << 6) | (flags.hasRedundancy << 4) |
  1015. flags.isNonSync,
  1016. flags.degradPrio & (0xf0 << 8),
  1017. flags.degradPrio & 0x0f, // sample_flags
  1018. ...cts, // sample_composition_time_offset
  1019. ],
  1020. 12 + 16 * i,
  1021. );
  1022. }
  1023. return Mp4Generator.box('trun', bytes);
  1024. }
  1025. /**
  1026. * Generate a MDAT box
  1027. *
  1028. * @param {shaka.transmuxer.Mp4Generator.StreamInfo} streamInfo
  1029. * @return {!Uint8Array}
  1030. * @private
  1031. */
  1032. mdat_(streamInfo) {
  1033. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  1034. const samples = streamInfo.data ? streamInfo.data.samples : [];
  1035. const allData = samples.map((sample) => sample.data);
  1036. const bytes = shaka.util.Uint8ArrayUtils.concat(...allData);
  1037. return Mp4Generator.box('mdat', bytes);
  1038. }
  1039. /**
  1040. * @param {number} number
  1041. * @param {number} numBytes
  1042. * @return {!Array<number>}
  1043. * @private
  1044. */
  1045. breakNumberIntoBytes_(number, numBytes) {
  1046. const bytes = [];
  1047. for (let byte = numBytes - 1; byte >= 0; byte--) {
  1048. bytes.push((number >> (8 * byte)) & 0xff);
  1049. }
  1050. return bytes;
  1051. }
  1052. /**
  1053. * Convert a hex string to buffer.
  1054. *
  1055. * @param {string} str
  1056. * @return {Uint8Array}
  1057. * @private
  1058. */
  1059. hexStringToBuffer_(str) {
  1060. const buf = new Uint8Array(str.length / 2);
  1061. for (let i = 0; i < str.length / 2; i += 1) {
  1062. buf[i] = parseInt(String(str[i * 2] + str[i * 2 + 1]), 16);
  1063. }
  1064. return buf;
  1065. }
  1066. /**
  1067. * Convert a string to char code.
  1068. *
  1069. * @param {string} str
  1070. * @return {number}
  1071. * @private
  1072. */
  1073. stringToCharCode_(str) {
  1074. let code = 0;
  1075. for (let i = 0; i < str.length; i += 1) {
  1076. code |= str.charCodeAt(i) << ((str.length - i - 1) * 8);
  1077. }
  1078. return code;
  1079. }
  1080. /**
  1081. * @private
  1082. */
  1083. static initStaticProperties_() {
  1084. const Mp4Generator = shaka.transmuxer.Mp4Generator;
  1085. if (Mp4Generator.initialized_) {
  1086. return;
  1087. }
  1088. Mp4Generator.initialized_ = true;
  1089. const majorBrand = new Uint8Array([105, 115, 111, 109]); // isom
  1090. const avc1Brand = new Uint8Array([97, 118, 99, 49]); // avc1
  1091. const minorVersion = new Uint8Array([0, 0, 0, 1]);
  1092. Mp4Generator.FTYP_ = Mp4Generator.box(
  1093. 'ftyp', majorBrand, minorVersion, majorBrand, avc1Brand);
  1094. const drefBox = Mp4Generator.box('dref', Mp4Generator.DREF_);
  1095. Mp4Generator.DINF_ = Mp4Generator.box('dinf', drefBox);
  1096. }
  1097. /**
  1098. * Generate a box
  1099. *
  1100. * @param {string} boxName
  1101. * @param {...!Uint8Array} payload
  1102. * @return {!Uint8Array}
  1103. */
  1104. static box(boxName, ...payload) {
  1105. let type = shaka.transmuxer.Mp4Generator.BOX_TYPES_.get(boxName);
  1106. if (!type) {
  1107. type = [
  1108. boxName.charCodeAt(0),
  1109. boxName.charCodeAt(1),
  1110. boxName.charCodeAt(2),
  1111. boxName.charCodeAt(3),
  1112. ];
  1113. shaka.transmuxer.Mp4Generator.BOX_TYPES_.set(boxName, type);
  1114. }
  1115. // make the header for the box
  1116. let size = 8;
  1117. // calculate the total size we need to allocate
  1118. for (let i = payload.length - 1; i >= 0; i--) {
  1119. size += payload[i].byteLength;
  1120. }
  1121. const result = new Uint8Array(size);
  1122. result[0] = (size >> 24) & 0xff;
  1123. result[1] = (size >> 16) & 0xff;
  1124. result[2] = (size >> 8) & 0xff;
  1125. result[3] = size & 0xff;
  1126. result.set(type, 4);
  1127. // copy the payload into the result
  1128. for (let i = 0, pointer = 8; i < payload.length; i++) {
  1129. // copy payload[i] array @ offset pointer
  1130. result.set(payload[i], pointer);
  1131. pointer += payload[i].byteLength;
  1132. }
  1133. return result;
  1134. }
  1135. };
  1136. /**
  1137. * @private {boolean}
  1138. */
  1139. shaka.transmuxer.Mp4Generator.initialized_ = false;
  1140. /**
  1141. * @private {number}
  1142. */
  1143. shaka.transmuxer.Mp4Generator.UINT32_MAX_ = Math.pow(2, 32) - 1;
  1144. /**
  1145. * @private {!Map<string, !Array<number>>}
  1146. */
  1147. shaka.transmuxer.Mp4Generator.BOX_TYPES_ = new Map();
  1148. /**
  1149. * @private {{video: !Uint8Array, audio: !Uint8Array}}
  1150. */
  1151. shaka.transmuxer.Mp4Generator.HDLR_TYPES_ = {
  1152. video: new Uint8Array([
  1153. 0x00, // version 0
  1154. 0x00, 0x00, 0x00, // flags
  1155. 0x00, 0x00, 0x00, 0x00, // pre_defined
  1156. 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
  1157. 0x00, 0x00, 0x00, 0x00, // reserved
  1158. 0x00, 0x00, 0x00, 0x00, // reserved
  1159. 0x00, 0x00, 0x00, 0x00, // reserved
  1160. 0x56, 0x69, 0x64, 0x65,
  1161. 0x6f, 0x48, 0x61, 0x6e,
  1162. 0x64, 0x6c, 0x65, 0x72, 0x00, // name: 'VideoHandler'
  1163. ]),
  1164. audio: new Uint8Array([
  1165. 0x00, // version 0
  1166. 0x00, 0x00, 0x00, // flags
  1167. 0x00, 0x00, 0x00, 0x00, // pre_defined
  1168. 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'
  1169. 0x00, 0x00, 0x00, 0x00, // reserved
  1170. 0x00, 0x00, 0x00, 0x00, // reserved
  1171. 0x00, 0x00, 0x00, 0x00, // reserved
  1172. 0x53, 0x6f, 0x75, 0x6e,
  1173. 0x64, 0x48, 0x61, 0x6e,
  1174. 0x64, 0x6c, 0x65, 0x72, 0x00, // name: 'SoundHandler'
  1175. ]),
  1176. };
  1177. /**
  1178. * @private {!Uint8Array}
  1179. */
  1180. shaka.transmuxer.Mp4Generator.STTS_ = new Uint8Array([
  1181. 0x00, // version
  1182. 0x00, 0x00, 0x00, // flags
  1183. 0x00, 0x00, 0x00, 0x00, // entry_count
  1184. ]);
  1185. /**
  1186. * @private {!Uint8Array}
  1187. */
  1188. shaka.transmuxer.Mp4Generator.STSC_ = new Uint8Array([
  1189. 0x00, // version
  1190. 0x00, 0x00, 0x00, // flags
  1191. 0x00, 0x00, 0x00, 0x00, // entry_count
  1192. ]);
  1193. /**
  1194. * @private {!Uint8Array}
  1195. */
  1196. shaka.transmuxer.Mp4Generator.STCO_ = new Uint8Array([
  1197. 0x00, // version
  1198. 0x00, 0x00, 0x00, // flags
  1199. 0x00, 0x00, 0x00, 0x00, // entry_count
  1200. ]);
  1201. /**
  1202. * @private {!Uint8Array}
  1203. */
  1204. shaka.transmuxer.Mp4Generator.STSZ_ = new Uint8Array([
  1205. 0x00, // version
  1206. 0x00, 0x00, 0x00, // flags
  1207. 0x00, 0x00, 0x00, 0x00, // sample_size
  1208. 0x00, 0x00, 0x00, 0x00, // sample_count
  1209. ]);
  1210. /**
  1211. * @private {!Uint8Array}
  1212. */
  1213. shaka.transmuxer.Mp4Generator.VMHD_ = new Uint8Array([
  1214. 0x00, // version
  1215. 0x00, 0x00, 0x01, // flags
  1216. 0x00, 0x00, // graphics mode
  1217. 0x00, 0x00,
  1218. 0x00, 0x00,
  1219. 0x00, 0x00, // op color
  1220. ]);
  1221. /**
  1222. * @private {!Uint8Array}
  1223. */
  1224. shaka.transmuxer.Mp4Generator.SMHD_ = new Uint8Array([
  1225. 0x00, // version
  1226. 0x00, 0x00, 0x00, // flags
  1227. 0x00, 0x00, // balance, 0 means centered
  1228. 0x00, 0x00, // reserved
  1229. ]);
  1230. /**
  1231. * @private {!Uint8Array}
  1232. */
  1233. shaka.transmuxer.Mp4Generator.STSD_ = new Uint8Array([
  1234. 0x00, // version 0
  1235. 0x00, 0x00, 0x00, // flags
  1236. 0x00, 0x00, 0x00, 0x01, // entry_count
  1237. ]);
  1238. /**
  1239. * @private {!Uint8Array}
  1240. */
  1241. shaka.transmuxer.Mp4Generator.FTYP_ = new Uint8Array([]);
  1242. /**
  1243. * @private {!Uint8Array}
  1244. */
  1245. shaka.transmuxer.Mp4Generator.DREF_ = new Uint8Array([
  1246. 0x00, // version 0
  1247. 0x00, 0x00, 0x00, // flags
  1248. 0x00, 0x00, 0x00, 0x01, // entry_count
  1249. 0x00, 0x00, 0x00, 0x0c, // entry_size
  1250. 0x75, 0x72, 0x6c, 0x20, // 'url' type
  1251. 0x00, // version 0
  1252. 0x00, 0x00, 0x01, // entry_flags
  1253. ]);
  1254. /**
  1255. * @private {!Uint8Array}
  1256. */
  1257. shaka.transmuxer.Mp4Generator.DINF_ = new Uint8Array([]);
  1258. /**
  1259. * @typedef {{
  1260. * id: number,
  1261. * type: string,
  1262. * codecs: string,
  1263. * encrypted: boolean,
  1264. * timescale: number,
  1265. * duration: number,
  1266. * videoNalus: !Array<string>,
  1267. * audioConfig: !Uint8Array,
  1268. * videoConfig: !Uint8Array,
  1269. * hSpacing: number,
  1270. * vSpacing: number,
  1271. * data: ?shaka.transmuxer.Mp4Generator.Data,
  1272. * stream: !shaka.extern.Stream
  1273. * }}
  1274. *
  1275. * @property {number} id
  1276. * A unique ID
  1277. * @property {string} type
  1278. * Indicate the content type: 'video' or 'audio'.
  1279. * @property {string} codecs
  1280. * <i>Defaults to '' (i.e., unknown / not needed).</i> <br>
  1281. * The Stream's codecs, e.g., 'avc1.4d4015' or 'vp9'<br>
  1282. * See {@link https://tools.ietf.org/html/rfc6381}
  1283. * @property {boolean} encrypted
  1284. * Indicate if the stream is encrypted.
  1285. * @property {number} timescale
  1286. * The Stream's timescale.
  1287. * @property {number} duration
  1288. * The Stream's duration.
  1289. * @property {!Array<string>} videoNalus
  1290. * The stream's video nalus.
  1291. * @property {!Uint8Array} audioConfig
  1292. * The stream's audio config.
  1293. * @property {!Uint8Array} videoConfig
  1294. * The stream's video config.
  1295. * @property {number} hSpacing
  1296. * The stream's video horizontal spacing of pixels.
  1297. * @property {number} vSpacing
  1298. * The stream's video vertical spacing of pixels.
  1299. * @property {?shaka.transmuxer.Mp4Generator.Data} data
  1300. * The stream's data.
  1301. * @property {!shaka.extern.Stream} stream
  1302. * The Stream.
  1303. */
  1304. shaka.transmuxer.Mp4Generator.StreamInfo;
  1305. /**
  1306. * @typedef {{
  1307. * sequenceNumber: number,
  1308. * baseMediaDecodeTime: number,
  1309. * samples: !Array<shaka.transmuxer.Mp4Generator.Mp4Sample>
  1310. * }}
  1311. *
  1312. * @property {number} sequenceNumber
  1313. * The sequence number.
  1314. * @property {number} baseMediaDecodeTime
  1315. * The base media decode time.
  1316. * @property {!Array<shaka.transmuxer.Mp4Generator.Mp4Sample>} samples
  1317. * The data samples.
  1318. */
  1319. shaka.transmuxer.Mp4Generator.Data;
  1320. /**
  1321. * @typedef {{
  1322. * data: !Uint8Array,
  1323. * size: number,
  1324. * duration: number,
  1325. * cts: number,
  1326. * flags: !shaka.transmuxer.Mp4Generator.Mp4SampleFlags
  1327. * }}
  1328. *
  1329. * @property {!Uint8Array} data
  1330. * The sample data.
  1331. * @property {number} size
  1332. * The sample size.
  1333. * @property {number} duration
  1334. * The sample duration.
  1335. * @property {number} cts
  1336. * The sample composition time.
  1337. * @property {!shaka.transmuxer.Mp4Generator.Mp4SampleFlags} flags
  1338. * The sample flags.
  1339. */
  1340. shaka.transmuxer.Mp4Generator.Mp4Sample;
  1341. /**
  1342. * @typedef {{
  1343. * isLeading: number,
  1344. * isDependedOn: number,
  1345. * hasRedundancy: number,
  1346. * degradPrio: number,
  1347. * dependsOn: number,
  1348. * isNonSync: number
  1349. * }}
  1350. *
  1351. * @property {number} isLeading
  1352. * @property {number} isDependedOn
  1353. * @property {number} hasRedundancy
  1354. * @property {number} degradPrio
  1355. * @property {number} dependsOn
  1356. * @property {number} isNonSync
  1357. */
  1358. shaka.transmuxer.Mp4Generator.Mp4SampleFlags;