mp4-generator.js 22 KB

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