summaryrefslogtreecommitdiff
path: root/node_modules/prism-media/src/opus/Opus.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/prism-media/src/opus/Opus.js')
-rw-r--r--node_modules/prism-media/src/opus/Opus.js189
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 };