mp4-generator.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800
  1. /**
  2. * mux.js
  3. *
  4. * Copyright (c) Brightcove
  5. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  6. *
  7. * Functions that generate fragmented MP4s suitable for use with Media
  8. * Source Extensions.
  9. */
  10. 'use strict';
  11. var MAX_UINT32 = require('../utils/numbers.js').MAX_UINT32;
  12. var box, dinf, esds, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd,
  13. trak, tkhd, mdia, mdhd, hdlr, sdtp, stbl, stsd, traf, trex,
  14. trun, types, MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR,
  15. AUDIO_HDLR, HDLR_TYPES, VMHD, SMHD, DREF, STCO, STSC, STSZ, STTS;
  16. // pre-calculate constants
  17. (function() {
  18. var i;
  19. types = {
  20. avc1: [], // codingname
  21. avcC: [],
  22. btrt: [],
  23. dinf: [],
  24. dref: [],
  25. esds: [],
  26. ftyp: [],
  27. hdlr: [],
  28. mdat: [],
  29. mdhd: [],
  30. mdia: [],
  31. mfhd: [],
  32. minf: [],
  33. moof: [],
  34. moov: [],
  35. mp4a: [], // codingname
  36. mvex: [],
  37. mvhd: [],
  38. pasp: [],
  39. sdtp: [],
  40. smhd: [],
  41. stbl: [],
  42. stco: [],
  43. stsc: [],
  44. stsd: [],
  45. stsz: [],
  46. stts: [],
  47. styp: [],
  48. tfdt: [],
  49. tfhd: [],
  50. traf: [],
  51. trak: [],
  52. trun: [],
  53. trex: [],
  54. tkhd: [],
  55. vmhd: []
  56. };
  57. // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we
  58. // don't throw an error
  59. if (typeof Uint8Array === 'undefined') {
  60. return;
  61. }
  62. for (i in types) {
  63. if (types.hasOwnProperty(i)) {
  64. types[i] = [
  65. i.charCodeAt(0),
  66. i.charCodeAt(1),
  67. i.charCodeAt(2),
  68. i.charCodeAt(3)
  69. ];
  70. }
  71. }
  72. MAJOR_BRAND = new Uint8Array([
  73. 'i'.charCodeAt(0),
  74. 's'.charCodeAt(0),
  75. 'o'.charCodeAt(0),
  76. 'm'.charCodeAt(0)
  77. ]);
  78. AVC1_BRAND = new Uint8Array([
  79. 'a'.charCodeAt(0),
  80. 'v'.charCodeAt(0),
  81. 'c'.charCodeAt(0),
  82. '1'.charCodeAt(0)
  83. ]);
  84. MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
  85. VIDEO_HDLR = new Uint8Array([
  86. 0x00, // version 0
  87. 0x00, 0x00, 0x00, // flags
  88. 0x00, 0x00, 0x00, 0x00, // pre_defined
  89. 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
  90. 0x00, 0x00, 0x00, 0x00, // reserved
  91. 0x00, 0x00, 0x00, 0x00, // reserved
  92. 0x00, 0x00, 0x00, 0x00, // reserved
  93. 0x56, 0x69, 0x64, 0x65,
  94. 0x6f, 0x48, 0x61, 0x6e,
  95. 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
  96. ]);
  97. AUDIO_HDLR = new Uint8Array([
  98. 0x00, // version 0
  99. 0x00, 0x00, 0x00, // flags
  100. 0x00, 0x00, 0x00, 0x00, // pre_defined
  101. 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'
  102. 0x00, 0x00, 0x00, 0x00, // reserved
  103. 0x00, 0x00, 0x00, 0x00, // reserved
  104. 0x00, 0x00, 0x00, 0x00, // reserved
  105. 0x53, 0x6f, 0x75, 0x6e,
  106. 0x64, 0x48, 0x61, 0x6e,
  107. 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
  108. ]);
  109. HDLR_TYPES = {
  110. video: VIDEO_HDLR,
  111. audio: AUDIO_HDLR
  112. };
  113. DREF = new Uint8Array([
  114. 0x00, // version 0
  115. 0x00, 0x00, 0x00, // flags
  116. 0x00, 0x00, 0x00, 0x01, // entry_count
  117. 0x00, 0x00, 0x00, 0x0c, // entry_size
  118. 0x75, 0x72, 0x6c, 0x20, // 'url' type
  119. 0x00, // version 0
  120. 0x00, 0x00, 0x01 // entry_flags
  121. ]);
  122. SMHD = new Uint8Array([
  123. 0x00, // version
  124. 0x00, 0x00, 0x00, // flags
  125. 0x00, 0x00, // balance, 0 means centered
  126. 0x00, 0x00 // reserved
  127. ]);
  128. STCO = new Uint8Array([
  129. 0x00, // version
  130. 0x00, 0x00, 0x00, // flags
  131. 0x00, 0x00, 0x00, 0x00 // entry_count
  132. ]);
  133. STSC = STCO;
  134. STSZ = new Uint8Array([
  135. 0x00, // version
  136. 0x00, 0x00, 0x00, // flags
  137. 0x00, 0x00, 0x00, 0x00, // sample_size
  138. 0x00, 0x00, 0x00, 0x00 // sample_count
  139. ]);
  140. STTS = STCO;
  141. VMHD = new Uint8Array([
  142. 0x00, // version
  143. 0x00, 0x00, 0x01, // flags
  144. 0x00, 0x00, // graphicsmode
  145. 0x00, 0x00,
  146. 0x00, 0x00,
  147. 0x00, 0x00 // opcolor
  148. ]);
  149. }());
  150. box = function(type) {
  151. var
  152. payload = [],
  153. size = 0,
  154. i,
  155. result,
  156. view;
  157. for (i = 1; i < arguments.length; i++) {
  158. payload.push(arguments[i]);
  159. }
  160. i = payload.length;
  161. // calculate the total size we need to allocate
  162. while (i--) {
  163. size += payload[i].byteLength;
  164. }
  165. result = new Uint8Array(size + 8);
  166. view = new DataView(result.buffer, result.byteOffset, result.byteLength);
  167. view.setUint32(0, result.byteLength);
  168. result.set(type, 4);
  169. // copy the payload into the result
  170. for (i = 0, size = 8; i < payload.length; i++) {
  171. result.set(payload[i], size);
  172. size += payload[i].byteLength;
  173. }
  174. return result;
  175. };
  176. dinf = function() {
  177. return box(types.dinf, box(types.dref, DREF));
  178. };
  179. esds = function(track) {
  180. return box(types.esds, new Uint8Array([
  181. 0x00, // version
  182. 0x00, 0x00, 0x00, // flags
  183. // ES_Descriptor
  184. 0x03, // tag, ES_DescrTag
  185. 0x19, // length
  186. 0x00, 0x00, // ES_ID
  187. 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority
  188. // DecoderConfigDescriptor
  189. 0x04, // tag, DecoderConfigDescrTag
  190. 0x11, // length
  191. 0x40, // object type
  192. 0x15, // streamType
  193. 0x00, 0x06, 0x00, // bufferSizeDB
  194. 0x00, 0x00, 0xda, 0xc0, // maxBitrate
  195. 0x00, 0x00, 0xda, 0xc0, // avgBitrate
  196. // DecoderSpecificInfo
  197. 0x05, // tag, DecoderSpecificInfoTag
  198. 0x02, // length
  199. // ISO/IEC 14496-3, AudioSpecificConfig
  200. // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
  201. (track.audioobjecttype << 3) | (track.samplingfrequencyindex >>> 1),
  202. (track.samplingfrequencyindex << 7) | (track.channelcount << 3),
  203. 0x06, 0x01, 0x02 // GASpecificConfig
  204. ]));
  205. };
  206. ftyp = function() {
  207. return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
  208. };
  209. hdlr = function(type) {
  210. return box(types.hdlr, HDLR_TYPES[type]);
  211. };
  212. mdat = function(data) {
  213. return box(types.mdat, data);
  214. };
  215. mdhd = function(track) {
  216. var result = new Uint8Array([
  217. 0x00, // version 0
  218. 0x00, 0x00, 0x00, // flags
  219. 0x00, 0x00, 0x00, 0x02, // creation_time
  220. 0x00, 0x00, 0x00, 0x03, // modification_time
  221. 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
  222. (track.duration >>> 24) & 0xFF,
  223. (track.duration >>> 16) & 0xFF,
  224. (track.duration >>> 8) & 0xFF,
  225. track.duration & 0xFF, // duration
  226. 0x55, 0xc4, // 'und' language (undetermined)
  227. 0x00, 0x00
  228. ]);
  229. // Use the sample rate from the track metadata, when it is
  230. // defined. The sample rate can be parsed out of an ADTS header, for
  231. // instance.
  232. if (track.samplerate) {
  233. result[12] = (track.samplerate >>> 24) & 0xFF;
  234. result[13] = (track.samplerate >>> 16) & 0xFF;
  235. result[14] = (track.samplerate >>> 8) & 0xFF;
  236. result[15] = (track.samplerate) & 0xFF;
  237. }
  238. return box(types.mdhd, result);
  239. };
  240. mdia = function(track) {
  241. return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
  242. };
  243. mfhd = function(sequenceNumber) {
  244. return box(types.mfhd, new Uint8Array([
  245. 0x00,
  246. 0x00, 0x00, 0x00, // flags
  247. (sequenceNumber & 0xFF000000) >> 24,
  248. (sequenceNumber & 0xFF0000) >> 16,
  249. (sequenceNumber & 0xFF00) >> 8,
  250. sequenceNumber & 0xFF // sequence_number
  251. ]));
  252. };
  253. minf = function(track) {
  254. return box(types.minf,
  255. track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD),
  256. dinf(),
  257. stbl(track));
  258. };
  259. moof = function(sequenceNumber, tracks) {
  260. var
  261. trackFragments = [],
  262. i = tracks.length;
  263. // build traf boxes for each track fragment
  264. while (i--) {
  265. trackFragments[i] = traf(tracks[i]);
  266. }
  267. return box.apply(null, [
  268. types.moof,
  269. mfhd(sequenceNumber)
  270. ].concat(trackFragments));
  271. };
  272. /**
  273. * Returns a movie box.
  274. * @param tracks {array} the tracks associated with this movie
  275. * @see ISO/IEC 14496-12:2012(E), section 8.2.1
  276. */
  277. moov = function(tracks) {
  278. var
  279. i = tracks.length,
  280. boxes = [];
  281. while (i--) {
  282. boxes[i] = trak(tracks[i]);
  283. }
  284. return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
  285. };
  286. mvex = function(tracks) {
  287. var
  288. i = tracks.length,
  289. boxes = [];
  290. while (i--) {
  291. boxes[i] = trex(tracks[i]);
  292. }
  293. return box.apply(null, [types.mvex].concat(boxes));
  294. };
  295. mvhd = function(duration) {
  296. var
  297. bytes = new Uint8Array([
  298. 0x00, // version 0
  299. 0x00, 0x00, 0x00, // flags
  300. 0x00, 0x00, 0x00, 0x01, // creation_time
  301. 0x00, 0x00, 0x00, 0x02, // modification_time
  302. 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
  303. (duration & 0xFF000000) >> 24,
  304. (duration & 0xFF0000) >> 16,
  305. (duration & 0xFF00) >> 8,
  306. duration & 0xFF, // duration
  307. 0x00, 0x01, 0x00, 0x00, // 1.0 rate
  308. 0x01, 0x00, // 1.0 volume
  309. 0x00, 0x00, // reserved
  310. 0x00, 0x00, 0x00, 0x00, // reserved
  311. 0x00, 0x00, 0x00, 0x00, // reserved
  312. 0x00, 0x01, 0x00, 0x00,
  313. 0x00, 0x00, 0x00, 0x00,
  314. 0x00, 0x00, 0x00, 0x00,
  315. 0x00, 0x00, 0x00, 0x00,
  316. 0x00, 0x01, 0x00, 0x00,
  317. 0x00, 0x00, 0x00, 0x00,
  318. 0x00, 0x00, 0x00, 0x00,
  319. 0x00, 0x00, 0x00, 0x00,
  320. 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
  321. 0x00, 0x00, 0x00, 0x00,
  322. 0x00, 0x00, 0x00, 0x00,
  323. 0x00, 0x00, 0x00, 0x00,
  324. 0x00, 0x00, 0x00, 0x00,
  325. 0x00, 0x00, 0x00, 0x00,
  326. 0x00, 0x00, 0x00, 0x00, // pre_defined
  327. 0xff, 0xff, 0xff, 0xff // next_track_ID
  328. ]);
  329. return box(types.mvhd, bytes);
  330. };
  331. sdtp = function(track) {
  332. var
  333. samples = track.samples || [],
  334. bytes = new Uint8Array(4 + samples.length),
  335. flags,
  336. i;
  337. // leave the full box header (4 bytes) all zero
  338. // write the sample table
  339. for (i = 0; i < samples.length; i++) {
  340. flags = samples[i].flags;
  341. bytes[i + 4] = (flags.dependsOn << 4) |
  342. (flags.isDependedOn << 2) |
  343. (flags.hasRedundancy);
  344. }
  345. return box(types.sdtp,
  346. bytes);
  347. };
  348. stbl = function(track) {
  349. return box(types.stbl,
  350. stsd(track),
  351. box(types.stts, STTS),
  352. box(types.stsc, STSC),
  353. box(types.stsz, STSZ),
  354. box(types.stco, STCO));
  355. };
  356. (function() {
  357. var videoSample, audioSample;
  358. stsd = function(track) {
  359. return box(types.stsd, new Uint8Array([
  360. 0x00, // version 0
  361. 0x00, 0x00, 0x00, // flags
  362. 0x00, 0x00, 0x00, 0x01
  363. ]), track.type === 'video' ? videoSample(track) : audioSample(track));
  364. };
  365. videoSample = function(track) {
  366. var
  367. sps = track.sps || [],
  368. pps = track.pps || [],
  369. sequenceParameterSets = [],
  370. pictureParameterSets = [],
  371. i,
  372. avc1Box;
  373. // assemble the SPSs
  374. for (i = 0; i < sps.length; i++) {
  375. sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8);
  376. sequenceParameterSets.push((sps[i].byteLength & 0xFF)); // sequenceParameterSetLength
  377. sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS
  378. }
  379. // assemble the PPSs
  380. for (i = 0; i < pps.length; i++) {
  381. pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8);
  382. pictureParameterSets.push((pps[i].byteLength & 0xFF));
  383. pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]));
  384. }
  385. avc1Box = [
  386. types.avc1, new Uint8Array([
  387. 0x00, 0x00, 0x00,
  388. 0x00, 0x00, 0x00, // reserved
  389. 0x00, 0x01, // data_reference_index
  390. 0x00, 0x00, // pre_defined
  391. 0x00, 0x00, // reserved
  392. 0x00, 0x00, 0x00, 0x00,
  393. 0x00, 0x00, 0x00, 0x00,
  394. 0x00, 0x00, 0x00, 0x00, // pre_defined
  395. (track.width & 0xff00) >> 8,
  396. track.width & 0xff, // width
  397. (track.height & 0xff00) >> 8,
  398. track.height & 0xff, // height
  399. 0x00, 0x48, 0x00, 0x00, // horizresolution
  400. 0x00, 0x48, 0x00, 0x00, // vertresolution
  401. 0x00, 0x00, 0x00, 0x00, // reserved
  402. 0x00, 0x01, // frame_count
  403. 0x13,
  404. 0x76, 0x69, 0x64, 0x65,
  405. 0x6f, 0x6a, 0x73, 0x2d,
  406. 0x63, 0x6f, 0x6e, 0x74,
  407. 0x72, 0x69, 0x62, 0x2d,
  408. 0x68, 0x6c, 0x73, 0x00,
  409. 0x00, 0x00, 0x00, 0x00,
  410. 0x00, 0x00, 0x00, 0x00,
  411. 0x00, 0x00, 0x00, // compressorname
  412. 0x00, 0x18, // depth = 24
  413. 0x11, 0x11 // pre_defined = -1
  414. ]),
  415. box(types.avcC, new Uint8Array([
  416. 0x01, // configurationVersion
  417. track.profileIdc, // AVCProfileIndication
  418. track.profileCompatibility, // profile_compatibility
  419. track.levelIdc, // AVCLevelIndication
  420. 0xff // lengthSizeMinusOne, hard-coded to 4 bytes
  421. ].concat(
  422. [sps.length], // numOfSequenceParameterSets
  423. sequenceParameterSets, // "SPS"
  424. [pps.length], // numOfPictureParameterSets
  425. pictureParameterSets // "PPS"
  426. ))),
  427. box(types.btrt, new Uint8Array([
  428. 0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
  429. 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
  430. 0x00, 0x2d, 0xc6, 0xc0 // avgBitrate
  431. ]))
  432. ];
  433. if (track.sarRatio) {
  434. var
  435. hSpacing = track.sarRatio[0],
  436. vSpacing = track.sarRatio[1];
  437. avc1Box.push(
  438. box(types.pasp, new Uint8Array([
  439. (hSpacing & 0xFF000000) >> 24,
  440. (hSpacing & 0xFF0000) >> 16,
  441. (hSpacing & 0xFF00) >> 8,
  442. hSpacing & 0xFF,
  443. (vSpacing & 0xFF000000) >> 24,
  444. (vSpacing & 0xFF0000) >> 16,
  445. (vSpacing & 0xFF00) >> 8,
  446. vSpacing & 0xFF
  447. ]))
  448. );
  449. }
  450. return box.apply(null, avc1Box);
  451. };
  452. audioSample = function(track) {
  453. return box(types.mp4a, new Uint8Array([
  454. // SampleEntry, ISO/IEC 14496-12
  455. 0x00, 0x00, 0x00,
  456. 0x00, 0x00, 0x00, // reserved
  457. 0x00, 0x01, // data_reference_index
  458. // AudioSampleEntry, ISO/IEC 14496-12
  459. 0x00, 0x00, 0x00, 0x00, // reserved
  460. 0x00, 0x00, 0x00, 0x00, // reserved
  461. (track.channelcount & 0xff00) >> 8,
  462. (track.channelcount & 0xff), // channelcount
  463. (track.samplesize & 0xff00) >> 8,
  464. (track.samplesize & 0xff), // samplesize
  465. 0x00, 0x00, // pre_defined
  466. 0x00, 0x00, // reserved
  467. (track.samplerate & 0xff00) >> 8,
  468. (track.samplerate & 0xff),
  469. 0x00, 0x00 // samplerate, 16.16
  470. // MP4AudioSampleEntry, ISO/IEC 14496-14
  471. ]), esds(track));
  472. };
  473. }());
  474. tkhd = function(track) {
  475. var result = new Uint8Array([
  476. 0x00, // version 0
  477. 0x00, 0x00, 0x07, // flags
  478. 0x00, 0x00, 0x00, 0x00, // creation_time
  479. 0x00, 0x00, 0x00, 0x00, // modification_time
  480. (track.id & 0xFF000000) >> 24,
  481. (track.id & 0xFF0000) >> 16,
  482. (track.id & 0xFF00) >> 8,
  483. track.id & 0xFF, // track_ID
  484. 0x00, 0x00, 0x00, 0x00, // reserved
  485. (track.duration & 0xFF000000) >> 24,
  486. (track.duration & 0xFF0000) >> 16,
  487. (track.duration & 0xFF00) >> 8,
  488. track.duration & 0xFF, // duration
  489. 0x00, 0x00, 0x00, 0x00,
  490. 0x00, 0x00, 0x00, 0x00, // reserved
  491. 0x00, 0x00, // layer
  492. 0x00, 0x00, // alternate_group
  493. 0x01, 0x00, // non-audio track volume
  494. 0x00, 0x00, // reserved
  495. 0x00, 0x01, 0x00, 0x00,
  496. 0x00, 0x00, 0x00, 0x00,
  497. 0x00, 0x00, 0x00, 0x00,
  498. 0x00, 0x00, 0x00, 0x00,
  499. 0x00, 0x01, 0x00, 0x00,
  500. 0x00, 0x00, 0x00, 0x00,
  501. 0x00, 0x00, 0x00, 0x00,
  502. 0x00, 0x00, 0x00, 0x00,
  503. 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
  504. (track.width & 0xFF00) >> 8,
  505. track.width & 0xFF,
  506. 0x00, 0x00, // width
  507. (track.height & 0xFF00) >> 8,
  508. track.height & 0xFF,
  509. 0x00, 0x00 // height
  510. ]);
  511. return box(types.tkhd, result);
  512. };
  513. /**
  514. * Generate a track fragment (traf) box. A traf box collects metadata
  515. * about tracks in a movie fragment (moof) box.
  516. */
  517. traf = function(track) {
  518. var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun,
  519. sampleDependencyTable, dataOffset,
  520. upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime;
  521. trackFragmentHeader = box(types.tfhd, new Uint8Array([
  522. 0x00, // version 0
  523. 0x00, 0x00, 0x3a, // flags
  524. (track.id & 0xFF000000) >> 24,
  525. (track.id & 0xFF0000) >> 16,
  526. (track.id & 0xFF00) >> 8,
  527. (track.id & 0xFF), // track_ID
  528. 0x00, 0x00, 0x00, 0x01, // sample_description_index
  529. 0x00, 0x00, 0x00, 0x00, // default_sample_duration
  530. 0x00, 0x00, 0x00, 0x00, // default_sample_size
  531. 0x00, 0x00, 0x00, 0x00 // default_sample_flags
  532. ]));
  533. upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (MAX_UINT32));
  534. lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (MAX_UINT32));
  535. trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([
  536. 0x01, // version 1
  537. 0x00, 0x00, 0x00, // flags
  538. // baseMediaDecodeTime
  539. (upperWordBaseMediaDecodeTime >>> 24) & 0xFF,
  540. (upperWordBaseMediaDecodeTime >>> 16) & 0xFF,
  541. (upperWordBaseMediaDecodeTime >>> 8) & 0xFF,
  542. upperWordBaseMediaDecodeTime & 0xFF,
  543. (lowerWordBaseMediaDecodeTime >>> 24) & 0xFF,
  544. (lowerWordBaseMediaDecodeTime >>> 16) & 0xFF,
  545. (lowerWordBaseMediaDecodeTime >>> 8) & 0xFF,
  546. lowerWordBaseMediaDecodeTime & 0xFF
  547. ]));
  548. // the data offset specifies the number of bytes from the start of
  549. // the containing moof to the first payload byte of the associated
  550. // mdat
  551. dataOffset = (32 + // tfhd
  552. 20 + // tfdt
  553. 8 + // traf header
  554. 16 + // mfhd
  555. 8 + // moof header
  556. 8); // mdat header
  557. // audio tracks require less metadata
  558. if (track.type === 'audio') {
  559. trackFragmentRun = trun(track, dataOffset);
  560. return box(types.traf,
  561. trackFragmentHeader,
  562. trackFragmentDecodeTime,
  563. trackFragmentRun);
  564. }
  565. // video tracks should contain an independent and disposable samples
  566. // box (sdtp)
  567. // generate one and adjust offsets to match
  568. sampleDependencyTable = sdtp(track);
  569. trackFragmentRun = trun(track,
  570. sampleDependencyTable.length + dataOffset);
  571. return box(types.traf,
  572. trackFragmentHeader,
  573. trackFragmentDecodeTime,
  574. trackFragmentRun,
  575. sampleDependencyTable);
  576. };
  577. /**
  578. * Generate a track box.
  579. * @param track {object} a track definition
  580. * @return {Uint8Array} the track box
  581. */
  582. trak = function(track) {
  583. track.duration = track.duration || 0xffffffff;
  584. return box(types.trak,
  585. tkhd(track),
  586. mdia(track));
  587. };
  588. trex = function(track) {
  589. var result = new Uint8Array([
  590. 0x00, // version 0
  591. 0x00, 0x00, 0x00, // flags
  592. (track.id & 0xFF000000) >> 24,
  593. (track.id & 0xFF0000) >> 16,
  594. (track.id & 0xFF00) >> 8,
  595. (track.id & 0xFF), // track_ID
  596. 0x00, 0x00, 0x00, 0x01, // default_sample_description_index
  597. 0x00, 0x00, 0x00, 0x00, // default_sample_duration
  598. 0x00, 0x00, 0x00, 0x00, // default_sample_size
  599. 0x00, 0x01, 0x00, 0x01 // default_sample_flags
  600. ]);
  601. // the last two bytes of default_sample_flags is the sample
  602. // degradation priority, a hint about the importance of this sample
  603. // relative to others. Lower the degradation priority for all sample
  604. // types other than video.
  605. if (track.type !== 'video') {
  606. result[result.length - 1] = 0x00;
  607. }
  608. return box(types.trex, result);
  609. };
  610. (function() {
  611. var audioTrun, videoTrun, trunHeader;
  612. // This method assumes all samples are uniform. That is, if a
  613. // duration is present for the first sample, it will be present for
  614. // all subsequent samples.
  615. // see ISO/IEC 14496-12:2012, Section 8.8.8.1
  616. trunHeader = function(samples, offset) {
  617. var durationPresent = 0, sizePresent = 0,
  618. flagsPresent = 0, compositionTimeOffset = 0;
  619. // trun flag constants
  620. if (samples.length) {
  621. if (samples[0].duration !== undefined) {
  622. durationPresent = 0x1;
  623. }
  624. if (samples[0].size !== undefined) {
  625. sizePresent = 0x2;
  626. }
  627. if (samples[0].flags !== undefined) {
  628. flagsPresent = 0x4;
  629. }
  630. if (samples[0].compositionTimeOffset !== undefined) {
  631. compositionTimeOffset = 0x8;
  632. }
  633. }
  634. return [
  635. 0x00, // version 0
  636. 0x00,
  637. durationPresent | sizePresent | flagsPresent | compositionTimeOffset,
  638. 0x01, // flags
  639. (samples.length & 0xFF000000) >>> 24,
  640. (samples.length & 0xFF0000) >>> 16,
  641. (samples.length & 0xFF00) >>> 8,
  642. samples.length & 0xFF, // sample_count
  643. (offset & 0xFF000000) >>> 24,
  644. (offset & 0xFF0000) >>> 16,
  645. (offset & 0xFF00) >>> 8,
  646. offset & 0xFF // data_offset
  647. ];
  648. };
  649. videoTrun = function(track, offset) {
  650. var bytesOffest, bytes, header, samples, sample, i;
  651. samples = track.samples || [];
  652. offset += 8 + 12 + (16 * samples.length);
  653. header = trunHeader(samples, offset);
  654. bytes = new Uint8Array(header.length + samples.length * 16);
  655. bytes.set(header);
  656. bytesOffest = header.length;
  657. for (i = 0; i < samples.length; i++) {
  658. sample = samples[i];
  659. bytes[bytesOffest++] = (sample.duration & 0xFF000000) >>> 24;
  660. bytes[bytesOffest++] = (sample.duration & 0xFF0000) >>> 16;
  661. bytes[bytesOffest++] = (sample.duration & 0xFF00) >>> 8;
  662. bytes[bytesOffest++] = sample.duration & 0xFF; // sample_duration
  663. bytes[bytesOffest++] = (sample.size & 0xFF000000) >>> 24;
  664. bytes[bytesOffest++] = (sample.size & 0xFF0000) >>> 16;
  665. bytes[bytesOffest++] = (sample.size & 0xFF00) >>> 8;
  666. bytes[bytesOffest++] = sample.size & 0xFF; // sample_size
  667. bytes[bytesOffest++] = (sample.flags.isLeading << 2) | sample.flags.dependsOn;
  668. bytes[bytesOffest++] = (sample.flags.isDependedOn << 6) |
  669. (sample.flags.hasRedundancy << 4) |
  670. (sample.flags.paddingValue << 1) |
  671. sample.flags.isNonSyncSample;
  672. bytes[bytesOffest++] = sample.flags.degradationPriority & 0xF0 << 8;
  673. bytes[bytesOffest++] = sample.flags.degradationPriority & 0x0F; // sample_flags
  674. bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF000000) >>> 24;
  675. bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF0000) >>> 16;
  676. bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF00) >>> 8;
  677. bytes[bytesOffest++] = sample.compositionTimeOffset & 0xFF; // sample_composition_time_offset
  678. }
  679. return box(types.trun, bytes);
  680. };
  681. audioTrun = function(track, offset) {
  682. var bytes, bytesOffest, header, samples, sample, i;
  683. samples = track.samples || [];
  684. offset += 8 + 12 + (8 * samples.length);
  685. header = trunHeader(samples, offset);
  686. bytes = new Uint8Array(header.length + samples.length * 8);
  687. bytes.set(header);
  688. bytesOffest = header.length;
  689. for (i = 0; i < samples.length; i++) {
  690. sample = samples[i];
  691. bytes[bytesOffest++] = (sample.duration & 0xFF000000) >>> 24;
  692. bytes[bytesOffest++] = (sample.duration & 0xFF0000) >>> 16;
  693. bytes[bytesOffest++] = (sample.duration & 0xFF00) >>> 8;
  694. bytes[bytesOffest++] = sample.duration & 0xFF; // sample_duration
  695. bytes[bytesOffest++] = (sample.size & 0xFF000000) >>> 24;
  696. bytes[bytesOffest++] = (sample.size & 0xFF0000) >>> 16;
  697. bytes[bytesOffest++] = (sample.size & 0xFF00) >>> 8;
  698. bytes[bytesOffest++] = sample.size & 0xFF; // sample_size
  699. }
  700. return box(types.trun, bytes);
  701. };
  702. trun = function(track, offset) {
  703. if (track.type === 'audio') {
  704. return audioTrun(track, offset);
  705. }
  706. return videoTrun(track, offset);
  707. };
  708. }());
  709. module.exports = {
  710. ftyp: ftyp,
  711. mdat: mdat,
  712. moof: moof,
  713. moov: moov,
  714. initSegment: function(tracks) {
  715. var
  716. fileType = ftyp(),
  717. movie = moov(tracks),
  718. result;
  719. result = new Uint8Array(fileType.byteLength + movie.byteLength);
  720. result.set(fileType);
  721. result.set(movie, fileType.byteLength);
  722. return result;
  723. }
  724. };