buddy

node MVC discord bot
Log | Files | Refs | README

OggDemuxer.js (3446B)


      1 const { Transform } = require('stream');
      2 
      3 const OGG_PAGE_HEADER_SIZE = 26;
      4 const STREAM_STRUCTURE_VERSION = 0;
      5 
      6 const charCode = x => x.charCodeAt(0);
      7 const OGGS_HEADER = Buffer.from([...'OggS'].map(charCode));
      8 const OPUS_HEAD = Buffer.from([...'OpusHead'].map(charCode));
      9 const OPUS_TAGS = Buffer.from([...'OpusTags'].map(charCode));
     10 
     11 /**
     12  * Demuxes an Ogg stream (containing Opus audio) to output an Opus stream.
     13  * @extends {TransformStream}
     14  * @memberof opus
     15  */
     16 class OggDemuxer extends Transform {
     17   /**
     18    * Creates a new OggOpus demuxer.
     19    * @param {Object} [options] options that you would pass to a regular Transform stream.
     20    * @memberof opus
     21    */
     22   constructor(options = {}) {
     23     super(Object.assign({ readableObjectMode: true }, options));
     24     this._remainder = null;
     25     this._head = null;
     26     this._bitstream = null;
     27   }
     28 
     29   _transform(chunk, encoding, done) {
     30     if (this._remainder) {
     31       chunk = Buffer.concat([this._remainder, chunk]);
     32       this._remainder = null;
     33     }
     34 
     35     while (chunk) {
     36       const result = this._readPage(chunk);
     37       if (result) chunk = result;
     38       else break;
     39     }
     40     this._remainder = chunk;
     41     done();
     42   }
     43 
     44   /**
     45    * Reads a page from a buffer
     46    * @private
     47    * @param {Buffer} chunk the chunk containing the page
     48    * @returns {boolean|Buffer} if a buffer, it will be a slice of the excess data of the original, otherwise it will be
     49    * false and would indicate that there is not enough data to go ahead with reading this page.
     50    */
     51   _readPage(chunk) {
     52     if (chunk.length < OGG_PAGE_HEADER_SIZE) {
     53       return false;
     54     }
     55     if (!chunk.slice(0, 4).equals(OGGS_HEADER)) {
     56       throw Error(`capture_pattern is not ${OGGS_HEADER}`);
     57     }
     58     if (chunk.readUInt8(4) !== STREAM_STRUCTURE_VERSION) {
     59       throw Error(`stream_structure_version is not ${STREAM_STRUCTURE_VERSION}`);
     60     }
     61 
     62     if (chunk.length < 27) return false;
     63     const pageSegments = chunk.readUInt8(26);
     64     if (chunk.length < 27 + pageSegments) return false;
     65     const table = chunk.slice(27, 27 + pageSegments);
     66     const bitstream = chunk.readUInt32BE(14);
     67 
     68     let sizes = [], totalSize = 0;
     69 
     70     for (let i = 0; i < pageSegments;) {
     71       let size = 0, x = 255;
     72       while (x === 255) {
     73         if (i >= table.length) return false;
     74         x = table.readUInt8(i);
     75         i++;
     76         size += x;
     77       }
     78       sizes.push(size);
     79       totalSize += size;
     80     }
     81 
     82     if (chunk.length < 27 + pageSegments + totalSize) return false;
     83 
     84     let start = 27 + pageSegments;
     85     for (const size of sizes) {
     86       const segment = chunk.slice(start, start + size);
     87       const header = segment.slice(0, 8);
     88       if (this._head) {
     89         if (header.equals(OPUS_TAGS)) this.emit('tags', segment);
     90         else if (this._bitstream === bitstream) this.push(segment);
     91       } else if (header.equals(OPUS_HEAD)) {
     92         this.emit('head', segment);
     93         this._head = segment;
     94         this._bitstream = bitstream;
     95       } else {
     96         this.emit('unknownSegment', segment);
     97       }
     98       start += size;
     99     }
    100     return chunk.slice(start);
    101   }
    102 }
    103 
    104 /**
    105  * Emitted when the demuxer encounters the opus head.
    106  * @event OggDemuxer#head
    107  * @memberof opus
    108  * @param {Buffer} segment a buffer containing the opus head data.
    109  */
    110 
    111 /**
    112  * Emitted when the demuxer encounters opus tags.
    113  * @event OggDemuxer#tags
    114  * @memberof opus
    115  * @param {Buffer} segment a buffer containing the opus tags.
    116  */
    117 
    118 module.exports = OggDemuxer;