diff options
Diffstat (limited to 'node_modules/prism-media/src/opus/Opus.js')
| -rw-r--r-- | node_modules/prism-media/src/opus/Opus.js | 189 |
1 files changed, 189 insertions, 0 deletions
diff --git a/node_modules/prism-media/src/opus/Opus.js b/node_modules/prism-media/src/opus/Opus.js new file mode 100644 index 0000000..9f3c598 --- /dev/null +++ b/node_modules/prism-media/src/opus/Opus.js @@ -0,0 +1,189 @@ +// Partly based on https://github.com/Rantanen/node-opus/blob/master/lib/Encoder.js + +const { Transform } = require('stream'); +const loader = require('../util/loader'); + +const CTL = { + BITRATE: 4002, + FEC: 4012, + PLP: 4014, +}; + +const Opus = loader.require([ + ['@discordjs/opus', o => o.OpusEncoder], + ['node-opus', o => o.OpusEncoder], + ['opusscript', o => o], +], { + fn: 'Encoder', +}); + +const charCode = x => x.charCodeAt(0); +const OPUS_HEAD = Buffer.from([...'OpusHead'].map(charCode)); +const OPUS_TAGS = Buffer.from([...'OpusTags'].map(charCode)); + +// frame size = (channels * rate * frame_duration) / 1000 + +/** + * Takes a stream of Opus data and outputs a stream of PCM data, or the inverse. + * **You shouldn't directly instantiate this class, see opus.Encoder and opus.Decoder instead!** + * @memberof opus + * @extends TransformStream + * @protected + */ +class OpusStream extends Transform { + /** + * Creates a new Opus transformer. + * @private + * @memberof opus + * @param {Object} [options] options that you would pass to a regular Transform stream + */ + constructor(options = {}) { + if (!Opus.Encoder) { + throw Error('Could not find an Opus module! Please install @discordjs/opus, node-opus, or opusscript.'); + } + super(Object.assign({ readableObjectMode: true }, options)); + if (Opus.name === 'opusscript') { + options.application = Opus.Encoder.Application[options.application]; + } + this.encoder = new Opus.Encoder(options.rate, options.channels, options.application); + + this._options = options; + this._required = this._options.frameSize * this._options.channels * 2; + } + + _encode(buffer) { + return this.encoder.encode(buffer, Opus.name === 'opusscript' ? null : this._options.frameSize); + } + + _decode(buffer) { + return this.encoder.decode(buffer, Opus.name === 'opusscript' ? null : this._options.frameSize); + } + + /** + * Returns the Opus module being used - `opusscript`, `node-opus`, or `@discordjs/opus`. + * @type {string} + * @readonly + * @example + * console.log(`Using Opus module ${prism.opus.Encoder.type}`); + */ + static get type() { + return Opus.name; + } + + /** + * Sets the bitrate of the stream. + * @param {number} bitrate the bitrate to use use, e.g. 48000 + * @public + */ + setBitrate(bitrate) { + (this.encoder.applyEncoderCTL || this.encoder.encoderCTL) + .apply(this.encoder, [CTL.BITRATE, Math.min(128e3, Math.max(16e3, bitrate))]); + } + + /** + * Enables or disables forward error correction. + * @param {boolean} enabled whether or not to enable FEC. + * @public + */ + setFEC(enabled) { + (this.encoder.applyEncoderCTL || this.encoder.encoderCTL) + .apply(this.encoder, [CTL.FEC, enabled ? 1 : 0]); + } + + /** + * Sets the expected packet loss over network transmission. + * @param {number} [percentage] a percentage (represented between 0 and 1) + */ + setPLP(percentage) { + (this.encoder.applyEncoderCTL || this.encoder.encoderCTL) + .apply(this.encoder, [CTL.PLP, Math.min(100, Math.max(0, percentage * 100))]); + } + + _final(cb) { + if (Opus.name === 'opusscript' && this.encoder) this.encoder.delete(); + cb(); + } +} + +/** + * An Opus encoder stream. + * + * Outputs opus packets in [object mode.](https://nodejs.org/api/stream.html#stream_object_mode) + * @extends opus.OpusStream + * @memberof opus + * @example + * const encoder = new prism.opus.Encoder({ frameSize: 960, channels: 2, rate: 48000 }); + * pcmAudio.pipe(encoder); + * // encoder will now output Opus-encoded audio packets + */ +class Encoder extends OpusStream { + /** + * Creates a new Opus encoder stream. + * @memberof opus + * @param {Object} options options that you would pass to a regular OpusStream, plus a few more: + * @param {number} options.frameSize the frame size in bytes to use (e.g. 960 for stereo audio at 48KHz with a frame + * duration of 20ms) + * @param {number} options.channels the number of channels to use + * @param {number} options.rate the sampling rate in Hz + */ + constructor(options) { + super(options); + this._buffer = Buffer.alloc(0); + } + + async _transform(chunk, encoding, done) { + this._buffer = Buffer.concat([this._buffer, chunk]); + let n = 0; + while (this._buffer.length >= this._required * (n + 1)) { + const buf = await this._encode(this._buffer.slice(n * this._required, (n + 1) * this._required)); + this.push(buf); + n++; + } + if (n > 0) this._buffer = this._buffer.slice(n * this._required); + return done(); + } + + _destroy(err, cb) { + super._destroy(err, cb); + this._buffer = null; + } +} + +/** + * An Opus decoder stream. + * + * Note that any stream you pipe into this must be in + * [object mode](https://nodejs.org/api/stream.html#stream_object_mode) and should output Opus packets. + * @extends opus.OpusStream + * @memberof opus + * @example + * const decoder = new prism.opus.Decoder({ frameSize: 960, channels: 2, rate: 48000 }); + * input.pipe(decoder); + * // decoder will now output PCM audio + */ +class Decoder extends OpusStream { + _transform(chunk, encoding, done) { + const signature = chunk.slice(0, 8); + if (signature.equals(OPUS_HEAD)) { + this.emit('format', { + channels: this._options.channels, + sampleRate: this._options.rate, + bitDepth: 16, + float: false, + signed: true, + version: chunk.readUInt8(8), + preSkip: chunk.readUInt16LE(10), + gain: chunk.readUInt16LE(16), + }); + return done(); + } + if (signature.equals(OPUS_TAGS)) { + this.emit('tags', chunk); + return done(); + } + this.push(this._decode(chunk)); + return done(); + } +} + +module.exports = { Decoder, Encoder }; |