mp4-inspector.test.js 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173
  1. 'use strict';
  2. /*
  3. ======== A Handy Little QUnit Reference ========
  4. http://api.qunitjs.com/
  5. Test methods:
  6. module(name, {[setup][ ,teardown]})
  7. QUnit.test(name, callback)
  8. expect(numberOfAssertions)
  9. stop(increment)
  10. start(decrement)
  11. Test assertions:
  12. assert.ok(value, [message])
  13. assert.equal(actual, expected, [message])
  14. notEqual(actual, expected, [message])
  15. assert.deepEqual(actual, expected, [message])
  16. notDeepEqual(actual, expected, [message])
  17. assert.strictEqual(actual, expected, [message])
  18. notStrictEqual(actual, expected, [message])
  19. throws(block, [expected], [message])
  20. */
  21. var
  22. mp4 = require('../lib/mp4'),
  23. mp4Helpers = require('./utils/mp4-helpers'),
  24. QUnit = require('qunit'),
  25. window = require('global/window'),
  26. typeBytes = mp4Helpers.typeBytes,
  27. box = mp4Helpers.box,
  28. unityMatrix = mp4Helpers.unityMatrix,
  29. BigInt = window.BigInt || Number,
  30. mvhd0 = box('mvhd',
  31. 0x00, // version 0
  32. 0x00, 0x00, 0x00, // flags
  33. 0x00, 0x00, 0x00, 0x01, // creation_time
  34. 0x00, 0x00, 0x00, 0x02, // modification_time
  35. 0x00, 0x00, 0x00, 0x3c, // timescale
  36. 0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
  37. 0x00, 0x01, 0x00, 0x00, // 1.0 rate
  38. 0x01, 0x00, // 1.0 volume
  39. 0x00, 0x00, // reserved
  40. 0x00, 0x00, 0x00, 0x00, // reserved
  41. 0x00, 0x00, 0x00, 0x00, // reserved
  42. unityMatrix,
  43. 0x00, 0x00, 0x00, 0x00,
  44. 0x00, 0x00, 0x00, 0x00,
  45. 0x00, 0x00, 0x00, 0x00,
  46. 0x00, 0x00, 0x00, 0x00,
  47. 0x00, 0x00, 0x00, 0x00,
  48. 0x00, 0x00, 0x00, 0x00, // pre_defined
  49. 0x00, 0x00, 0x00, 0x02),
  50. tkhd0 = box('tkhd',
  51. 0x00, // version 0
  52. 0x00, 0x00, 0x00, // flags
  53. 0x00, 0x00, 0x00, 0x02, // creation_time
  54. 0x00, 0x00, 0x00, 0x03, // modification_time
  55. 0x00, 0x00, 0x00, 0x01, // track_ID
  56. 0x00, 0x00, 0x00, 0x00, // reserved
  57. 0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
  58. 0x00, 0x00, 0x00, 0x00,
  59. 0x00, 0x00, 0x00, 0x00, // reserved
  60. 0x00, 0x00, // layer
  61. 0x00, 0x00, // alternate_group
  62. 0x00, 0x00, // non-audio track volume
  63. 0x00, 0x00, // reserved
  64. unityMatrix,
  65. 0x01, 0x2c, 0x80, 0x00, // 300.5 in 16.16 fixed point
  66. 0x00, 0x96, 0x80, 0x00), // 150.5 in 16.16 fixed point
  67. mdhd0 = box('mdhd',
  68. 0x00, // version 0
  69. 0x00, 0x00, 0x00, // flags
  70. 0x00, 0x00, 0x00, 0x02, // creation_time
  71. 0x00, 0x00, 0x00, 0x03, // modification_time
  72. 0x00, 0x00, 0x00, 0x3c, // timescale
  73. 0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
  74. 0x15, 0xc7, // 'eng' language
  75. 0x00, 0x00);
  76. QUnit.module('MP4 Inspector');
  77. QUnit.test('produces an empty array for empty input', function(assert) {
  78. assert.strictEqual(mp4.tools.inspect(new Uint8Array([])).length, 0, 'returned an empty array');
  79. });
  80. QUnit.test('can parse a Box', function(assert) {
  81. var box = new Uint8Array([
  82. 0x00, 0x00, 0x00, 0x00, // size 0
  83. 0x00, 0x00, 0x00, 0x00 // boxtype 0
  84. ]);
  85. assert.deepEqual(mp4.tools.inspect(box), [{
  86. type: '\u0000\u0000\u0000\u0000',
  87. size: 0,
  88. data: box.subarray(box.byteLength)
  89. }], 'parsed a Box');
  90. });
  91. QUnit.test('can parse an ftyp', function(assert) {
  92. assert.deepEqual(mp4.tools.inspect(new Uint8Array(box('ftyp',
  93. 0x61, 0x76, 0x63, 0x31, // major brand
  94. 0x00, 0x00, 0x00, 0x02, // minor version
  95. 98, 111, 111, 112, // compatible brands
  96. 98, 101, 101, 112 // compatible brands
  97. ))), [{
  98. type: 'ftyp',
  99. size: 4 * 6,
  100. majorBrand: 'avc1',
  101. minorVersion: 2,
  102. compatibleBrands: ['boop', 'beep']
  103. }], 'parsed an ftyp');
  104. });
  105. QUnit.test('can parse a pdin', function(assert) {
  106. assert.deepEqual(mp4.tools.inspect(new Uint8Array(box('pdin',
  107. 0x01, // version 1
  108. 0x01, 0x02, 0x03, // flags
  109. 0x00, 0x00, 0x04, 0x00, // 1024 = 0x400 bytes/second rate
  110. 0x00, 0x00, 0x00, 0x01 // initial delay
  111. ))), [{
  112. size: 20,
  113. type: 'pdin',
  114. version: 1,
  115. flags: new Uint8Array([1, 2, 3]),
  116. rate: 1024,
  117. initialDelay: 1
  118. }], 'parsed a pdin');
  119. });
  120. QUnit.test('can parse an mdat', function(assert) {
  121. var mdat = new Uint8Array(box('mdat',
  122. 0, 0, 0, 4, // length
  123. 0x01, 0x02, 0x03, 0x04 // data
  124. ));
  125. assert.deepEqual(mp4.tools.inspect(mdat), [{
  126. size: 16,
  127. type: 'mdat',
  128. nals: [
  129. 'slice_layer_without_partitioning_rbsp'
  130. ],
  131. byteLength: 8
  132. }], 'parsed an mdat');
  133. });
  134. QUnit.test('can parse a free or skip', function(assert) {
  135. var
  136. free = new Uint8Array(box('free',
  137. 0x01, 0x02, 0x03, 0x04)), // data
  138. skip = new Uint8Array(box('skip',
  139. 0x01, 0x02, 0x03, 0x04)); // data
  140. assert.deepEqual(mp4.tools.inspect(free), [{
  141. size: 12,
  142. type: 'free',
  143. data: free.subarray(free.byteLength - 4)
  144. }], 'parsed a free');
  145. assert.deepEqual(mp4.tools.inspect(skip), [{
  146. size: 12,
  147. type: 'skip',
  148. data: skip.subarray(skip.byteLength - 4)
  149. }], 'parsed a skip');
  150. });
  151. QUnit.test('can parse a version 0 mvhd', function(assert) {
  152. assert.deepEqual(mp4.tools.inspect(new Uint8Array(mvhd0)), [{
  153. type: 'mvhd',
  154. version: 0,
  155. flags: new Uint8Array([0, 0, 0]),
  156. creationTime: new Date(1000 - 2082844800000),
  157. modificationTime: new Date(2000 - 2082844800000),
  158. timescale: 60,
  159. duration: 600,
  160. rate: 1,
  161. volume: 1,
  162. matrix: new Uint32Array(unityMatrix),
  163. size: 108,
  164. nextTrackId: 2
  165. }]);
  166. });
  167. QUnit.test('can parse a version 0 tkhd', function(assert) {
  168. assert.deepEqual(mp4.tools.inspect(new Uint8Array(tkhd0)), [{
  169. type: 'tkhd',
  170. version: 0,
  171. flags: new Uint8Array([0, 0, 0]),
  172. creationTime: new Date(2000 - 2082844800000),
  173. modificationTime: new Date(3000 - 2082844800000),
  174. size: 92,
  175. trackId: 1,
  176. duration: 600,
  177. layer: 0,
  178. alternateGroup: 0,
  179. volume: 0,
  180. matrix: new Uint32Array(unityMatrix),
  181. width: 300.5,
  182. height: 150.5
  183. }]);
  184. });
  185. QUnit.test('can parse a version 0 mdhd', function(assert) {
  186. assert.deepEqual(mp4.tools.inspect(new Uint8Array(mdhd0)), [{
  187. type: 'mdhd',
  188. version: 0,
  189. flags: new Uint8Array([0, 0, 0]),
  190. creationTime: new Date(2000 - 2082844800000),
  191. modificationTime: new Date(3000 - 2082844800000),
  192. size: 32,
  193. timescale: 60,
  194. duration: 600,
  195. language: 'eng'
  196. }]);
  197. });
  198. QUnit.test('can parse a moov', function(assert) {
  199. var data = mp4Helpers.sampleMoov;
  200. QUnit.dump.maxDepth = 100;
  201. var result = QUnit.dump.parse(mp4.tools.inspect(new Uint8Array(data)));
  202. var expected = QUnit.dump.parse([{
  203. type: 'moov',
  204. size: 1129,
  205. boxes: [{
  206. type: 'mvhd',
  207. version: 1,
  208. flags: new Uint8Array([0, 0, 0]),
  209. creationTime: new Date(1000 - 2082844800000),
  210. modificationTime: new Date(2000 - 2082844800000),
  211. timescale: 1000,
  212. duration: 600,
  213. rate: 1,
  214. size: 120,
  215. volume: 1,
  216. matrix: new Uint32Array(unityMatrix),
  217. nextTrackId: 2
  218. }, {
  219. type: 'trak',
  220. size: 519,
  221. boxes: [{
  222. type: 'tkhd',
  223. flags: new Uint8Array([0, 0, 0]),
  224. version: 1,
  225. creationTime: new Date(2000 - 2082844800000),
  226. modificationTime: new Date(3000 - 2082844800000),
  227. size: 104,
  228. trackId: 1,
  229. duration: 600,
  230. layer: 0,
  231. alternateGroup: 0,
  232. volume: 0,
  233. matrix: new Uint32Array(unityMatrix),
  234. width: 300,
  235. height: 150
  236. }, {
  237. type: 'edts',
  238. size: 36,
  239. boxes: [{
  240. type: 'elst',
  241. size: 28,
  242. version: 0,
  243. flags: new Uint8Array([0, 0, 0]),
  244. edits: [{
  245. segmentDuration: 0,
  246. mediaTime: 1024,
  247. mediaRate: 1.5
  248. }]
  249. }]
  250. }, {
  251. type: 'mdia',
  252. size: 371,
  253. boxes: [{
  254. type: 'mdhd',
  255. version: 1,
  256. flags: new Uint8Array([0, 0, 0]),
  257. creationTime: new Date(2000 - 2082844800000),
  258. modificationTime: new Date(3000 - 2082844800000),
  259. timescale: 90e3,
  260. duration: 600,
  261. language: 'eng',
  262. size: 44
  263. }, {
  264. type: 'hdlr',
  265. version: 1,
  266. flags: new Uint8Array([0, 0, 0]),
  267. handlerType: 'vide',
  268. name: 'one',
  269. size: 37
  270. }, {
  271. type: 'minf',
  272. size: 282,
  273. boxes: [{
  274. type: 'dinf',
  275. size: 36,
  276. boxes: [{
  277. type: 'dref',
  278. size: 28,
  279. version: 1,
  280. flags: new Uint8Array([0, 0, 0]),
  281. dataReferences: [{
  282. type: 'url ',
  283. size: 12,
  284. version: 0,
  285. flags: new Uint8Array([0, 0, 1])
  286. }]
  287. }]
  288. }, {
  289. type: 'stbl',
  290. size: 238,
  291. boxes: [{
  292. type: 'stsd',
  293. size: 114,
  294. version: 1,
  295. flags: new Uint8Array([0, 0, 0]),
  296. sampleDescriptions: [{
  297. config: [
  298. {
  299. avcLevelIndication: 13,
  300. avcProfileIndication: 77,
  301. configurationVersion: 0,
  302. lengthSizeMinusOne: 0,
  303. pps: [],
  304. profileCompatibility: 64,
  305. size: 0,
  306. sps: [],
  307. type: 'avcC'
  308. }
  309. ],
  310. dataReferenceIndex: 0,
  311. depth: 0,
  312. frameCount: 0,
  313. height: 0,
  314. horizresolution: 0,
  315. size: 98,
  316. type: 'avc1',
  317. vertresolution: 0,
  318. width: 0
  319. }
  320. ]
  321. }, {
  322. type: 'stts',
  323. size: 24,
  324. version: 1,
  325. flags: new Uint8Array([0, 0, 0]),
  326. timeToSamples: [{
  327. sampleCount: 1,
  328. sampleDelta: 1
  329. }]
  330. }, {
  331. type: 'stsc',
  332. version: 1,
  333. flags: new Uint8Array([0, 0, 0]),
  334. sampleToChunks: [{
  335. firstChunk: 2,
  336. samplesPerChunk: 3,
  337. sampleDescriptionIndex: 1
  338. }],
  339. size: 28
  340. }, {
  341. type: 'stco',
  342. size: 20,
  343. version: 1,
  344. flags: new Uint8Array([0, 0, 0]),
  345. chunkOffsets: [1]
  346. }, {
  347. type: 'stss',
  348. size: 20,
  349. version: 0,
  350. flags: new Uint8Array([0, 0, 0]),
  351. syncSamples: [1]
  352. }, {
  353. type: 'ctts',
  354. size: 24,
  355. version: 0,
  356. flags: new Uint8Array([0, 0, 0]),
  357. compositionOffsets: [
  358. { sampleCount: 1, sampleOffset: 1 }
  359. ]
  360. }]
  361. }]
  362. }]
  363. }]
  364. }, {
  365. type: 'trak',
  366. size: 482,
  367. boxes: [{
  368. type: 'tkhd',
  369. flags: new Uint8Array([0, 0, 0]),
  370. version: 1,
  371. creationTime: new Date(2000 - 2082844800000),
  372. modificationTime: new Date(3000 - 2082844800000),
  373. size: 104,
  374. trackId: 2,
  375. duration: 600,
  376. layer: 0,
  377. alternateGroup: 0,
  378. volume: 0,
  379. matrix: new Uint32Array(unityMatrix),
  380. width: 300,
  381. height: 150
  382. }, {
  383. type: 'edts',
  384. size: 44,
  385. boxes: [{
  386. type: 'elst',
  387. size: 36,
  388. version: 1,
  389. flags: new Uint8Array([0, 0, 0]),
  390. edits: [{
  391. segmentDuration: 0,
  392. mediaTime: BigInt('0x1000000000000000'),
  393. mediaRate: 1.5
  394. }]
  395. }]
  396. }, {
  397. type: 'mdia',
  398. size: 326,
  399. boxes: [{
  400. type: 'mdhd',
  401. version: 1,
  402. flags: new Uint8Array([0, 0, 0]),
  403. creationTime: new Date(2000 - 2082844800000),
  404. modificationTime: new Date(3000 - 2082844800000),
  405. timescale: 90e3,
  406. duration: 600,
  407. language: 'eng',
  408. size: 44
  409. }, {
  410. type: 'hdlr',
  411. version: 1,
  412. flags: new Uint8Array([0, 0, 0]),
  413. handlerType: 'soun',
  414. name: 'one',
  415. size: 37
  416. }, {
  417. type: 'minf',
  418. size: 237,
  419. boxes: [{
  420. type: 'dinf',
  421. size: 36,
  422. boxes: [{
  423. type: 'dref',
  424. size: 28,
  425. version: 1,
  426. flags: new Uint8Array([0, 0, 0]),
  427. dataReferences: [{
  428. type: 'url ',
  429. size: 12,
  430. version: 0,
  431. flags: new Uint8Array([0, 0, 1])
  432. }]
  433. }]
  434. }, {
  435. type: 'stbl',
  436. size: 193,
  437. boxes: [{
  438. type: 'stsd',
  439. size: 89,
  440. version: 1,
  441. flags: new Uint8Array([0, 0, 0]),
  442. sampleDescriptions: [{
  443. channelcount: 0,
  444. dataReferenceIndex: 0,
  445. samplerate: 0,
  446. samplesize: 0,
  447. size: 73,
  448. streamDescriptor: {
  449. decoderConfig: {
  450. avgBitrate: 0,
  451. bufferSize: 0,
  452. decoderConfigDescriptor: {
  453. audioObjectType: 0,
  454. channelConfiguration: 0,
  455. length: 0,
  456. samplingFrequencyIndex: 0,
  457. tag: 0
  458. },
  459. maxBitrate: 0,
  460. objectProfileIndication: 64,
  461. streamType: 2
  462. },
  463. esId: 0,
  464. flags: {
  465. 0: 0,
  466. 1: 0,
  467. 2: 0
  468. },
  469. size: 0,
  470. streamPriority: 0,
  471. type: 'esds',
  472. version: 0
  473. },
  474. type: 'mp4a'
  475. }]
  476. }, {
  477. type: 'stts',
  478. size: 24,
  479. version: 1,
  480. flags: new Uint8Array([0, 0, 0]),
  481. timeToSamples: [{
  482. sampleCount: 1,
  483. sampleDelta: 1
  484. }]
  485. }, {
  486. type: 'stsc',
  487. version: 1,
  488. flags: new Uint8Array([0, 0, 0]),
  489. sampleToChunks: [{
  490. firstChunk: 2,
  491. samplesPerChunk: 3,
  492. sampleDescriptionIndex: 1
  493. }],
  494. size: 28
  495. }, {
  496. type: 'ctts',
  497. size: 24,
  498. version: 1,
  499. flags: new Uint8Array([0, 0, 0]),
  500. compositionOffsets: [
  501. { sampleCount: 1, sampleOffset: -1 }
  502. ]
  503. }, {
  504. type: 'stco',
  505. size: 20,
  506. version: 1,
  507. flags: new Uint8Array([0, 0, 0]),
  508. chunkOffsets: [1]
  509. }]
  510. }]
  511. }]
  512. }]
  513. }]
  514. }]);
  515. assert.equal(result, expected, 'can parse moov');
  516. });
  517. QUnit.test('can parse an mvex', function(assert) {
  518. var mvex =
  519. box('mvex',
  520. box('trex',
  521. 0x00, // version
  522. 0x00, 0x00, 0x00, // flags
  523. 0x00, 0x00, 0x00, 0x01, // track_ID
  524. 0x00, 0x00, 0x00, 0x01, // default_sample_description_index
  525. 0x00, 0x00, 0x00, 0x02, // default_sample_duration
  526. 0x00, 0x00, 0x00, 0x03, // default_sample_size
  527. 0x00, 0x61, 0x00, 0x01)); // default_sample_flags
  528. assert.deepEqual(mp4.tools.inspect(new Uint8Array(mvex)), [{
  529. type: 'mvex',
  530. size: 40,
  531. boxes: [{
  532. type: 'trex',
  533. size: 32,
  534. version: 0,
  535. flags: new Uint8Array([0, 0, 0]),
  536. trackId: 1,
  537. defaultSampleDescriptionIndex: 1,
  538. defaultSampleDuration: 2,
  539. defaultSampleSize: 3,
  540. sampleDependsOn: 0,
  541. sampleIsDependedOn: 1,
  542. sampleHasRedundancy: 2,
  543. samplePaddingValue: 0,
  544. sampleIsDifferenceSample: true,
  545. sampleDegradationPriority: 1
  546. }]
  547. }], 'parsed an mvex');
  548. });
  549. QUnit.test('can parse a video stsd', function(assert) {
  550. var data = box('stsd',
  551. 0x00, // version 0
  552. 0x00, 0x00, 0x00, // flags
  553. 0x00, 0x00, 0x00, 0x01,
  554. box('avc1',
  555. 0x00, 0x00, 0x00,
  556. 0x00, 0x00, 0x00, // reserved
  557. 0x00, 0x01, // data_reference_index
  558. 0x00, 0x00, // pre_defined
  559. 0x00, 0x00, // reserved
  560. 0x00, 0x00, 0x00, 0x00,
  561. 0x00, 0x00, 0x00, 0x00,
  562. 0x00, 0x00, 0x00, 0x00, // pre_defined
  563. 0x01, 0x2c, // width = 300
  564. 0x00, 0x96, // height = 150
  565. 0x00, 0x48, 0x00, 0x00, // horizresolution
  566. 0x00, 0x48, 0x00, 0x00, // vertresolution
  567. 0x00, 0x00, 0x00, 0x00, // reserved
  568. 0x00, 0x01, // frame_count
  569. 0x04,
  570. typeBytes('avc1'),
  571. 0x00, 0x00, 0x00, 0x00,
  572. 0x00, 0x00, 0x00, 0x00,
  573. 0x00, 0x00, 0x00, 0x00,
  574. 0x00, 0x00, 0x00, 0x00,
  575. 0x00, 0x00, 0x00, 0x00,
  576. 0x00, 0x00, 0x00, 0x00,
  577. 0x00, 0x00, 0x00, // compressorname
  578. 0x00, 0x18, // depth = 24
  579. 0x11, 0x11, // pre_defined
  580. box('avcC',
  581. 0x01, // configurationVersion
  582. 0x00, // AVCProfileIndication??
  583. 0x00, // profile_compatibility
  584. 0x00, // AVCLevelIndication
  585. 0x1c, // lengthSizeMinusOne
  586. 0xe1, // numOfSequenceParameterSets
  587. 0x00, 0x01, // sequenceParameterSetLength
  588. 0x00, // "SPS"
  589. 0x02, // numOfPictureParameterSets
  590. 0x00, 0x02, // pictureParameterSetLength
  591. 0x01, 0x02, // "PPS"
  592. 0x00, 0x01, // pictureParameterSetLength
  593. 0xff), // "PPS"
  594. box('btrt',
  595. 0x00, 0x00, 0x00, 0x00, // bufferSizeDB
  596. 0x00, 0x00, 0x00, 0x01, // maxBitrate
  597. 0x00, 0x00, 0x00, 0x01), // avgBitrate
  598. box('pasp',
  599. 0x00, 0x00, 0x00, 0x01, // hSpacing
  600. 0x00, 0x00, 0x00, 0x01))); // vSpacing
  601. assert.deepEqual(mp4.tools.inspect(new Uint8Array(data)), [{
  602. type: 'stsd',
  603. size: 163,
  604. version: 0,
  605. flags: new Uint8Array([0, 0, 0]),
  606. sampleDescriptions: [{
  607. type: 'avc1',
  608. size: 147,
  609. dataReferenceIndex: 1,
  610. width: 300,
  611. height: 150,
  612. horizresolution: 72,
  613. vertresolution: 72,
  614. frameCount: 1,
  615. depth: 24,
  616. config: [{
  617. type: 'avcC',
  618. size: 25,
  619. configurationVersion: 1,
  620. avcProfileIndication: 0,
  621. profileCompatibility: 0,
  622. avcLevelIndication: 0,
  623. lengthSizeMinusOne: 0,
  624. sps: [new Uint8Array(1)],
  625. pps: [new Uint8Array([1, 2]),
  626. new Uint8Array([0xff])]
  627. }, {
  628. type: 'btrt',
  629. size: 20,
  630. bufferSizeDB: 0,
  631. maxBitrate: 1,
  632. avgBitrate: 1
  633. }, {
  634. type: 'pasp',
  635. size: 16,
  636. data: new Uint8Array([0, 0, 0, 1, 0, 0, 0, 1])
  637. }]
  638. }]
  639. }]);
  640. });
  641. QUnit.test('can parse an audio stsd', function(assert) {
  642. var data = box('stsd',
  643. 0x00, // version 0
  644. 0x00, 0x00, 0x00, // flags
  645. 0x00, 0x00, 0x00, 0x01, // entry_count
  646. box('mp4a',
  647. 0x00, 0x00, 0x00,
  648. 0x00, 0x00, 0x00, // reserved
  649. 0x00, 0x01, // data_reference_index
  650. 0x00, 0x00, 0x00, 0x00,
  651. 0x00, 0x00, 0x00, 0x00, // reserved
  652. 0x00, 0x02, // channelcount
  653. 0x00, 0x10, // samplesize
  654. 0x00, 0x00, // pre_defined
  655. 0x00, 0x00, // reserved
  656. 0xbb, 0x80, 0x00, 0x00, // samplerate, fixed-point 16.16
  657. box('esds',
  658. 0x00, // version 0
  659. 0x00, 0x00, 0x00, // flags
  660. 0x03, // tag, ES_DescrTag
  661. 0x00, // length
  662. 0x00, 0x01, // ES_ID
  663. 0x00, // streamDependenceFlag, URL_Flag, reserved, streamPriority
  664. // DecoderConfigDescriptor
  665. 0x04, // tag, DecoderConfigDescrTag
  666. 0x0d, // length
  667. 0x40, // objectProfileIndication, AAC Main
  668. 0x15, // streamType, AudioStream. upstream, reserved
  669. 0x00, 0x00, 0xff, // bufferSizeDB
  670. 0x00, 0x00, 0x00, 0xff, // maxBitrate
  671. 0x00, 0x00, 0x00, 0xaa, // avgBitrate
  672. // DecoderSpecificInfo
  673. 0x05, // tag, DecoderSpecificInfoTag
  674. 0x02, // length
  675. // audioObjectType, samplingFrequencyIndex, channelConfiguration
  676. 0x11, 0x90,
  677. // GASpecificConfig
  678. 0x06, 0x01, 0x02)));
  679. assert.deepEqual(mp4.tools.inspect(new Uint8Array(data)), [{
  680. version: 0,
  681. flags: new Uint8Array([0, 0, 0]),
  682. type: 'stsd',
  683. size: 91,
  684. sampleDescriptions: [{
  685. type: 'mp4a',
  686. dataReferenceIndex: 1,
  687. channelcount: 2,
  688. samplesize: 16,
  689. samplerate: 48000,
  690. size: 75,
  691. streamDescriptor: {
  692. type: 'esds',
  693. version: 0,
  694. size: 39,
  695. flags: new Uint8Array([0, 0, 0]),
  696. esId: 1,
  697. streamPriority: 0,
  698. decoderConfig: {
  699. objectProfileIndication: 0x40,
  700. streamType: 0x05,
  701. bufferSize: 0xff,
  702. maxBitrate: 0xff,
  703. avgBitrate: 0xaa,
  704. decoderConfigDescriptor: {
  705. tag: 5,
  706. length: 2,
  707. audioObjectType: 2,
  708. samplingFrequencyIndex: 3,
  709. channelConfiguration: 2
  710. }
  711. }
  712. }
  713. }]
  714. }], 'parsed an audio stsd');
  715. });
  716. QUnit.test('can parse an styp', function(assert) {
  717. assert.deepEqual(mp4.tools.inspect(new Uint8Array(box('styp',
  718. 0x61, 0x76, 0x63, 0x31, // major brand
  719. 0x00, 0x00, 0x00, 0x02, // minor version
  720. 98, 111, 111, 112 // compatible brands
  721. ))), [{
  722. type: 'styp',
  723. size: 4 * 5,
  724. majorBrand: 'avc1',
  725. minorVersion: 2,
  726. compatibleBrands: ['boop']
  727. }], 'parsed an styp');
  728. });
  729. QUnit.test('can parse a vmhd', function(assert) {
  730. var data = box('vmhd',
  731. 0x00, // version
  732. 0x00, 0x00, 0x00, // flags
  733. 0x00, 0x00, // graphicsmode
  734. 0x00, 0x00,
  735. 0x00, 0x00,
  736. 0x00, 0x00); // opcolor
  737. assert.deepEqual(mp4.tools.inspect(new Uint8Array(data)),
  738. [{
  739. type: 'vmhd',
  740. size: 20,
  741. version: 0,
  742. flags: new Uint8Array([0, 0, 0]),
  743. graphicsmode: 0,
  744. opcolor: new Uint16Array([0, 0, 0])
  745. }]);
  746. });
  747. QUnit.test('can parse an stsz', function(assert) {
  748. var data = box('stsz',
  749. 0x00, // version
  750. 0x00, 0x00, 0x00, // flags
  751. 0x00, 0x00, 0x00, 0x00, // sample_size
  752. 0x00, 0x00, 0x00, 0x03, // sample_count
  753. 0x00, 0x00, 0x00, 0x01, // entry_size
  754. 0x00, 0x00, 0x00, 0x02, // entry_size
  755. 0x00, 0x00, 0x00, 0x03); // entry_size
  756. assert.deepEqual(mp4.tools.inspect(new Uint8Array(data)),
  757. [{
  758. type: 'stsz',
  759. size: 32,
  760. version: 0,
  761. flags: new Uint8Array([0, 0, 0]),
  762. sampleSize: 0,
  763. entries: [1, 2, 3]
  764. }]);
  765. });
  766. QUnit.test('can parse a moof', function(assert) {
  767. var data = box('moof',
  768. box('mfhd',
  769. 0x00, // version
  770. 0x00, 0x00, 0x00, // flags
  771. 0x00, 0x00, 0x00, 0x04), // sequence_number
  772. box('traf',
  773. box('tfhd',
  774. 0x00, // version
  775. 0x00, 0x00, 0x3b, // flags
  776. 0x00, 0x00, 0x00, 0x01, // track_ID
  777. 0x00, 0x00, 0x00, 0x00,
  778. 0x00, 0x00, 0x00, 0x01, // base_data_offset
  779. 0x00, 0x00, 0x00, 0x02, // sample_description_index
  780. 0x00, 0x00, 0x00, 0x03, // default_sample_duration,
  781. 0x00, 0x00, 0x00, 0x04, // default_sample_size
  782. 0x00, 0x00, 0x00, 0x05))); // default_sample_flags
  783. assert.deepEqual(mp4.tools.inspect(new Uint8Array(data)),
  784. [{
  785. type: 'moof',
  786. size: 72,
  787. boxes: [{
  788. type: 'mfhd',
  789. size: 16,
  790. version: 0,
  791. flags: new Uint8Array([0, 0, 0]),
  792. sequenceNumber: 4
  793. },
  794. {
  795. type: 'traf',
  796. size: 48,
  797. boxes: [{
  798. type: 'tfhd',
  799. version: 0,
  800. size: 40,
  801. flags: new Uint8Array([0x00, 0, 0x3b]),
  802. trackId: 1,
  803. baseDataOffset: 1,
  804. sampleDescriptionIndex: 2,
  805. defaultSampleDuration: 3,
  806. defaultSampleSize: 4,
  807. defaultSampleFlags: 5
  808. }]
  809. }]
  810. }]);
  811. });
  812. QUnit.test('can parse a trun', function(assert) {
  813. var data = box('trun',
  814. 0x00, // version
  815. 0x00, 0x0b, 0x05, // flags
  816. 0x00, 0x00, 0x00, 0x02, // sample_count
  817. 0x00, 0x00, 0x00, 0x01, // data_offset
  818. // first_sample_flags
  819. // r:0000 il:10 sdo:01 sido:10 shr:01 spv:111 snss:1
  820. // dp:1111 1110 1101 1100
  821. 0x09, 0x9f, 0xfe, 0xdc,
  822. 0x00, 0x00, 0x00, 0x09, // sample_duration
  823. 0x00, 0x00, 0x00, 0xff, // sample_size
  824. 0x00, 0x00, 0x00, 0x00, // sample_composition_time_offset
  825. 0x00, 0x00, 0x00, 0x08, // sample_duration
  826. 0x00, 0x00, 0x00, 0xfe, // sample_size
  827. 0x00, 0x00, 0x00, 0x00); // sample_composition_time_offset
  828. assert.deepEqual(mp4.tools.inspect(new Uint8Array(data)),
  829. [{
  830. type: 'trun',
  831. version: 0,
  832. size: 48,
  833. flags: new Uint8Array([0, 0x0b, 0x05]),
  834. dataOffset: 1,
  835. samples: [{
  836. duration: 9,
  837. size: 0xff,
  838. flags: {
  839. isLeading: 2,
  840. dependsOn: 1,
  841. isDependedOn: 2,
  842. hasRedundancy: 1,
  843. paddingValue: 7,
  844. isNonSyncSample: 1,
  845. degradationPriority: 0xfedc
  846. },
  847. compositionTimeOffset: 0
  848. }, {
  849. duration: 8,
  850. size: 0xfe,
  851. compositionTimeOffset: 0
  852. }]
  853. }]);
  854. });
  855. QUnit.test('can parse a trun with per-sample flags', function(assert) {
  856. var data = box('trun',
  857. 0x00, // version
  858. 0x00, 0x0f, 0x00, // flags
  859. 0x00, 0x00, 0x00, 0x01, // sample_count
  860. 0x00, 0x00, 0x00, 0x09, // sample_duration
  861. 0x00, 0x00, 0x00, 0xff, // sample_size
  862. // sample_flags
  863. // r:0000 il:00 sdo:01, sido:11 shr:00 spv:010 snss:0
  864. // dp: 0001 0010 0011 0100
  865. 0x01, 0xc4, 0x12, 0x34,
  866. 0x00, 0x00, 0x00, 0x00); // sample_composition_time_offset
  867. assert.deepEqual(mp4.tools.inspect(new Uint8Array(data)),
  868. [{
  869. type: 'trun',
  870. version: 0,
  871. size: 32,
  872. flags: new Uint8Array([0, 0x0f, 0x00]),
  873. samples: [{
  874. duration: 9,
  875. size: 0xff,
  876. flags: {
  877. isLeading: 0,
  878. dependsOn: 1,
  879. isDependedOn: 3,
  880. hasRedundancy: 0,
  881. paddingValue: 2,
  882. isNonSyncSample: 0,
  883. degradationPriority: 0x1234
  884. },
  885. compositionTimeOffset: 0
  886. }]
  887. }]);
  888. });
  889. QUnit.test('can parse an sdtp', function(assert) {
  890. var data = box('sdtp',
  891. 0x00, // version
  892. 0x00, 0x00, 0x00, // flags
  893. // reserved + sample_depends_on +
  894. // sample_is_dependend_on + sample_has_redundancy
  895. 0x15,
  896. // reserved + sample_depends_on +
  897. // sample_is_dependend_on + sample_has_redundancy
  898. 0x27);
  899. assert.deepEqual(mp4.tools.inspect(new Uint8Array(data)), [{
  900. type: 'sdtp',
  901. version: 0,
  902. flags: new Uint8Array([0, 0, 0]),
  903. size: 14,
  904. samples: [{
  905. dependsOn: 1,
  906. isDependedOn: 1,
  907. hasRedundancy: 1
  908. }, {
  909. dependsOn: 2,
  910. isDependedOn: 1,
  911. hasRedundancy: 3
  912. }]
  913. }]);
  914. });
  915. QUnit.test('can parse a sidx', function(assert) {
  916. var data = box('sidx',
  917. 0x00, // version
  918. 0x00, 0x00, 0x00, // flags
  919. 0x00, 0x00, 0x00, 0x02, // reference_ID
  920. 0x00, 0x00, 0x00, 0x01, // timescale
  921. 0x01, 0x02, 0x03, 0x04, // earliest_presentation_time
  922. 0x00, 0x00, 0x00, 0x00, // first_offset
  923. 0x00, 0x00, // reserved
  924. 0x00, 0x02, // reference_count
  925. // first reference
  926. 0x80, 0x00, 0x00, 0x07, // reference_type(1) + referenced_size(31)
  927. 0x00, 0x00, 0x00, 0x08, // subsegment_duration
  928. 0x80, 0x00, 0x00, 0x09, // starts_with_SAP(1) + SAP_type(3) + SAP_delta_time(28)
  929. // second reference
  930. 0x00, 0x00, 0x00, 0x03, // reference_type(1) + referenced_size(31)
  931. 0x00, 0x00, 0x00, 0x04, // subsegment_duration
  932. 0x10, 0x00, 0x00, 0x05 // starts_with_SAP(1) + SAP_type(3) + SAP_delta_time(28)
  933. );
  934. assert.deepEqual(mp4.tools.inspect(new Uint8Array(data)),
  935. [{
  936. type: 'sidx',
  937. version: 0,
  938. flags: new Uint8Array([0, 0x00, 0x00]),
  939. timescale: 1,
  940. earliestPresentationTime: 0x01020304,
  941. firstOffset: 0,
  942. referenceId: 2,
  943. size: 56,
  944. references: [{
  945. referenceType: 1,
  946. referencedSize: 7,
  947. subsegmentDuration: 8,
  948. startsWithSap: true,
  949. sapType: 0,
  950. sapDeltaTime: 9
  951. }, {
  952. referenceType: 0,
  953. referencedSize: 3,
  954. subsegmentDuration: 4,
  955. startsWithSap: false,
  956. sapType: 1,
  957. sapDeltaTime: 5
  958. }]
  959. }]);
  960. });
  961. QUnit.test('can parse a version 1 sidx', function(assert) {
  962. var data = box('sidx',
  963. 0x01, // version
  964. 0x00, 0x00, 0x00, // flags
  965. 0x00, 0x00, 0x00, 0x02, // reference_ID
  966. 0x00, 0x00, 0x00, 0x01, // timescale
  967. 0x00, 0x00, 0x00, 0x00, // earliest_presentation_time
  968. 0x01, 0x02, 0x03, 0x04,
  969. 0x00, 0x00, 0x00, 0x00, // first_offset
  970. 0x00, 0x00, 0x00, 0x00,
  971. 0x00, 0x00, // reserved
  972. 0x00, 0x02, // reference_count
  973. // first reference
  974. 0x80, 0x00, 0x00, 0x07, // reference_type(1) + referenced_size(31)
  975. 0x00, 0x00, 0x00, 0x08, // subsegment_duration
  976. 0x80, 0x00, 0x00, 0x09, // starts_with_SAP(1) + SAP_type(3) + SAP_delta_time(28)
  977. // second reference
  978. 0x00, 0x00, 0x00, 0x03, // reference_type(1) + referenced_size(31)
  979. 0x00, 0x00, 0x00, 0x04, // subsegment_duration
  980. 0x10, 0x00, 0x00, 0x05 // starts_with_SAP(1) + SAP_type(3) + SAP_delta_time(28)
  981. );
  982. assert.deepEqual(mp4.tools.inspect(new Uint8Array(data)),
  983. [{
  984. type: 'sidx',
  985. version: 1,
  986. flags: new Uint8Array([0, 0x00, 0x00]),
  987. timescale: 1,
  988. earliestPresentationTime: 0x01020304,
  989. firstOffset: 0,
  990. referenceId: 2,
  991. size: 64,
  992. references: [{
  993. referenceType: 1,
  994. referencedSize: 7,
  995. subsegmentDuration: 8,
  996. startsWithSap: true,
  997. sapType: 0,
  998. sapDeltaTime: 9
  999. }, {
  1000. referenceType: 0,
  1001. referencedSize: 3,
  1002. subsegmentDuration: 4,
  1003. startsWithSap: false,
  1004. sapType: 1,
  1005. sapDeltaTime: 5
  1006. }]
  1007. }]);
  1008. });
  1009. QUnit.test('can parse a big version 1 sidx', function(assert) {
  1010. var data = box('sidx',
  1011. 0x01, // version
  1012. 0x00, 0x00, 0x00, // flags
  1013. 0x00, 0x00, 0x00, 0x02, // reference_ID
  1014. 0x00, 0x00, 0x00, 0x01, // timescale
  1015. 0x01, 0x02, 0x03, 0x04, // earliest_presentation_time
  1016. 0x05, 0x06, 0x07, 0x08,
  1017. 0x08, 0x07, 0x06, 0x05, // first_offset
  1018. 0x04, 0x03, 0x02, 0x01,
  1019. 0x00, 0x00, // reserved
  1020. 0x00, 0x02, // reference_count
  1021. // first reference
  1022. 0x80, 0x00, 0x00, 0x07, // reference_type(1) + referenced_size(31)
  1023. 0x00, 0x00, 0x00, 0x08, // subsegment_duration
  1024. 0x80, 0x00, 0x00, 0x09, // starts_with_SAP(1) + SAP_type(3) + SAP_delta_time(28)
  1025. // second reference
  1026. 0x00, 0x00, 0x00, 0x03, // reference_type(1) + referenced_size(31)
  1027. 0x00, 0x00, 0x00, 0x04, // subsegment_duration
  1028. 0x10, 0x00, 0x00, 0x05 // starts_with_SAP(1) + SAP_type(3) + SAP_delta_time(28)
  1029. );
  1030. assert.deepEqual(mp4.tools.inspect(new Uint8Array(data)),
  1031. [{
  1032. type: 'sidx',
  1033. version: 1,
  1034. flags: new Uint8Array([0, 0x00, 0x00]),
  1035. timescale: 1,
  1036. earliestPresentationTime: BigInt('0x0102030405060708'),
  1037. firstOffset: BigInt('0x0807060504030201'),
  1038. referenceId: 2,
  1039. size: 64,
  1040. references: [{
  1041. referenceType: 1,
  1042. referencedSize: 7,
  1043. subsegmentDuration: 8,
  1044. startsWithSap: true,
  1045. sapType: 0,
  1046. sapDeltaTime: 9
  1047. }, {
  1048. referenceType: 0,
  1049. referencedSize: 3,
  1050. subsegmentDuration: 4,
  1051. startsWithSap: false,
  1052. sapType: 1,
  1053. sapDeltaTime: 5
  1054. }]
  1055. }]);
  1056. });
  1057. QUnit.test('can parse an smhd', function(assert) {
  1058. var data = box('smhd',
  1059. 0x00, // version
  1060. 0x00, 0x00, 0x00, // flags
  1061. 0x00, 0xff, // balance, fixed-point 8.8
  1062. 0x00, 0x00); // reserved
  1063. assert.deepEqual(mp4.tools.inspect(new Uint8Array(data)),
  1064. [{
  1065. type: 'smhd',
  1066. size: 16,
  1067. version: 0,
  1068. flags: new Uint8Array([0, 0, 0]),
  1069. balance: 0xff / Math.pow(2, 8)
  1070. }],
  1071. 'parsed an smhd');
  1072. });
  1073. QUnit.test('can parse a version 0 tfdt', function(assert) {
  1074. var data = box('tfdt',
  1075. 0x00, // version
  1076. 0x00, 0x00, 0x00, // flags
  1077. 0x01, 0x02, 0x03, 0x04); // baseMediaDecodeTime
  1078. assert.deepEqual(mp4.tools.inspect(new Uint8Array(data)),
  1079. [{
  1080. type: 'tfdt',
  1081. version: 0,
  1082. size: 16,
  1083. flags: new Uint8Array([0, 0, 0]),
  1084. baseMediaDecodeTime: 0x01020304
  1085. }]);
  1086. });
  1087. QUnit.test('can parse a version 1 tfdt and return an unsigned integer value', function(assert) {
  1088. var data = box('tfdt',
  1089. 0x01, // version
  1090. 0x00, 0x00, 0x00, // flags
  1091. 0x81, 0x02, 0x03, 0x04,
  1092. 0x05, 0x06, 0x07, 0x08); // baseMediaDecodeTime
  1093. assert.deepEqual(mp4.tools.inspect(new Uint8Array(data)),
  1094. [{
  1095. type: 'tfdt',
  1096. version: 1,
  1097. size: 20,
  1098. flags: new Uint8Array([0, 0, 0]),
  1099. baseMediaDecodeTime: BigInt('0x8102030405060708')
  1100. }]);
  1101. });
  1102. QUnit.test('can parse a series of boxes', function(assert) {
  1103. var ftyp = [
  1104. 0x00, 0x00, 0x00, 0x18 // size 4 * 6 = 24
  1105. ].concat(typeBytes('ftyp')).concat([
  1106. 0x69, 0x73, 0x6f, 0x6d, // major brand
  1107. 0x00, 0x00, 0x00, 0x02, // minor version
  1108. 98, 101, 101, 112, // compatible brands
  1109. 98, 111, 111, 112 // compatible brands
  1110. ]);
  1111. assert.deepEqual(mp4.tools.inspect(new Uint8Array(ftyp.concat(ftyp))),
  1112. [{
  1113. type: 'ftyp',
  1114. size: 4 * 6,
  1115. majorBrand: 'isom',
  1116. minorVersion: 2,
  1117. compatibleBrands: ['beep', 'boop']
  1118. }, {
  1119. type: 'ftyp',
  1120. size: 4 * 6,
  1121. majorBrand: 'isom',
  1122. minorVersion: 2,
  1123. compatibleBrands: ['beep', 'boop']
  1124. }],
  1125. 'parsed two boxes in series');
  1126. });