utils.js 7.84 KB
var
  mp2t = require('../lib/m2ts'),
  id3Generator = require('./utils/id3-generator'),
  MP2T_PACKET_LENGTH = mp2t.MP2T_PACKET_LENGTH,
  PMT,
  PAT,
  generatePMT,
  pesHeader,
  packetize,
  transportPacket,
  videoPes,
  adtsFrame,
  audioPes,
  timedMetadataPes,
  binaryStringToArrayOfBytes,
  leftPad;


PMT = [
  0x47, // sync byte
  // tei:0 pusi:1 tp:0 pid:0 0000 0010 0000
  0x40, 0x10,
  // tsc:01 afc:01 cc:0000 pointer_field:0000 0000
  0x50, 0x00,
  // tid:0000 0010 ssi:0 0:0 r:00 sl:0000 0001 1100
  0x02, 0x00, 0x1c,
  // pn:0000 0000 0000 0001
  0x00, 0x01,
  // r:00 vn:00 000 cni:1 sn:0000 0000 lsn:0000 0000
  0x01, 0x00, 0x00,
  // r:000 ppid:0 0011 1111 1111
  0x03, 0xff,
  // r:0000 pil:0000 0000 0000
  0x00, 0x00,
  // h264
  // st:0001 1010 r:000 epid:0 0000 0001 0001
  0x1b, 0x00, 0x11,
  // r:0000 esil:0000 0000 0000
  0x00, 0x00,
  // adts
  // st:0000 1111 r:000 epid:0 0000 0001 0010
  0x0f, 0x00, 0x12,
  // r:0000 esil:0000 0000 0000
  0x00, 0x00,

  // timed metadata
  // st:0001 0111 r:000 epid:0 0000 0001 0011
  0x15, 0x00, 0x13,
  // r:0000 esil:0000 0000 0000
  0x00, 0x00,

  // crc
  0x00, 0x00, 0x00, 0x00
];

/*
 Packet Header:
 | sb | tei pusi tp pid:5 | pid | tsc afc cc |
 with af:
 | afl | ... | <data> |
 without af:
 | <data> |

PAT:
 | pf? | ... |
 | tid | ssi '0' r sl:4 | sl | tsi:8 |
 | tsi | r vn cni | sn | lsn |

with program_number == '0':
 | pn | pn | r np:5 | np |
otherwise:
 | pn | pn | r pmp:5 | pmp |
*/

PAT = [
  0x47, // sync byte
  // tei:0 pusi:1 tp:0 pid:0 0000 0000 0000
  0x40, 0x00,
  // tsc:01 afc:01 cc:0000 pointer_field:0000 0000
  0x50, 0x00,
  // tid:0000 0000 ssi:0 0:0 r:00 sl:0000 0000 0000
  0x00, 0x00, 0x00,
  // tsi:0000 0000 0000 0000
  0x00, 0x00,
  // r:00 vn:00 000 cni:1 sn:0000 0000 lsn:0000 0000
  0x01, 0x00, 0x00,
  // pn:0000 0000 0000 0001
  0x00, 0x01,
  // r:000 pmp:0 0000 0010 0000
  0x00, 0x10,
  // crc32:0000 0000 0000 0000 0000 0000 0000 0000
  0x00, 0x00, 0x00, 0x00
];

generatePMT = function(options) {
  var PMT = [
    0x47, // sync byte
    // tei:0 pusi:1 tp:0 pid:0 0000 0010 0000
    0x40, 0x10,
    // tsc:01 afc:01 cc:0000 pointer_field:0000 0000
    0x50, 0x00,
    // tid:0000 0010 ssi:0 0:0 r:00 sl:0000 0001 1100
    0x02, 0x00, 0x1c,
    // pn:0000 0000 0000 0001
    0x00, 0x01,
    // r:00 vn:00 000 cni:1 sn:0000 0000 lsn:0000 0000
    0x01, 0x00, 0x00,
    // r:000 ppid:0 0011 1111 1111
    0x03, 0xff,
    // r:0000 pil:0000 0000 0000
    0x00, 0x00];

    if (options.hasVideo) {
      // h264
      PMT = PMT.concat([
        // st:0001 1010 r:000 epid:0 0000 0001 0001
        0x1b, 0x00, 0x11,
        // r:0000 esil:0000 0000 0000
        0x00, 0x00
      ]);
    }

    if (options.hasAudio) {
      // adts
      PMT = PMT.concat([
        // st:0000 1111 r:000 epid:0 0000 0001 0010
        0x0f, 0x00, 0x12,
        // r:0000 esil:0000 0000 0000
        0x00, 0x00
      ]);
    }

    if (options.hasMetadata) {
      // timed metadata
      PMT = PMT.concat([
        // st:0001 0111 r:000 epid:0 0000 0001 0011
        0x15, 0x00, 0x13,
        // r:0000 esil:0000 0000 0000
        0x00, 0x00
      ]);
    }

    // crc
    return PMT.concat([0x00, 0x00, 0x00, 0x00]);
};

pesHeader = function(first, pts, dataLength) {
  if (!dataLength) {
    dataLength = 0;
  } else {
    // Add the pes header length (only the portion after the
    // pes_packet_length field)
    dataLength += 3;
  }

  // PES_packet(), Rec. ITU-T H.222.0, Table 2-21
  var result = [
    // pscp:0000 0000 0000 0000 0000 0001
    0x00, 0x00, 0x01,
    // sid:0000 0000 ppl:0000 0000 0000 0000
    0x00, 0x00, 0x00,
    // 10 psc:00 pp:0 dai:1 c:0 ooc:0
    0x84,
    // pdf:?0 ef:1 erf:0 dtmf:0 acif:0 pcf:0 pef:0
    0x20 | (pts ? 0x80 : 0x00),
    // phdl:0000 0000
    (first ? 0x01 : 0x00) + (pts ? 0x05 : 0x00)
  ];

  // Only store 15 bits of the PTS for QUnit.testing purposes
  if (pts) {
    var
      pts32 = Math.floor(pts / 2), // right shift by 1
      leftMostBit = ((pts32 & 0x80000000) >>> 31) & 0x01,
      firstThree;

    pts = pts & 0xffffffff;        // remove left most bit
    firstThree = (leftMostBit << 3) | (((pts & 0xc0000000) >>> 29) & 0x06) | 0x01;
    result.push((0x2 << 4) | firstThree);
    result.push((pts >>> 22) & 0xff);
    result.push(((pts >>> 14) | 0x01) & 0xff);
    result.push((pts >>> 7) & 0xff);
    result.push(((pts << 1) | 0x01) & 0xff);

    // Add the bytes spent on the pts info
    dataLength += 5;
  }
  if (first) {
    result.push(0x00);
    dataLength += 1;
  }

  // Finally set the pes_packet_length field
  result[4] = (dataLength & 0x0000FF00) >> 8;
  result[5] = dataLength & 0x000000FF;

  return result;
};

packetize = function(data) {
  var packet = new Uint8Array(MP2T_PACKET_LENGTH);
  packet.set(data);
  return packet;
};

/**
 * Helper function to create transport stream PES packets
 * @param pid {uint8} - the program identifier (PID)
 * @param data {arraylike} - the payload bytes
 * @payload first {boolean} - true if this PES should be a payload
 * unit start
 */
transportPacket = function(pid, data, first, pts, isVideoData) {
  var
    adaptationFieldLength = 188 - data.length - 14 - (first ? 1 : 0) - (pts ? 5 : 0),
    // transport_packet(), Rec. ITU-T H.222.0, Table 2-2
    result = [
      // sync byte
      0x47,
      // tei:0 pusi:1 tp:0 pid:0 0000 0001 0001
      0x40, pid,
      // tsc:01 afc:11 cc:0000
      0x70
    ].concat([
      // afl
      adaptationFieldLength & 0xff,
      // di:0 rai:0 espi:0 pf:0 of:0 spf:0 tpdf:0 afef:0
      0x00
    ]),
    i;

  i = adaptationFieldLength - 1;
  while (i--) {
    // stuffing_bytes
    result.push(0xff);
  }

  // PES_packet(), Rec. ITU-T H.222.0, Table 2-21
  result = result.concat(pesHeader(first, pts, isVideoData ? 0 : data.length));

  return result.concat(data);
};

/**
 * Helper function to create video PES packets
 * @param data {arraylike} - the payload bytes
 * @payload first {boolean} - true if this PES should be a payload
 * unit start
 */
videoPes = function(data, first, pts) {
  return transportPacket(0x11, [
    // NAL unit start code
    0x00, 0x00, 0x01
  ].concat(data), first, pts, true);
};

/**
 * Helper function to create audio ADTS frame header
 * @param dataLength {number} - the payload byte count
 */
adtsFrame = function(dataLength) {
  var frameLength = dataLength + 7;
  return [
    0xff, 0xf1,                            // no CRC
    0x10,                                  // AAC Main, 44.1KHz
    0xb0 | ((frameLength & 0x1800) >> 11), // 2 channels
    (frameLength & 0x7f8) >> 3,
    ((frameLength & 0x07) << 5) + 7,       // frame length in bytes
    0x00                                   // one AAC per ADTS frame
  ];
};

/**
 * Helper function to create audio PES packets
 * @param data {arraylike} - the payload bytes
 * @payload first {boolean} - true if this PES should be a payload
 * unit start
 */
audioPes = function(data, first, pts) {
  return transportPacket(0x12,
    adtsFrame(data.length).concat(data),
    first, pts);
};

timedMetadataPes = function(data) {
  var id3 = id3Generator;
  return transportPacket(0x13, id3.id3Tag(id3.id3Frame('PRIV', 0x00, 0x01)));
};

binaryStringToArrayOfBytes = function(string) {
  var
    array = [],
    arrayIndex = 0,
    stringIndex = 0;

  while (stringIndex < string.length) {
    array[arrayIndex] = parseInt(string.slice(stringIndex, stringIndex + 8), 2);

    arrayIndex++;
    // next byte
    stringIndex += 8;
  }

  return array;
};

leftPad = function(string, targetLength) {
  if (string.length >= targetLength) {
    return string;
  }
  return new Array(targetLength - string.length + 1).join('0') + string;
};

module.exports = {
  PMT: PMT,
  PAT: PAT,
  generatePMT: generatePMT,
  pesHeader: pesHeader,
  packetize: packetize,
  transportPacket: transportPacket,
  videoPes: videoPes,
  adtsFrame: adtsFrame,
  audioPes: audioPes,
  timedMetadataPes: timedMetadataPes,
  binaryStringToArrayOfBytes: binaryStringToArrayOfBytes,
  leftPad: leftPad
};