mp4-probe.test.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. 'use strict';
  2. var
  3. QUnit = require('qunit'),
  4. probe = require('../lib/mp4/probe'),
  5. mp4Helpers = require('./utils/mp4-helpers'),
  6. box = mp4Helpers.box,
  7. id3 = require('./utils/id3-generator'),
  8. // defined below
  9. moovWithoutMdhd,
  10. moovWithoutTkhd,
  11. moofWithTfdt,
  12. multiMoof,
  13. multiTraf,
  14. noTrunSamples,
  15. v1boxes;
  16. QUnit.module('MP4 Probe');
  17. QUnit.test('reads the timescale from an mdhd', function(assert) {
  18. // sampleMoov has a base timescale of 1000 with an override to 90kHz
  19. // in the mdhd
  20. assert.deepEqual(probe.timescale(new Uint8Array(mp4Helpers.sampleMoov)), {
  21. 1: 90e3,
  22. 2: 90e3
  23. }, 'found the timescale');
  24. });
  25. QUnit.test('reads tracks', function(assert) {
  26. var tracks = probe.tracks(new Uint8Array(mp4Helpers.sampleMoov));
  27. assert.equal(tracks.length, 2, 'two tracks');
  28. assert.equal(tracks[0].codec, 'avc1.4d400d', 'codec is correct');
  29. assert.equal(tracks[0].id, 1, 'id is correct');
  30. assert.equal(tracks[0].type, 'video', 'type is correct');
  31. assert.equal(tracks[0].timescale, 90e3, 'timescale is correct');
  32. assert.equal(tracks[1].codec, 'mp4a.40.2', 'codec is correct');
  33. assert.equal(tracks[1].id, 2, 'id is correct');
  34. assert.equal(tracks[1].type, 'audio', 'type is correct');
  35. assert.equal(tracks[1].timescale, 90e3, 'timescale is correct');
  36. });
  37. QUnit.test('returns null if the tkhd is missing', function(assert) {
  38. assert.equal(probe.timescale(new Uint8Array(moovWithoutTkhd)), null, 'indicated missing info');
  39. });
  40. QUnit.test('returns null if the mdhd is missing', function(assert) {
  41. assert.equal(probe.timescale(new Uint8Array(moovWithoutMdhd)), null, 'indicated missing info');
  42. });
  43. QUnit.test('startTime reads the base decode time from a tfdt', function(assert) {
  44. assert.equal(probe.startTime({
  45. 4: 2
  46. }, new Uint8Array(moofWithTfdt)),
  47. 0x01020304 / 2,
  48. 'calculated base decode time');
  49. });
  50. QUnit.test('startTime returns the earliest base decode time', function(assert) {
  51. assert.equal(probe.startTime({
  52. 4: 2,
  53. 6: 1
  54. }, new Uint8Array(multiMoof)),
  55. 0x01020304 / 2,
  56. 'returned the earlier time');
  57. });
  58. QUnit.test('startTime parses 64-bit base decode times', function(assert) {
  59. assert.equal(probe.startTime({
  60. 4: 3
  61. }, new Uint8Array(v1boxes)),
  62. 0x0101020304 / 3,
  63. 'parsed a long value');
  64. });
  65. QUnit.test('compositionStartTime calculates composition time using composition time' +
  66. 'offset from first trun sample', function(assert) {
  67. assert.equal(probe.compositionStartTime({
  68. 1: 6,
  69. 4: 3
  70. }, new Uint8Array(moofWithTfdt)),
  71. (0x01020304 + 10) / 3,
  72. 'calculated correct composition start time');
  73. });
  74. QUnit.test('compositionStartTime looks at only the first traf', function(assert) {
  75. assert.equal(probe.compositionStartTime({
  76. 2: 6,
  77. 4: 3
  78. }, new Uint8Array(multiTraf)),
  79. (0x01020304 + 10) / 3,
  80. 'calculated composition start time from first traf');
  81. });
  82. QUnit.test('compositionStartTime uses default composition time offset of 0' +
  83. 'if no trun samples present', function(assert) {
  84. assert.equal(probe.compositionStartTime({
  85. 2: 6,
  86. 4: 3
  87. }, new Uint8Array(noTrunSamples)),
  88. (0x01020304 + 0) / 3,
  89. 'calculated correct composition start time using default offset');
  90. });
  91. QUnit.test('getTimescaleFromMediaHeader gets timescale for version 0 mdhd', function(assert) {
  92. var mdhd = new Uint8Array([
  93. 0x00, // version 0
  94. 0x00, 0x00, 0x00, // flags
  95. // version 0 has 32 bit creation_time, modification_time, and duration
  96. 0x00, 0x00, 0x00, 0x02, // creation_time
  97. 0x00, 0x00, 0x00, 0x03, // modification_time
  98. 0x00, 0x00, 0x03, 0xe8, // timescale = 1000
  99. 0x00, 0x00, 0x00, 0x00,
  100. 0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
  101. 0x15, 0xc7 // 'eng' language
  102. ]);
  103. assert.equal(
  104. probe.getTimescaleFromMediaHeader(mdhd),
  105. 1000,
  106. 'got timescale from version 0 mdhd'
  107. );
  108. });
  109. QUnit.test('getTimescaleFromMediaHeader gets timescale for version 0 mdhd', function(assert) {
  110. var mdhd = new Uint8Array([
  111. 0x01, // version 1
  112. 0x00, 0x00, 0x00, // flags
  113. // version 1 has 64 bit creation_time, modification_time, and duration
  114. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // creation_time
  115. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // modification_time
  116. 0x00, 0x00, 0x03, 0xe8, // timescale = 1000
  117. 0x00, 0x00, 0x00, 0x00,
  118. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
  119. 0x15, 0xc7 // 'eng' language
  120. ]);
  121. assert.equal(
  122. probe.getTimescaleFromMediaHeader(mdhd),
  123. 1000,
  124. 'got timescale from version 1 mdhd'
  125. );
  126. });
  127. QUnit.test('can get ID3 data from a v0 EMSG box', function(assert) {
  128. var id3Data = new Uint8Array(id3.id3Tag(id3.id3Frame('PRIV',
  129. id3.stringToCString('priv-owner@example.com'),
  130. id3.stringToInts('foo.bar.id3.com')))
  131. );
  132. var v0EmsgId3Data = mp4Helpers.generateEmsgBoxData(0, id3Data);
  133. var emsgId3Box = new Uint8Array(box('emsg', [].slice.call(v0EmsgId3Data)));
  134. var emsgBoxes = probe.getEmsgID3(emsgId3Box, 10);
  135. assert.equal(emsgBoxes[0].cueTime, 20, 'got correct emsg cueTime value from v0 emsg');
  136. assert.equal(emsgBoxes[0].duration, 0, 'got correct emsg duration value from v0 emsg');
  137. assert.equal(emsgBoxes[0].frames[0].id, 'PRIV' , 'got correct ID3 id');
  138. assert.equal(emsgBoxes[0].frames[0].owner, 'priv-owner@example.com', 'got correct ID3 owner');
  139. assert.deepEqual(emsgBoxes[0].frames[0].data, new Uint8Array(id3.stringToInts('foo.bar.id3.com')), 'got correct ID3 data');
  140. });
  141. QUnit.test('can get ID3 data from a v1 EMSG box', function(assert) {
  142. var id3Data = new Uint8Array(id3.id3Tag(id3.id3Frame('TXXX',
  143. 0x03, // utf-8
  144. id3.stringToCString('foo bar'),
  145. id3.stringToCString('{ "key": "value" }')),
  146. [0x00, 0x00])
  147. );
  148. var v1EmsgId3Data = mp4Helpers.generateEmsgBoxData(1, id3Data);
  149. var emsgId3Box = new Uint8Array(box('emsg', [].slice.call(v1EmsgId3Data)));
  150. var emsgBoxes = probe.getEmsgID3(emsgId3Box);
  151. assert.equal(emsgBoxes[0].cueTime, 100, 'got correct emsg cueTime value from v1 emsg');
  152. assert.equal(emsgBoxes[0].duration, 0.01, 'got correct emsg duration value from v1 emsg');
  153. assert.equal(emsgBoxes[0].frames[0].id, 'TXXX' , 'got correct ID3 id');
  154. assert.equal(emsgBoxes[0].frames[0].description, 'foo bar', 'got correct ID3 description');
  155. assert.deepEqual(JSON.parse(emsgBoxes[0].frames[0].data), { key: 'value' }, 'got correct ID3 data');
  156. });
  157. QUnit.test('can get ID3 data from multiple EMSG boxes', function(assert) {
  158. var v1id3Data = new Uint8Array(id3.id3Tag(id3.id3Frame('PRIV',
  159. id3.stringToCString('priv-owner@example.com'),
  160. id3.stringToInts('foo.bar.id3.com')))
  161. );
  162. var v0id3Data = new Uint8Array(id3.id3Tag(id3.id3Frame('TXXX',
  163. 0x03, // utf-8
  164. id3.stringToCString('foo bar'),
  165. id3.stringToCString('{ "key": "value" }')),
  166. [0x00, 0x00])
  167. );
  168. var v1EmsgId3Data = mp4Helpers.generateEmsgBoxData(1, v1id3Data);
  169. var v1emsgId3Box = new Uint8Array(box('emsg', [].slice.call(v1EmsgId3Data)));
  170. var v0EmsgId3Data = mp4Helpers.generateEmsgBoxData(0, v0id3Data);
  171. var v0emsgId3Box = new Uint8Array(box('emsg', [].slice.call(v0EmsgId3Data)));
  172. var multiBoxData = new Uint8Array(v1emsgId3Box.length + v0emsgId3Box.length);
  173. multiBoxData.set(v1emsgId3Box);
  174. multiBoxData.set(v0emsgId3Box, v1emsgId3Box.length);
  175. var emsgBoxes = probe.getEmsgID3(multiBoxData);
  176. assert.equal(emsgBoxes[0].cueTime, 100, 'got correct emsg cueTime value from v1 emsg');
  177. assert.equal(emsgBoxes[0].duration, 0.01, 'got correct emsg duration value from v1 emsg');
  178. assert.equal(emsgBoxes[0].frames[0].id, 'PRIV' , 'got correct ID3 id');
  179. assert.equal(emsgBoxes[0].frames[0].owner, 'priv-owner@example.com', 'got correct ID3 owner');
  180. assert.deepEqual(emsgBoxes[0].frames[0].data, new Uint8Array(id3.stringToInts('foo.bar.id3.com')), 'got correct ID3 data');
  181. assert.equal(emsgBoxes[1].cueTime, 10, 'got correct emsg cueTime value from v0 emsg');
  182. assert.equal(emsgBoxes[1].duration, 0, 'got correct emsg duration value from v0 emsg');
  183. assert.equal(emsgBoxes[1].frames[0].id, 'TXXX' , 'got correct ID3 id');
  184. assert.equal(emsgBoxes[1].frames[0].description, 'foo bar', 'got correct ID3 description');
  185. assert.deepEqual(JSON.parse(emsgBoxes[1].frames[0].data),{ key: 'value' }, 'got correct ID3 data');
  186. });
  187. // ---------
  188. // Test Data
  189. // ---------
  190. moovWithoutTkhd =
  191. box('moov',
  192. box('trak',
  193. box('mdia',
  194. box('mdhd',
  195. 0x00, // version 0
  196. 0x00, 0x00, 0x00, // flags
  197. 0x00, 0x00, 0x00, 0x02, // creation_time
  198. 0x00, 0x00, 0x00, 0x03, // modification_time
  199. 0x00, 0x00, 0x03, 0xe8, // timescale = 1000
  200. 0x00, 0x00, 0x00, 0x00,
  201. 0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
  202. 0x15, 0xc7, // 'eng' language
  203. 0x00, 0x00),
  204. box('hdlr',
  205. 0x00, // version 1
  206. 0x00, 0x00, 0x00, // flags
  207. 0x00, 0x00, 0x00, 0x00, // pre_defined
  208. mp4Helpers.typeBytes('vide'), // handler_type
  209. 0x00, 0x00, 0x00, 0x00, // reserved
  210. 0x00, 0x00, 0x00, 0x00, // reserved
  211. 0x00, 0x00, 0x00, 0x00, // reserved
  212. mp4Helpers.typeBytes('one'), 0x00)))); // name
  213. moovWithoutMdhd =
  214. box('moov',
  215. box('trak',
  216. box('tkhd',
  217. 0x01, // version 1
  218. 0x00, 0x00, 0x00, // flags
  219. 0x00, 0x00, 0x00, 0x00,
  220. 0x00, 0x00, 0x00, 0x02, // creation_time
  221. 0x00, 0x00, 0x00, 0x00,
  222. 0x00, 0x00, 0x00, 0x03, // modification_time
  223. 0x00, 0x00, 0x00, 0x01, // track_ID
  224. 0x00, 0x00, 0x00, 0x00, // reserved
  225. 0x00, 0x00, 0x00, 0x00,
  226. 0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
  227. 0x00, 0x00, 0x00, 0x00,
  228. 0x00, 0x00, 0x00, 0x00, // reserved
  229. 0x00, 0x00, // layer
  230. 0x00, 0x00, // alternate_group
  231. 0x00, 0x00, // non-audio track volume
  232. 0x00, 0x00, // reserved
  233. mp4Helpers.unityMatrix,
  234. 0x01, 0x2c, 0x00, 0x00, // 300 in 16.16 fixed-point
  235. 0x00, 0x96, 0x00, 0x00), // 150 in 16.16 fixed-point
  236. box('mdia',
  237. box('hdlr',
  238. 0x01, // version 1
  239. 0x00, 0x00, 0x00, // flags
  240. 0x00, 0x00, 0x00, 0x00, // pre_defined
  241. mp4Helpers.typeBytes('vide'), // handler_type
  242. 0x00, 0x00, 0x00, 0x00, // reserved
  243. 0x00, 0x00, 0x00, 0x00, // reserved
  244. 0x00, 0x00, 0x00, 0x00, // reserved
  245. mp4Helpers.typeBytes('one'), 0x00)))); // name
  246. moofWithTfdt =
  247. box('moof',
  248. box('mfhd',
  249. 0x00, // version
  250. 0x00, 0x00, 0x00, // flags
  251. 0x00, 0x00, 0x00, 0x04), // sequence_number
  252. box('traf',
  253. box('tfhd',
  254. 0x00, // version
  255. 0x00, 0x00, 0x3b, // flags
  256. 0x00, 0x00, 0x00, 0x04, // track_ID = 4
  257. 0x00, 0x00, 0x00, 0x00,
  258. 0x00, 0x00, 0x00, 0x01, // base_data_offset
  259. 0x00, 0x00, 0x00, 0x02, // sample_description_index
  260. 0x00, 0x00, 0x00, 0x03, // default_sample_duration,
  261. 0x00, 0x00, 0x00, 0x04, // default_sample_size
  262. 0x00, 0x00, 0x00, 0x05),
  263. box('tfdt',
  264. 0x00, // version
  265. 0x00, 0x00, 0x00, // flags
  266. 0x01, 0x02, 0x03, 0x04), // baseMediaDecodeTime
  267. box('trun',
  268. 0x00, // version
  269. 0x00, 0x0f, 0x01, // flags: dataOffsetPresent, sampleDurationPresent,
  270. // sampleSizePresent, sampleFlagsPresent,
  271. // sampleCompositionTimeOffsetsPresent
  272. 0x00, 0x00, 0x00, 0x02, // sample_count
  273. 0x00, 0x00, 0x00, 0x00, // data_offset, no first_sample_flags
  274. // sample 1
  275. 0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
  276. 0x00, 0x00, 0x00, 0x0a, // sample_size = 10
  277. 0x00, 0x00, 0x00, 0x00, // sample_flags
  278. 0x00, 0x00, 0x00, 0x0a, // signed sample_composition_time_offset = 10
  279. // sample 2
  280. 0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
  281. 0x00, 0x00, 0x00, 0x0a, // sample_size = 10
  282. 0x00, 0x00, 0x00, 0x00, // sample_flags
  283. 0x00, 0x00, 0x00, 0x14))); // signed sample_composition_time_offset = 20
  284. noTrunSamples =
  285. box('moof',
  286. box('mfhd',
  287. 0x00, // version
  288. 0x00, 0x00, 0x00, // flags
  289. 0x00, 0x00, 0x00, 0x04), // sequence_number
  290. box('traf',
  291. box('tfhd',
  292. 0x00, // version
  293. 0x00, 0x00, 0x3b, // flags
  294. 0x00, 0x00, 0x00, 0x04, // track_ID = 4
  295. 0x00, 0x00, 0x00, 0x00,
  296. 0x00, 0x00, 0x00, 0x01, // base_data_offset
  297. 0x00, 0x00, 0x00, 0x02, // sample_description_index
  298. 0x00, 0x00, 0x00, 0x03, // default_sample_duration,
  299. 0x00, 0x00, 0x00, 0x04, // default_sample_size
  300. 0x00, 0x00, 0x00, 0x05),
  301. box('tfdt',
  302. 0x00, // version
  303. 0x00, 0x00, 0x00, // flags
  304. 0x01, 0x02, 0x03, 0x04), // baseMediaDecodeTime
  305. box('trun',
  306. 0x00, // version
  307. 0x00, 0x0f, 0x01, // flags: dataOffsetPresent, sampleDurationPresent,
  308. // sampleSizePresent, sampleFlagsPresent,
  309. // sampleCompositionTimeOffsetsPresent
  310. 0x00, 0x00, 0x00, 0x00, // sample_count
  311. 0x00, 0x00, 0x00, 0x00))); // data_offset, no first_sample_flags
  312. multiTraf =
  313. box('moof',
  314. box('mfhd',
  315. 0x00, // version
  316. 0x00, 0x00, 0x00, // flags
  317. 0x00, 0x00, 0x00, 0x04), // sequence_number
  318. box('traf',
  319. box('tfhd',
  320. 0x00, // version
  321. 0x00, 0x00, 0x3b, // flags
  322. 0x00, 0x00, 0x00, 0x04, // track_ID = 4
  323. 0x00, 0x00, 0x00, 0x00,
  324. 0x00, 0x00, 0x00, 0x01, // base_data_offset
  325. 0x00, 0x00, 0x00, 0x02, // sample_description_index
  326. 0x00, 0x00, 0x00, 0x03, // default_sample_duration,
  327. 0x00, 0x00, 0x00, 0x04, // default_sample_size
  328. 0x00, 0x00, 0x00, 0x05),
  329. box('tfdt',
  330. 0x00, // version
  331. 0x00, 0x00, 0x00, // flags
  332. 0x01, 0x02, 0x03, 0x04), // baseMediaDecodeTime
  333. box('trun',
  334. 0x00, // version
  335. 0x00, 0x0f, 0x01, // flags: dataOffsetPresent, sampleDurationPresent,
  336. // sampleSizePresent, sampleFlagsPresent,
  337. // sampleCompositionTimeOffsetsPresent
  338. 0x00, 0x00, 0x00, 0x02, // sample_count
  339. 0x00, 0x00, 0x00, 0x00, // data_offset, no first_sample_flags
  340. // sample 1
  341. 0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
  342. 0x00, 0x00, 0x00, 0x0a, // sample_size = 10
  343. 0x00, 0x00, 0x00, 0x00, // sample_flags
  344. 0x00, 0x00, 0x00, 0x0a, // signed sample_composition_time_offset = 10
  345. // sample 2
  346. 0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
  347. 0x00, 0x00, 0x00, 0x0a, // sample_size = 10
  348. 0x00, 0x00, 0x00, 0x00, // sample_flags
  349. 0x00, 0x00, 0x00, 0x14)), // signed sample_composition_time_offset = 20
  350. box('traf',
  351. box('tfhd',
  352. 0x00, // version
  353. 0x00, 0x00, 0x3b, // flags
  354. 0x00, 0x00, 0x00, 0x02, // track_ID = 2
  355. 0x00, 0x00, 0x00, 0x00,
  356. 0x00, 0x00, 0x00, 0x01, // base_data_offset
  357. 0x00, 0x00, 0x00, 0x02, // sample_description_index
  358. 0x00, 0x00, 0x00, 0x03, // default_sample_duration,
  359. 0x00, 0x00, 0x00, 0x04, // default_sample_size
  360. 0x00, 0x00, 0x00, 0x05),
  361. box('tfdt',
  362. 0x00, // version
  363. 0x00, 0x00, 0x00, // flags
  364. 0x01, 0x02, 0x01, 0x02), // baseMediaDecodeTime
  365. box('trun',
  366. 0x00, // version
  367. 0x00, 0x0f, 0x01, // flags: dataOffsetPresent, sampleDurationPresent,
  368. // sampleSizePresent, sampleFlagsPresent,
  369. // sampleCompositionTimeOffsetsPresent
  370. 0x00, 0x00, 0x00, 0x02, // sample_count
  371. 0x00, 0x00, 0x00, 0x00, // data_offset, no first_sample_flags
  372. // sample 1
  373. 0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
  374. 0x00, 0x00, 0x00, 0x0a, // sample_size = 10
  375. 0x00, 0x00, 0x00, 0x00, // sample_flags
  376. 0x00, 0x00, 0x00, 0x0b, // signed sample_composition_time_offset = 11
  377. // sample 2
  378. 0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
  379. 0x00, 0x00, 0x00, 0x0a, // sample_size = 10
  380. 0x00, 0x00, 0x00, 0x00, // sample_flags
  381. 0x00, 0x00, 0x00, 0x05))); // signed sample_composition_time_offset = 5
  382. multiMoof = moofWithTfdt
  383. .concat(box('moof',
  384. box('mfhd',
  385. 0x00, // version
  386. 0x00, 0x00, 0x00, // flags
  387. 0x00, 0x00, 0x00, 0x04), // sequence_number
  388. box('traf',
  389. box('tfhd',
  390. 0x00, // version
  391. 0x00, 0x00, 0x3b, // flags
  392. 0x00, 0x00, 0x00, 0x06, // track_ID = 6
  393. 0x00, 0x00, 0x00, 0x00,
  394. 0x00, 0x00, 0x00, 0x01, // base_data_offset
  395. 0x00, 0x00, 0x00, 0x02, // sample_description_index
  396. 0x00, 0x00, 0x00, 0x03, // default_sample_duration,
  397. 0x00, 0x00, 0x00, 0x04, // default_sample_size
  398. 0x00, 0x00, 0x00, 0x05),
  399. box('tfdt',
  400. 0x00, // version
  401. 0x00, 0x00, 0x00, // flags
  402. 0x01, 0x02, 0x03, 0x04), // baseMediaDecodeTime
  403. box('trun',
  404. 0x00, // version
  405. 0x00, 0x0f, 0x01, // flags: dataOffsetPresent, sampleDurationPresent,
  406. // sampleSizePresent, sampleFlagsPresent,
  407. // sampleCompositionTimeOffsetsPresent
  408. 0x00, 0x00, 0x00, 0x02, // sample_count
  409. 0x00, 0x00, 0x00, 0x00, // data_offset, no first_sample_flags
  410. // sample 1
  411. 0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
  412. 0x00, 0x00, 0x00, 0x0a, // sample_size = 10
  413. 0x00, 0x00, 0x00, 0x00, // sample_flags
  414. 0x00, 0x00, 0x00, 0x14, // signed sample_composition_time_offset = 20
  415. // sample 2
  416. 0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
  417. 0x00, 0x00, 0x00, 0x0a, // sample_size = 10
  418. 0x00, 0x00, 0x00, 0x00, // sample_flags
  419. 0x00, 0x00, 0x00, 0x0a)))); // signed sample_composition_time_offset = 10
  420. v1boxes =
  421. box('moof',
  422. box('mfhd',
  423. 0x01, // version
  424. 0x00, 0x00, 0x00, // flags
  425. 0x00, 0x00, 0x00, 0x04), // sequence_number
  426. box('traf',
  427. box('tfhd',
  428. 0x01, // version
  429. 0x00, 0x00, 0x3b, // flags
  430. 0x00, 0x00, 0x00, 0x04, // track_ID = 4
  431. 0x00, 0x00, 0x00, 0x00,
  432. 0x00, 0x00, 0x00, 0x01, // base_data_offset
  433. 0x00, 0x00, 0x00, 0x02, // sample_description_index
  434. 0x00, 0x00, 0x00, 0x03, // default_sample_duration,
  435. 0x00, 0x00, 0x00, 0x04, // default_sample_size
  436. 0x00, 0x00, 0x00, 0x05),
  437. box('tfdt',
  438. 0x01, // version
  439. 0x00, 0x00, 0x00, // flags
  440. 0x00, 0x00, 0x00, 0x01,
  441. 0x01, 0x02, 0x03, 0x04))); // baseMediaDecodeTime