lib/pes/pes_aac_parser.js

// Copyright 2018 Eyevinn Technology. All rights reserved
// Use of this source code is governed by a MIT License
// license that can be found in the LICENSE file.
// Author: Jonas Birme (Eyevinn Technology)

const PESParser = require("./pes_parser.js");
const log = require("logplease").create("PESAACParser", { useColors: false });
const util = require("../util.js");

const SAMPLING_RATES = {
  0: 96000,  // `0000` - 96000 Hz
  1: 88200,  // `0001` - 88200 Hz
  2: 64000,  // `0010` - 64000 Hz
  3: 48000,  // `0011` - 48000 Hz
  4: 44100,  // `0100` - 44100 Hz
  5: 32000,  // `0101` - 32000 Hz
  6: 24000,  // `0110` - 24000 Hz
  7: 22050,  // `0111` - 22050 Hz
  8: 16000,  // `1000` - 16000 Hz
  9: 12000,  // `1001` - 12000 Hz
 10: 11025,  // `1010` - 11025 Hz
 11:  8000,  // `1011` - 8000 Hz
 12:  7350,  // `1100` - 7350 Hz
 13:     0,  // `1101` - Reserved
 14:     0,  // `1110` - Reserved
 15:     0,  // `1111` - other
};

const CHANNELS = {
  0: 0.0,
  1: 1.0,    //   1 ch - (Front:       center)
  2: 2.0,    //   2 ch - (Front: left,         right)
  3: 3.0,    //   3 ch - (Front: left, center, right)
  4: 4.0,    //   4 ch - (Front: left, center, right)                   (Rear:       center)
  5: 5.0,    //   5 ch - (Front: left, center, right)                   (Rear: left,        right)
  6: 5.1,    // 5.1 ch - (Front: left, center, right)                   (Rear: left,        right, subwoofer)
  7: 7.1,    // 7.1 ch - (Front: left, center, right)(Side: left, right)(Rear: left,        right, subwoofer)
};

/**
 * @class
 * @extends PESParser
 */
class PESAACParser extends PESParser {
  constructor(pes) {
    super(pes);
  }

  /**
   * Get all ADTS Frames in this data stream
   * 
   * @return {AdtsFrame[]}
   */
  getAdtsFrames() {
    const data = this.getData();
    const len = data.byteLength;
    let pos = 0;
    let adtsFrames = [];

    //log.debug(util.hexDump(data));

    let byte0, byte1, byte2, byte3, byte4, byte5, byte6, byte7, byte8;
    while (pos < len) {
      let frameStart = pos;
      byte0 = data[pos];
      byte1 = data[pos + 1];      
      byte2 = data[pos + 2];      
      byte3 = data[pos + 3];      
      byte4 = data[pos + 4];      
      byte5 = data[pos + 5];      
      byte6 = data[pos + 6];      
      byte7 = data[pos + 7];      
      byte8 = data[pos + 8];      
      //log.debug(`pos=${pos}, byte:`, util.toHex(byte));

      let syncword = (byte0 === 0xFF) && ((byte1 & 0xF0) === 0xF0);   // 1111 1111 1111
      let mpegVersion = (byte1 & 0x08) === 1 ? 2 : 4;                 // 0 == MPEG4, 1 == MPEG2
      let layer = (byte1 & 0x06) === 0 ? true : false;
      let crcProtection = (byte1 & 0x01) === 0 ? true : false;        // 0 == HAS CRC, 1 == NO CRC
      let audioObjectType = ((byte2 & 0xC0) >> 6) + 1;                // 2 == AAC-LC, 5 == HE-AAC, 29 == HE-AAC v2
      let samplingRate = SAMPLING_RATES[(byte2 & 0x3C) >> 2];         // 0100 == 44100
      let channels = CHANNELS[((byte2 & 0x01) << 2 | byte3 & 0xC0) >> 6];  // 2 == LEFT+RIGHT

      let adtsFrameLength = (byte3 & 0x03) << 11 | byte4 << 3 | (byte5 & 0xE0) >> 5; // adtsHeaderLength + crcLength + (rawDataBlockEnd - rawDataBlockStart)
      let bufferFullness  = (byte5 & 0x1f) << 6  | byte6 >> 2;
      let rdbsInFrame = (byte6 & 0x03);
      let adtsHeaderLength = 7;
      let crcLength  = crcProtection ? 2 : 0;
      let rawDataBlockStart = pos + adtsHeaderLength;
      let rawDataBlockEnd = pos + adtsFrameLength;
      let crc1 = crcProtection ? (((byte7 << 8) | byte8) >>> 0) : 0;
      let crc2 = crc1;
      let error = false;

      if (!syncword || !layer || audioObjectType !== 2) {
        error = true;
      }
      //log.debug(`${pos}: syncword=${syncword}, layer=${layer}, audioObjectType=${audioObjectType}, error=${error}`);

      if (error) {
        pos++; // skip unknown byte
      } else {
        let pes = this.getHeaderForByteOffset(pos);
        pos += adtsFrameLength;

        adtsFrames.push({
          pes: pes,
          frameStart: frameStart,
          frameEnd: rawDataBlockEnd,
          mpegVersion: mpegVersion,
          crcProtection: crcProtection,
          audioObjectType: audioObjectType,
          samplingRate: samplingRate,
          channels: channels,
          adtsFrameLength: adtsFrameLength,
          adtsHeaderLength: adtsHeaderLength,
          crcLength: crcLength,
          rawDataBlockStart: rawDataBlockStart,
          rawDataBlockEnd: rawDataBlockEnd,
          bufferFullness: bufferFullness,
          rdbsInFrame: rdbsInFrame,
          error: error,
          data: data.subarray(rawDataBlockStart, rawDataBlockEnd)
        });
      }

    }
    return adtsFrames;
  }

  audioType(type) {
    const mapping = {
      2: 'AAC-LC',
      5: 'HE-AAC',
      29: 'HE-AACv2'
    };
    return mapping[type];
  }
}

module.exports = PESAACParser;