caption-packet-parser.js 5.11 KB
/**
 * mux.js
 *
 * Copyright (c) Brightcove
 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
 *
 * Reads in-band caption information from a video elementary
 * stream. Captions must follow the CEA-708 standard for injection
 * into an MPEG-2 transport streams.
 * @see https://en.wikipedia.org/wiki/CEA-708
 * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
 */
'use strict'; // Supplemental enhancement information (SEI) NAL units have a
// payload type field to indicate how they are to be
// interpreted. CEAS-708 caption content is always transmitted with
// payload type 0x04.

var USER_DATA_REGISTERED_ITU_T_T35 = 4,
    RBSP_TRAILING_BITS = 128;
/**
  * Parse a supplemental enhancement information (SEI) NAL unit.
  * Stops parsing once a message of type ITU T T35 has been found.
  *
  * @param bytes {Uint8Array} the bytes of a SEI NAL unit
  * @return {object} the parsed SEI payload
  * @see Rec. ITU-T H.264, 7.3.2.3.1
  */

var parseSei = function parseSei(bytes) {
  var i = 0,
      result = {
    payloadType: -1,
    payloadSize: 0
  },
      payloadType = 0,
      payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message

  while (i < bytes.byteLength) {
    // stop once we have hit the end of the sei_rbsp
    if (bytes[i] === RBSP_TRAILING_BITS) {
      break;
    } // Parse payload type


    while (bytes[i] === 0xFF) {
      payloadType += 255;
      i++;
    }

    payloadType += bytes[i++]; // Parse payload size

    while (bytes[i] === 0xFF) {
      payloadSize += 255;
      i++;
    }

    payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break
    // there can only ever be one caption message in a frame's sei

    if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
      var userIdentifier = String.fromCharCode(bytes[i + 3], bytes[i + 4], bytes[i + 5], bytes[i + 6]);

      if (userIdentifier === 'GA94') {
        result.payloadType = payloadType;
        result.payloadSize = payloadSize;
        result.payload = bytes.subarray(i, i + payloadSize);
        break;
      } else {
        result.payload = void 0;
      }
    } // skip the payload and parse the next message


    i += payloadSize;
    payloadType = 0;
    payloadSize = 0;
  }

  return result;
}; // see ANSI/SCTE 128-1 (2013), section 8.1


var parseUserData = function parseUserData(sei) {
  // itu_t_t35_contry_code must be 181 (United States) for
  // captions
  if (sei.payload[0] !== 181) {
    return null;
  } // itu_t_t35_provider_code should be 49 (ATSC) for captions


  if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) {
    return null;
  } // the user_identifier should be "GA94" to indicate ATSC1 data


  if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') {
    return null;
  } // finally, user_data_type_code should be 0x03 for caption data


  if (sei.payload[7] !== 0x03) {
    return null;
  } // return the user_data_type_structure and strip the trailing
  // marker bits


  return sei.payload.subarray(8, sei.payload.length - 1);
}; // see CEA-708-D, section 4.4


var parseCaptionPackets = function parseCaptionPackets(pts, userData) {
  var results = [],
      i,
      count,
      offset,
      data; // if this is just filler, return immediately

  if (!(userData[0] & 0x40)) {
    return results;
  } // parse out the cc_data_1 and cc_data_2 fields


  count = userData[0] & 0x1f;

  for (i = 0; i < count; i++) {
    offset = i * 3;
    data = {
      type: userData[offset + 2] & 0x03,
      pts: pts
    }; // capture cc data when cc_valid is 1

    if (userData[offset + 2] & 0x04) {
      data.ccData = userData[offset + 3] << 8 | userData[offset + 4];
      results.push(data);
    }
  }

  return results;
};

var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
  var length = data.byteLength,
      emulationPreventionBytesPositions = [],
      i = 1,
      newLength,
      newData; // Find all `Emulation Prevention Bytes`

  while (i < length - 2) {
    if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
      emulationPreventionBytesPositions.push(i + 2);
      i += 2;
    } else {
      i++;
    }
  } // If no Emulation Prevention Bytes were found just return the original
  // array


  if (emulationPreventionBytesPositions.length === 0) {
    return data;
  } // Create a new array to hold the NAL unit data


  newLength = length - emulationPreventionBytesPositions.length;
  newData = new Uint8Array(newLength);
  var sourceIndex = 0;

  for (i = 0; i < newLength; sourceIndex++, i++) {
    if (sourceIndex === emulationPreventionBytesPositions[0]) {
      // Skip this byte
      sourceIndex++; // Remove this position index

      emulationPreventionBytesPositions.shift();
    }

    newData[i] = data[sourceIndex];
  }

  return newData;
}; // exports


module.exports = {
  parseSei: parseSei,
  parseUserData: parseUserData,
  parseCaptionPackets: parseCaptionPackets,
  discardEmulationPreventionBytes: discardEmulationPreventionBytes,
  USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35
};