caption-packet-parser.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. /**
  2. * mux.js
  3. *
  4. * Copyright (c) Brightcove
  5. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  6. *
  7. * Reads in-band caption information from a video elementary
  8. * stream. Captions must follow the CEA-708 standard for injection
  9. * into an MPEG-2 transport streams.
  10. * @see https://en.wikipedia.org/wiki/CEA-708
  11. * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
  12. */
  13. 'use strict'; // Supplemental enhancement information (SEI) NAL units have a
  14. // payload type field to indicate how they are to be
  15. // interpreted. CEAS-708 caption content is always transmitted with
  16. // payload type 0x04.
  17. var USER_DATA_REGISTERED_ITU_T_T35 = 4,
  18. RBSP_TRAILING_BITS = 128;
  19. /**
  20. * Parse a supplemental enhancement information (SEI) NAL unit.
  21. * Stops parsing once a message of type ITU T T35 has been found.
  22. *
  23. * @param bytes {Uint8Array} the bytes of a SEI NAL unit
  24. * @return {object} the parsed SEI payload
  25. * @see Rec. ITU-T H.264, 7.3.2.3.1
  26. */
  27. var parseSei = function parseSei(bytes) {
  28. var i = 0,
  29. result = {
  30. payloadType: -1,
  31. payloadSize: 0
  32. },
  33. payloadType = 0,
  34. payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message
  35. while (i < bytes.byteLength) {
  36. // stop once we have hit the end of the sei_rbsp
  37. if (bytes[i] === RBSP_TRAILING_BITS) {
  38. break;
  39. } // Parse payload type
  40. while (bytes[i] === 0xFF) {
  41. payloadType += 255;
  42. i++;
  43. }
  44. payloadType += bytes[i++]; // Parse payload size
  45. while (bytes[i] === 0xFF) {
  46. payloadSize += 255;
  47. i++;
  48. }
  49. payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break
  50. // there can only ever be one caption message in a frame's sei
  51. if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
  52. var userIdentifier = String.fromCharCode(bytes[i + 3], bytes[i + 4], bytes[i + 5], bytes[i + 6]);
  53. if (userIdentifier === 'GA94') {
  54. result.payloadType = payloadType;
  55. result.payloadSize = payloadSize;
  56. result.payload = bytes.subarray(i, i + payloadSize);
  57. break;
  58. } else {
  59. result.payload = void 0;
  60. }
  61. } // skip the payload and parse the next message
  62. i += payloadSize;
  63. payloadType = 0;
  64. payloadSize = 0;
  65. }
  66. return result;
  67. }; // see ANSI/SCTE 128-1 (2013), section 8.1
  68. var parseUserData = function parseUserData(sei) {
  69. // itu_t_t35_contry_code must be 181 (United States) for
  70. // captions
  71. if (sei.payload[0] !== 181) {
  72. return null;
  73. } // itu_t_t35_provider_code should be 49 (ATSC) for captions
  74. if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) {
  75. return null;
  76. } // the user_identifier should be "GA94" to indicate ATSC1 data
  77. if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') {
  78. return null;
  79. } // finally, user_data_type_code should be 0x03 for caption data
  80. if (sei.payload[7] !== 0x03) {
  81. return null;
  82. } // return the user_data_type_structure and strip the trailing
  83. // marker bits
  84. return sei.payload.subarray(8, sei.payload.length - 1);
  85. }; // see CEA-708-D, section 4.4
  86. var parseCaptionPackets = function parseCaptionPackets(pts, userData) {
  87. var results = [],
  88. i,
  89. count,
  90. offset,
  91. data; // if this is just filler, return immediately
  92. if (!(userData[0] & 0x40)) {
  93. return results;
  94. } // parse out the cc_data_1 and cc_data_2 fields
  95. count = userData[0] & 0x1f;
  96. for (i = 0; i < count; i++) {
  97. offset = i * 3;
  98. data = {
  99. type: userData[offset + 2] & 0x03,
  100. pts: pts
  101. }; // capture cc data when cc_valid is 1
  102. if (userData[offset + 2] & 0x04) {
  103. data.ccData = userData[offset + 3] << 8 | userData[offset + 4];
  104. results.push(data);
  105. }
  106. }
  107. return results;
  108. };
  109. var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
  110. var length = data.byteLength,
  111. emulationPreventionBytesPositions = [],
  112. i = 1,
  113. newLength,
  114. newData; // Find all `Emulation Prevention Bytes`
  115. while (i < length - 2) {
  116. if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
  117. emulationPreventionBytesPositions.push(i + 2);
  118. i += 2;
  119. } else {
  120. i++;
  121. }
  122. } // If no Emulation Prevention Bytes were found just return the original
  123. // array
  124. if (emulationPreventionBytesPositions.length === 0) {
  125. return data;
  126. } // Create a new array to hold the NAL unit data
  127. newLength = length - emulationPreventionBytesPositions.length;
  128. newData = new Uint8Array(newLength);
  129. var sourceIndex = 0;
  130. for (i = 0; i < newLength; sourceIndex++, i++) {
  131. if (sourceIndex === emulationPreventionBytesPositions[0]) {
  132. // Skip this byte
  133. sourceIndex++; // Remove this position index
  134. emulationPreventionBytesPositions.shift();
  135. }
  136. newData[i] = data[sourceIndex];
  137. }
  138. return newData;
  139. }; // exports
  140. module.exports = {
  141. parseSei: parseSei,
  142. parseUserData: parseUserData,
  143. parseCaptionPackets: parseCaptionPackets,
  144. discardEmulationPreventionBytes: discardEmulationPreventionBytes,
  145. USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35
  146. };