caption-parser.test.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. 'use strict';
  2. var segments = require('data-files!segments');
  3. var probe = require('../lib/mp4/probe');
  4. var CaptionParser = require('../lib/mp4').CaptionParser;
  5. var captionParser;
  6. var dashInit = segments['dash-608-captions-init.mp4']();
  7. // This file includes 2 segments data to force a flush
  8. // of the first caption. The second caption is at 200s
  9. var dashSegment = segments['dash-608-captions-seg.m4s']();
  10. var malformedSei = segments['malformed-sei.m4s']();
  11. var malformedSeiInit = segments['malformed-sei-init.mp4']();
  12. var mp4Helpers = require('./utils/mp4-helpers');
  13. var box = mp4Helpers.box;
  14. var seiNalUnitGenerator = require('./utils/sei-nal-unit-generator');
  15. var makeMdatFromCaptionPackets = seiNalUnitGenerator.makeMdatFromCaptionPackets;
  16. var characters = seiNalUnitGenerator.characters;
  17. var packets0;
  18. var version0Moof;
  19. var version0Segment;
  20. var packets1;
  21. var version1Moof;
  22. var version1Segment;
  23. QUnit.module('MP4 Caption Parser', {
  24. beforeEach: function() {
  25. captionParser = new CaptionParser();
  26. captionParser.init();
  27. },
  28. afterEach: function() {
  29. captionParser.reset();
  30. }
  31. });
  32. QUnit.test('parse captions from real segment', function(assert) {
  33. var trackIds;
  34. var timescales;
  35. var cc;
  36. trackIds = probe.videoTrackIds(dashInit);
  37. timescales = probe.timescale(dashInit);
  38. cc = captionParser.parse(dashSegment, trackIds, timescales);
  39. assert.equal(cc.captions.length, 1);
  40. assert.equal(cc.captions[0].text, '00:00:00',
  41. 'real segment caption has correct text');
  42. assert.equal(cc.captions[0].stream, 'CC1',
  43. 'real segment caption has correct stream');
  44. assert.equal(cc.captions[0].startTime, 0,
  45. 'real segment caption has correct startTime');
  46. assert.equal(cc.captions[0].endTime, 119,
  47. 'real segment caption has correct endTime');
  48. assert.equal(cc.captionStreams.CC1, true,
  49. 'real segment caption streams have correct settings');
  50. });
  51. QUnit.test('parse captions when init segment received late', function(assert) {
  52. var trackIds;
  53. var timescales;
  54. var cc;
  55. trackIds = probe.videoTrackIds(dashInit);
  56. timescales = probe.timescale(dashInit);
  57. cc = captionParser.parse(dashSegment, [], {});
  58. assert.ok(!cc, 'there should not be any parsed captions yet');
  59. cc = captionParser.parse(dashSegment, trackIds, timescales);
  60. assert.equal(cc.captions.length, 1);
  61. });
  62. QUnit.test('parseTrackId for version 0 and version 1 boxes', function(assert) {
  63. var v0Captions;
  64. var v1Captions;
  65. v0Captions = captionParser.parse(
  66. new Uint8Array(version0Segment), // segment
  67. [1], // trackIds
  68. { 1: 90000 }); // timescales);
  69. assert.equal(v0Captions.captions.length, 1, 'got 1 version0 caption');
  70. assert.equal(v0Captions.captions[0].text, 'test string #1',
  71. 'got the expected version0 caption text');
  72. assert.equal(v0Captions.captions[0].stream, 'CC1',
  73. 'returned the correct caption stream CC1');
  74. assert.equal(v0Captions.captions[0].startTime, 10 / 90000,
  75. 'the start time for version0 caption is correct');
  76. assert.equal(v0Captions.captions[0].endTime, 10 / 90000,
  77. 'the end time for version0 caption is correct');
  78. assert.equal(v0Captions.captionStreams.CC1, true,
  79. 'stream is CC1');
  80. assert.ok(!v0Captions.captionStreams.CC4,
  81. 'stream is not CC4');
  82. // Clear parsed captions
  83. captionParser.clearParsedCaptions();
  84. v1Captions = captionParser.parse(
  85. new Uint8Array(version1Segment),
  86. [2], // trackIds
  87. { 2: 90000 }); // timescales
  88. assert.equal(v1Captions.captions.length, 1, 'got version1 caption');
  89. assert.equal(v1Captions.captions[0].text, 'test string #2',
  90. 'got the expected version1 caption text');
  91. assert.equal(v1Captions.captions[0].stream, 'CC4',
  92. 'returned the correct caption stream CC4');
  93. assert.equal(v1Captions.captions[0].startTime, 30 / 90000,
  94. 'the start time for version1 caption is correct');
  95. assert.equal(v1Captions.captions[0].endTime, 30 / 90000,
  96. 'the end time for version1 caption is correct');
  97. assert.equal(v1Captions.captionStreams.CC4, true,
  98. 'stream is CC4');
  99. assert.ok(!v1Captions.captionStreams.CC1,
  100. 'stream is not CC1');
  101. });
  102. QUnit.test('returns log on invalid sei nal parse', function(assert) {
  103. var trackIds;
  104. var timescales;
  105. var result;
  106. var logs = [];
  107. trackIds = probe.videoTrackIds(malformedSeiInit);
  108. timescales = probe.timescale(malformedSeiInit);
  109. result = captionParser.parse(malformedSei, trackIds, timescales);
  110. assert.deepEqual(result.logs, [
  111. {level: 'warn', message: 'We\'ve encountered a nal unit without data at 189975 for trackId 1. See mux.js#223.'}
  112. ], 'logged invalid sei nal');
  113. });
  114. // ---------
  115. // Test Data
  116. // ---------
  117. // "test string #1", channel 1, field 1
  118. packets0 = [
  119. // Send another command so that the second EOC isn't ignored
  120. { ccData: 0x1420, type: 0 },
  121. // RCL, resume caption loading
  122. { ccData: 0x1420, type: 0 },
  123. // 'test string #1'
  124. { ccData: characters('te'), type: 0 },
  125. { ccData: characters('st'), type: 0 },
  126. { ccData: characters(' s'), type: 0 },
  127. // 'test string #1' continued
  128. { ccData: characters('tr'), type: 0 },
  129. { ccData: characters('in'), type: 0 },
  130. { ccData: characters('g '), type: 0 },
  131. { ccData: characters('#1'), type: 0 },
  132. // EOC, End of Caption. End display
  133. { ccData: 0x142f, type: 0 },
  134. // EOC, End of Caption. Finished transmitting, begin display
  135. { ccData: 0x142f, type: 0 },
  136. // Send another command so that the second EOC isn't ignored
  137. { ccData: 0x1420, type: 0 },
  138. // EOC, End of Caption. End display
  139. { ccData: 0x142f, type: 0 }
  140. ];
  141. // "test string #2", channel 2, field 2
  142. packets1 = [
  143. // Send another command so that the second EOC isn't ignored
  144. { ccData: 0x1d20, type: 1 },
  145. // RCL, resume caption loading
  146. { ccData: 0x1d20, type: 1 },
  147. // 'test string #2'
  148. { ccData: characters('te'), type: 1 },
  149. { ccData: characters('st'), type: 1 },
  150. { ccData: characters(' s'), type: 1 },
  151. // 'test string #2' continued
  152. { ccData: characters('tr'), type: 1 },
  153. { ccData: characters('in'), type: 1 },
  154. { ccData: characters('g '), type: 1 },
  155. { ccData: characters('#2'), type: 1 },
  156. // EOC, End of Caption. End display
  157. { ccData: 0x1d2f, type: 1 },
  158. // EOC, End of Caption. Finished transmitting, begin display
  159. { ccData: 0x1d2f, type: 1 },
  160. // Send another command so that the second EOC isn't ignored
  161. { ccData: 0x1d20, type: 1 },
  162. // EOC, End of Caption. End display
  163. { ccData: 0x1d2f, type: 1 }
  164. ];
  165. /**
  166. * version 0:
  167. * Uses version 0 boxes, no first sample flags
  168. * sample size, flags, duration, composition time offset included.
  169. **/
  170. version0Moof =
  171. box('moof',
  172. box('traf',
  173. box('tfhd',
  174. 0x00, // version
  175. 0x00, 0x00, 0x00, // flags
  176. 0x00, 0x00, 0x00, 0x01, // track_ID
  177. 0x00, 0x00, 0x00, 0x00,
  178. 0x00, 0x00, 0x00, 0x00, // base_data_offset
  179. 0x00, 0x00, 0x00, 0x00, // sample_description_index
  180. 0x00, 0x00, 0x00, 0x00, // default_sample_duration
  181. 0x00, 0x00, 0x00, 0x00, // default_sample_size
  182. 0x00, 0x00, 0x00, 0x00), // default_sample_flags
  183. box('tfdt',
  184. 0x00, // version
  185. 0x00, 0x00, 0x00, // flags
  186. 0x00, 0x00, 0x00, 0x00), // baseMediaDecodeTime,
  187. box('trun',
  188. 0x00, // version
  189. 0x00, 0x0f, 0x01, // flags: dataOffsetPresent, sampleDurationPresent,
  190. // sampleSizePresent, sampleFlagsPresent,
  191. // sampleCompositionTimeOffsetsPresent
  192. 0x00, 0x00, 0x00, 0x02, // sample_count
  193. 0x00, 0x00, 0x00, 0x00, // data_offset, no first_sample_flags
  194. // sample 1
  195. 0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
  196. 0x00, 0x00, 0x00, 0x0a, // sample_size = 10
  197. 0x00, 0x00, 0x00, 0x00, // sample_flags
  198. 0x00, 0x00, 0x00, 0x0a, // signed sample_composition_time_offset = 10
  199. // sample 2
  200. 0x00, 0x00, 0x00, 0x0a, // sample_duration = 10
  201. 0x00, 0x00, 0x00, 0x0a, // sample_size = 10
  202. 0x00, 0x00, 0x00, 0x00, // sample_flags
  203. 0x00, 0x00, 0x00, 0x14))); // signed sample_composition_time_offset = 20
  204. version0Segment = version0Moof.concat(makeMdatFromCaptionPackets(packets0));
  205. /**
  206. * version 1:
  207. * Uses version 1 boxes, has first sample flags,
  208. * other samples include flags and composition time offset only.
  209. **/
  210. version1Moof =
  211. box('moof',
  212. box('traf',
  213. box('tfhd',
  214. 0x01, // version
  215. 0x00, 0x00, 0x18, // flags
  216. 0x00, 0x00, 0x00, 0x02, // track_ID
  217. // no base_data_offset, sample_description_index
  218. 0x00, 0x00, 0x00, 0x0a, // default_sample_duration = 10
  219. 0x00, 0x00, 0x00, 0x0a), // default_sample_size = 10
  220. box('tfdt',
  221. 0x01, // version
  222. 0x00, 0x00, 0x00, // flags
  223. 0x00, 0x00, 0x00, 0x00,
  224. 0x00, 0x00, 0x00, 0x14), // baseMediaDecodeTime = 20,
  225. box('trun',
  226. 0x01, // version
  227. 0x00, 0x0c, 0x05, // flags: dataOffsetPresent, sampleFlagsPresent,
  228. // firstSampleFlagsPresent,
  229. // sampleCompositionTimeOffsetsPresent
  230. 0x00, 0x00, 0x00, 0x02, // sample_count
  231. 0x00, 0x00, 0x00, 0x00, // data_offset, has first_sample_flags
  232. // sample 1
  233. 0x00, 0x00, 0x00, 0x00, // sample_flags
  234. 0x00, 0x00, 0x00, 0x0a, // signed sample_composition_time_offset = 10
  235. // sample 2
  236. 0x00, 0x00, 0x00, 0x00, // sample_flags
  237. 0x00, 0x00, 0x00, 0x14))); // signed sample_composition_time_offset = 20
  238. version1Segment = version1Moof.concat(makeMdatFromCaptionPackets(packets1));