mp4-generator.test.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  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. tools = require('../lib/tools/mp4-inspector.js'),
  24. QUnit = require('qunit'),
  25. validateMvhd, validateTrak, validateTkhd, validateMdia,
  26. validateMdhd, validateHdlr, validateMinf, validateDinf,
  27. validateStbl, validateStsd, validateMvex,
  28. validateVideoSample, validateAudioSample;
  29. QUnit.module('MP4 Generator');
  30. QUnit.test('generates a BSMFF ftyp', function(assert) {
  31. var data = mp4.generator.ftyp(), boxes;
  32. assert.ok(data, 'box is not null');
  33. boxes = tools.inspect(data);
  34. assert.equal(1, boxes.length, 'generated a single box');
  35. assert.equal(boxes[0].type, 'ftyp', 'generated ftyp type');
  36. assert.equal(boxes[0].size, data.byteLength, 'generated size');
  37. assert.equal(boxes[0].majorBrand, 'isom', 'major version is "isom"');
  38. assert.equal(boxes[0].minorVersion, 1, 'minor version is one');
  39. });
  40. validateMvhd = function(mvhd) {
  41. QUnit.assert.equal(mvhd.type, 'mvhd', 'generated a mvhd');
  42. QUnit.assert.equal(mvhd.duration, 0xffffffff, 'wrote the maximum movie header duration');
  43. QUnit.assert.equal(mvhd.nextTrackId, 0xffffffff, 'wrote the max next track id');
  44. };
  45. validateTrak = function(trak, expected) {
  46. expected = expected || {};
  47. QUnit.assert.equal(trak.type, 'trak', 'generated a trak');
  48. QUnit.assert.equal(trak.boxes.length, 2, 'generated two track sub boxes');
  49. validateTkhd(trak.boxes[0], expected);
  50. validateMdia(trak.boxes[1], expected);
  51. };
  52. validateTkhd = function(tkhd, expected) {
  53. QUnit.assert.equal(tkhd.type, 'tkhd', 'generated a tkhd');
  54. QUnit.assert.equal(tkhd.trackId, 7, 'wrote the track id');
  55. QUnit.assert.deepEqual(tkhd.flags, new Uint8Array([0, 0, 7]), 'flags should QUnit.equal 7');
  56. QUnit.assert.equal(tkhd.duration,
  57. expected.duration || Math.pow(2, 32) - 1,
  58. 'wrote duration into the track header');
  59. QUnit.assert.equal(tkhd.width, expected.width || 0, 'wrote width into the track header');
  60. QUnit.assert.equal(tkhd.height, expected.height || 0, 'wrote height into the track header');
  61. QUnit.assert.equal(tkhd.volume, 1, 'set volume to 1');
  62. };
  63. validateMdia = function(mdia, expected) {
  64. QUnit.assert.equal(mdia.type, 'mdia', 'generated an mdia type');
  65. QUnit.assert.equal(mdia.boxes.length, 3, 'generated three track media sub boxes');
  66. validateMdhd(mdia.boxes[0], expected);
  67. validateHdlr(mdia.boxes[1], expected);
  68. validateMinf(mdia.boxes[2], expected);
  69. };
  70. validateMdhd = function(mdhd, expected) {
  71. QUnit.assert.equal(mdhd.type, 'mdhd', 'generate an mdhd type');
  72. QUnit.assert.equal(mdhd.language, 'und', 'wrote undetermined language');
  73. QUnit.assert.equal(mdhd.timescale, expected.timescale || 90000, 'wrote the timescale');
  74. QUnit.assert.equal(mdhd.duration,
  75. expected.duration || Math.pow(2, 32) - 1,
  76. 'wrote duration into the media header');
  77. };
  78. validateHdlr = function(hdlr, expected) {
  79. QUnit.assert.equal(hdlr.type, 'hdlr', 'generate an hdlr type');
  80. if (expected.type !== 'audio') {
  81. QUnit.assert.equal(hdlr.handlerType, 'vide', 'wrote a video handler');
  82. QUnit.assert.equal(hdlr.name, 'VideoHandler', 'wrote the handler name');
  83. } else {
  84. QUnit.assert.equal(hdlr.handlerType, 'soun', 'wrote a sound handler');
  85. QUnit.assert.equal(hdlr.name, 'SoundHandler', 'wrote the sound handler name');
  86. }
  87. };
  88. validateMinf = function(minf, expected) {
  89. QUnit.assert.equal(minf.type, 'minf', 'generate an minf type');
  90. QUnit.assert.equal(minf.boxes.length, 3, 'generates three minf sub boxes');
  91. if (expected.type !== 'audio') {
  92. QUnit.assert.deepEqual({
  93. type: 'vmhd',
  94. size: 20,
  95. version: 0,
  96. flags: new Uint8Array([0, 0, 1]),
  97. graphicsmode: 0,
  98. opcolor: new Uint16Array([0, 0, 0])
  99. }, minf.boxes[0], 'generates a vhmd');
  100. } else {
  101. QUnit.assert.deepEqual({
  102. type: 'smhd',
  103. size: 16,
  104. version: 0,
  105. flags: new Uint8Array([0, 0, 0]),
  106. balance: 0
  107. }, minf.boxes[0], 'generates an smhd');
  108. }
  109. validateDinf(minf.boxes[1]);
  110. validateStbl(minf.boxes[2], expected);
  111. };
  112. validateDinf = function(dinf) {
  113. QUnit.assert.deepEqual({
  114. type: 'dinf',
  115. size: 36,
  116. boxes: [{
  117. type: 'dref',
  118. size: 28,
  119. version: 0,
  120. flags: new Uint8Array([0, 0, 0]),
  121. dataReferences: [{
  122. type: 'url ',
  123. size: 12,
  124. version: 0,
  125. flags: new Uint8Array([0, 0, 1])
  126. }]
  127. }]
  128. }, dinf, 'generates a dinf');
  129. };
  130. validateStbl = function(stbl, expected) {
  131. QUnit.assert.equal(stbl.type, 'stbl', 'generates an stbl type');
  132. QUnit.assert.equal(stbl.boxes.length, 5, 'generated five stbl child boxes');
  133. validateStsd(stbl.boxes[0], expected);
  134. QUnit.assert.deepEqual({
  135. type: 'stts',
  136. size: 16,
  137. version: 0,
  138. flags: new Uint8Array([0, 0, 0]),
  139. timeToSamples: []
  140. }, stbl.boxes[1], 'generated an stts');
  141. QUnit.assert.deepEqual({
  142. type: 'stsc',
  143. size: 16,
  144. version: 0,
  145. flags: new Uint8Array([0, 0, 0]),
  146. sampleToChunks: []
  147. }, stbl.boxes[2], 'generated an stsc');
  148. QUnit.assert.deepEqual({
  149. type: 'stsz',
  150. version: 0,
  151. size: 20,
  152. flags: new Uint8Array([0, 0, 0]),
  153. sampleSize: 0,
  154. entries: []
  155. }, stbl.boxes[3], 'generated an stsz');
  156. QUnit.assert.deepEqual({
  157. type: 'stco',
  158. size: 16,
  159. version: 0,
  160. flags: new Uint8Array([0, 0, 0]),
  161. chunkOffsets: []
  162. }, stbl.boxes[4], 'generated and stco');
  163. };
  164. validateStsd = function(stsd, expected) {
  165. QUnit.assert.equal(stsd.type, 'stsd', 'generated an stsd');
  166. QUnit.assert.equal(stsd.sampleDescriptions.length, 1, 'generated one sample');
  167. if (expected.type !== 'audio') {
  168. validateVideoSample(stsd.sampleDescriptions[0]);
  169. } else {
  170. validateAudioSample(stsd.sampleDescriptions[0]);
  171. }
  172. };
  173. validateVideoSample = function(sample) {
  174. QUnit.assert.deepEqual(sample, {
  175. type: 'avc1',
  176. size: 152,
  177. dataReferenceIndex: 1,
  178. width: 600,
  179. height: 300,
  180. horizresolution: 72,
  181. vertresolution: 72,
  182. frameCount: 1,
  183. depth: 24,
  184. config: [{
  185. type: 'avcC',
  186. size: 30,
  187. configurationVersion: 1,
  188. avcProfileIndication: 3,
  189. avcLevelIndication: 5,
  190. profileCompatibility: 7,
  191. lengthSizeMinusOne: 3,
  192. sps: [new Uint8Array([
  193. 0, 1, 2
  194. ]), new Uint8Array([
  195. 3, 4, 5
  196. ])],
  197. pps: [new Uint8Array([
  198. 6, 7, 8
  199. ])]
  200. }, {
  201. type: 'btrt',
  202. size: 20,
  203. bufferSizeDB: 1875072,
  204. maxBitrate: 3000000,
  205. avgBitrate: 3000000
  206. }, {
  207. type: 'pasp',
  208. size: 16,
  209. data: new Uint8Array([0, 0, 0, 1, 0, 0, 0, 1])
  210. }]
  211. }, 'generated a video sample');
  212. };
  213. validateAudioSample = function(sample) {
  214. QUnit.assert.deepEqual(sample, {
  215. type: 'mp4a',
  216. size: 75,
  217. dataReferenceIndex: 1,
  218. channelcount: 2,
  219. samplesize: 16,
  220. samplerate: 48000,
  221. streamDescriptor: {
  222. type: 'esds',
  223. version: 0,
  224. flags: new Uint8Array([0, 0, 0]),
  225. size: 39,
  226. esId: 0,
  227. streamPriority: 0,
  228. // these values were hard-coded based on a working audio init segment
  229. decoderConfig: {
  230. avgBitrate: 56000,
  231. maxBitrate: 56000,
  232. bufferSize: 1536,
  233. objectProfileIndication: 64,
  234. streamType: 5,
  235. decoderConfigDescriptor: {
  236. audioObjectType: 2,
  237. channelConfiguration: 2,
  238. length: 2,
  239. samplingFrequencyIndex: 3,
  240. tag: 5
  241. }
  242. }
  243. }
  244. }, 'generated an audio sample');
  245. };
  246. validateMvex = function(mvex, options) {
  247. options = options || {
  248. sampleDegradationPriority: 1
  249. };
  250. QUnit.assert.deepEqual({
  251. type: 'mvex',
  252. size: 40,
  253. boxes: [{
  254. type: 'trex',
  255. size: 32,
  256. version: 0,
  257. flags: new Uint8Array([0, 0, 0]),
  258. trackId: 7,
  259. defaultSampleDescriptionIndex: 1,
  260. defaultSampleDuration: 0,
  261. defaultSampleSize: 0,
  262. sampleDependsOn: 0,
  263. sampleIsDependedOn: 0,
  264. sampleHasRedundancy: 0,
  265. samplePaddingValue: 0,
  266. sampleIsDifferenceSample: true,
  267. sampleDegradationPriority: options.sampleDegradationPriority
  268. }]
  269. }, mvex, 'writes a movie extends box');
  270. };
  271. QUnit.test('generates a video moov', function(assert) {
  272. var
  273. boxes,
  274. data = mp4.generator.moov([{
  275. id: 7,
  276. duration: 100,
  277. width: 600,
  278. height: 300,
  279. type: 'video',
  280. profileIdc: 3,
  281. levelIdc: 5,
  282. profileCompatibility: 7,
  283. sarRatio: [1, 1],
  284. sps: [new Uint8Array([0, 1, 2]), new Uint8Array([3, 4, 5])],
  285. pps: [new Uint8Array([6, 7, 8])]
  286. }]);
  287. assert.ok(data, 'box is not null');
  288. boxes = tools.inspect(data);
  289. assert.equal(boxes.length, 1, 'generated a single box');
  290. assert.equal(boxes[0].type, 'moov', 'generated a moov type');
  291. assert.equal(boxes[0].size, data.byteLength, 'generated size');
  292. assert.equal(boxes[0].boxes.length, 3, 'generated three sub boxes');
  293. validateMvhd(boxes[0].boxes[0]);
  294. validateTrak(boxes[0].boxes[1], {
  295. duration: 100,
  296. width: 600,
  297. height: 300
  298. });
  299. validateMvex(boxes[0].boxes[2]);
  300. });
  301. QUnit.test('generates an audio moov', function(assert) {
  302. var
  303. data = mp4.generator.moov([{
  304. id: 7,
  305. type: 'audio',
  306. audioobjecttype: 2,
  307. channelcount: 2,
  308. samplerate: 48000,
  309. samplingfrequencyindex: 3,
  310. samplesize: 16
  311. }]),
  312. boxes;
  313. assert.ok(data, 'box is not null');
  314. boxes = tools.inspect(data);
  315. assert.equal(boxes.length, 1, 'generated a single box');
  316. assert.equal(boxes[0].type, 'moov', 'generated a moov type');
  317. assert.equal(boxes[0].size, data.byteLength, 'generated size');
  318. assert.equal(boxes[0].boxes.length, 3, 'generated three sub boxes');
  319. validateMvhd(boxes[0].boxes[0]);
  320. validateTrak(boxes[0].boxes[1], {
  321. type: 'audio',
  322. timescale: 48000
  323. });
  324. validateMvex(boxes[0].boxes[2], {
  325. sampleDegradationPriority: 0
  326. });
  327. });
  328. QUnit.test('generates a sound hdlr', function(assert) {
  329. var boxes, hdlr,
  330. data = mp4.generator.moov([{
  331. duration: 100,
  332. type: 'audio'
  333. }]);
  334. assert.ok(data, 'box is not null');
  335. boxes = tools.inspect(data);
  336. hdlr = boxes[0].boxes[1].boxes[1].boxes[1];
  337. assert.equal(hdlr.type, 'hdlr', 'generate an hdlr type');
  338. assert.equal(hdlr.handlerType, 'soun', 'wrote a sound handler');
  339. assert.equal(hdlr.name, 'SoundHandler', 'wrote the handler name');
  340. });
  341. QUnit.test('generates a video hdlr', function(assert) {
  342. var boxes, hdlr,
  343. data = mp4.generator.moov([{
  344. duration: 100,
  345. width: 600,
  346. height: 300,
  347. type: 'video',
  348. sps: [],
  349. pps: []
  350. }]);
  351. assert.ok(data, 'box is not null');
  352. boxes = tools.inspect(data);
  353. hdlr = boxes[0].boxes[1].boxes[1].boxes[1];
  354. assert.equal(hdlr.type, 'hdlr', 'generate an hdlr type');
  355. assert.equal(hdlr.handlerType, 'vide', 'wrote a video handler');
  356. assert.equal(hdlr.name, 'VideoHandler', 'wrote the handler name');
  357. });
  358. QUnit.test('generates an initialization segment', function(assert) {
  359. var
  360. data = mp4.generator.initSegment([{
  361. id: 1,
  362. width: 600,
  363. height: 300,
  364. type: 'video',
  365. sps: [new Uint8Array([0])],
  366. pps: [new Uint8Array([1])]
  367. }, {
  368. id: 2,
  369. type: 'audio'
  370. }]),
  371. init, mvhd, trak1, trak2, mvex;
  372. init = tools.inspect(data);
  373. assert.equal(init.length, 2, 'generated two boxes');
  374. assert.equal(init[0].type, 'ftyp', 'generated a ftyp box');
  375. assert.equal(init[1].type, 'moov', 'generated a moov box');
  376. assert.equal(init[1].boxes[0].duration, 0xffffffff, 'wrote a maximum duration');
  377. mvhd = init[1].boxes[0];
  378. assert.equal(mvhd.type, 'mvhd', 'wrote an mvhd');
  379. trak1 = init[1].boxes[1];
  380. assert.equal(trak1.type, 'trak', 'wrote a trak');
  381. assert.equal(trak1.boxes[0].trackId, 1, 'wrote the first track id');
  382. assert.equal(trak1.boxes[0].width, 600, 'wrote the first track width');
  383. assert.equal(trak1.boxes[0].height, 300, 'wrote the first track height');
  384. assert.equal(trak1.boxes[1].boxes[1].handlerType, 'vide', 'wrote the first track type');
  385. trak2 = init[1].boxes[2];
  386. assert.equal(trak2.type, 'trak', 'wrote a trak');
  387. assert.equal(trak2.boxes[0].trackId, 2, 'wrote the second track id');
  388. assert.equal(trak2.boxes[1].boxes[1].handlerType, 'soun', 'wrote the second track type');
  389. mvex = init[1].boxes[3];
  390. assert.equal(mvex.type, 'mvex', 'wrote an mvex');
  391. });
  392. QUnit.test('generates a minimal moof', function(assert) {
  393. var
  394. data = mp4.generator.moof(7, [{
  395. id: 17,
  396. samples: [{
  397. duration: 9000,
  398. size: 10,
  399. flags: {
  400. isLeading: 0,
  401. dependsOn: 2,
  402. isDependedOn: 1,
  403. hasRedundancy: 0,
  404. paddingValue: 0,
  405. isNonSyncSample: 0,
  406. degradationPriority: 14
  407. },
  408. compositionTimeOffset: 500
  409. }, {
  410. duration: 10000,
  411. size: 11,
  412. flags: {
  413. isLeading: 0,
  414. dependsOn: 1,
  415. isDependedOn: 0,
  416. hasRedundancy: 0,
  417. paddingValue: 0,
  418. isNonSyncSample: 0,
  419. degradationPriority: 9
  420. },
  421. compositionTimeOffset: 1000
  422. }]
  423. }]),
  424. moof = tools.inspect(data),
  425. trun,
  426. sdtp;
  427. assert.equal(moof.length, 1, 'generated one box');
  428. assert.equal(moof[0].type, 'moof', 'generated a moof box');
  429. assert.equal(moof[0].boxes.length, 2, 'generated two child boxes');
  430. assert.equal(moof[0].boxes[0].type, 'mfhd', 'generated an mfhd box');
  431. assert.equal(moof[0].boxes[0].sequenceNumber, 7, 'included the sequence_number');
  432. assert.equal(moof[0].boxes[1].type, 'traf', 'generated a traf box');
  433. assert.equal(moof[0].boxes[1].boxes.length, 4, 'generated track fragment info');
  434. assert.equal(moof[0].boxes[1].boxes[0].type, 'tfhd', 'generated a tfhd box');
  435. assert.equal(moof[0].boxes[1].boxes[0].trackId, 17, 'wrote the first track id');
  436. assert.equal(moof[0].boxes[1].boxes[0].baseDataOffset, undefined, 'did not set a base data offset');
  437. assert.equal(moof[0].boxes[1].boxes[1].type, 'tfdt', 'generated a tfdt box');
  438. assert.ok(moof[0].boxes[1].boxes[1].baseMediaDecodeTime >= 0,
  439. 'media decode time is non-negative');
  440. trun = moof[0].boxes[1].boxes[2];
  441. assert.equal(trun.type, 'trun', 'generated a trun box');
  442. assert.equal(typeof trun.dataOffset, 'number', 'has a data offset');
  443. assert.ok(trun.dataOffset >= 0, 'has a non-negative data offset');
  444. assert.equal(trun.dataOffset, moof[0].size + 8, 'sets the data offset past the mdat header');
  445. assert.equal(trun.samples.length, 2, 'wrote two samples');
  446. assert.equal(trun.samples[0].duration, 9000, 'wrote a sample duration');
  447. assert.equal(trun.samples[0].size, 10, 'wrote a sample size');
  448. assert.deepEqual(trun.samples[0].flags, {
  449. isLeading: 0,
  450. dependsOn: 2,
  451. isDependedOn: 1,
  452. hasRedundancy: 0,
  453. paddingValue: 0,
  454. isNonSyncSample: 0,
  455. degradationPriority: 14
  456. }, 'wrote the sample flags');
  457. assert.equal(trun.samples[0].compositionTimeOffset, 500, 'wrote the composition time offset');
  458. assert.equal(trun.samples[1].duration, 10000, 'wrote a sample duration');
  459. assert.equal(trun.samples[1].size, 11, 'wrote a sample size');
  460. assert.deepEqual(trun.samples[1].flags, {
  461. isLeading: 0,
  462. dependsOn: 1,
  463. isDependedOn: 0,
  464. hasRedundancy: 0,
  465. paddingValue: 0,
  466. isNonSyncSample: 0,
  467. degradationPriority: 9
  468. }, 'wrote the sample flags');
  469. assert.equal(trun.samples[1].compositionTimeOffset, 1000, 'wrote the composition time offset');
  470. sdtp = moof[0].boxes[1].boxes[3];
  471. assert.equal(sdtp.type, 'sdtp', 'generated an sdtp box');
  472. assert.equal(sdtp.samples.length, 2, 'wrote two samples');
  473. assert.deepEqual(sdtp.samples[0], {
  474. dependsOn: 2,
  475. isDependedOn: 1,
  476. hasRedundancy: 0
  477. }, 'wrote the sample data table');
  478. assert.deepEqual(sdtp.samples[1], {
  479. dependsOn: 1,
  480. isDependedOn: 0,
  481. hasRedundancy: 0
  482. }, 'wrote the sample data table');
  483. });
  484. QUnit.test('generates a moof for audio', function(assert) {
  485. var
  486. data = mp4.generator.moof(7, [{
  487. id: 17,
  488. type: 'audio',
  489. samples: [{
  490. duration: 9000,
  491. size: 10
  492. }, {
  493. duration: 10000,
  494. size: 11
  495. }]
  496. }]),
  497. moof = tools.inspect(data),
  498. trun;
  499. assert.deepEqual(moof[0].boxes[1].boxes.length, 3, 'generated three traf children');
  500. trun = moof[0].boxes[1].boxes[2];
  501. assert.ok(trun, 'generated a trun');
  502. assert.equal(trun.dataOffset, data.byteLength + 8, 'calculated the data offset');
  503. assert.deepEqual(trun.samples, [{
  504. duration: 9000,
  505. size: 10
  506. }, {
  507. duration: 10000,
  508. size: 11
  509. }], 'wrote simple audio samples');
  510. });
  511. QUnit.test('can generate a traf without samples', function(assert) {
  512. var
  513. data = mp4.generator.moof(8, [{
  514. trackId: 13
  515. }]),
  516. moof = tools.inspect(data);
  517. assert.equal(moof[0].boxes[1].boxes[2].samples.length, 0, 'generated no samples');
  518. });
  519. QUnit.test('generates an mdat', function(assert) {
  520. var
  521. data = mp4.generator.mdat(new Uint8Array([1, 2, 3, 4])),
  522. mdat = tools.inspect(data);
  523. assert.equal(mdat.length, 1, 'generated one box');
  524. assert.equal(mdat[0].type, 'mdat', 'generated an mdat box');
  525. assert.deepEqual(mdat[0].byteLength, 4, 'encapsulated the data');
  526. });