UNPKG

libopusutils

Version:

fork of johni0702/libopus.js. contains libopus compiled to JavaScript using emscripten

193 lines (176 loc) 5.63 kB
var libopus = require('../build/libopus.js').instance; var utils = require('./utils'); var util = require('util'); var extend = require('extend'); var Transform = require('stream').Transform; /** * Encoding mode. * @readonly * @enum {number} */ var Application = { VOIP: 2048, AUDIO: 2049, RESTRICTED_LOWDELAY: 2051 }; var p_pcm = utils.p_pcm; var p_data = utils.p_data; /** * Encoder for opus streams. * * @param {object} [opts={}] - Options for the encoder * @param {(8000|12000|16000|24000|48000)} [opts.rate=48000] - Sampling rate of input signal (Hz) * @param {number} [opts.channels=1] - Number of (interleaved) channels * @param {Application} [opts.application=AUDIO] - Encoding mode * @param {boolean} [opts.unsafe=false] - Mark this encoder as unsafe.<br> * Encoders in unsafe mode generally operate faster.<br> * Warning: {@link #destroy()} MUST be called on an unsafe encoder before * it is garbage collected. Otherwise it will leak memory. * @constructor */ function Encoder(opts) { // Allow use without new if (!(this instanceof Encoder)) return new Encoder(opts); opts = extend({ rate: 48000, channels: 1, application: Application.AUDIO, unsafe: false }, opts); if (opts.channels < 1 || opts.channels > 2) { throw "channels must be either 1 or 2"; } if ([8000, 12000, 16000, 24000, 48000].indexOf(opts.rate) == -1) { throw "rate can only be 8k, 12k, 16k, 24k or 48k"; } if (opts.application !== Application.VOIP && opts.application !== Application.AUDIO && opts.application !== Application.RESTRICTED_LOWDELAY) { throw "invalid application type"; } this._rate = opts.rate; this._channels = opts.channels; this._application = opts.application; this._unsafe = opts.unsafe; // Allocate space for the encoder state var size = libopus._opus_encoder_get_size(this._channels); var enc = libopus._malloc(size); // Initialize the encoder var ret = libopus._opus_encoder_init(enc, this._rate, this._channels, this._application); if (ret !== 0) { // Free allocated space and throw error libopus._free(enc); throw utils.stringifyError(ret); } // In unsafe mode, that's it. However in safe mode, we copy the state // to a local buffer and free our allocated memory afterwards if (this._unsafe) { this._state = enc; } else { this._state = libopus.HEAPU8.slice(enc, enc + size); libopus._free(enc); } } /** * Calls the specified function with the state loaded into memory. * * @param func - The function to be called * @returns The return value of func */ Encoder.prototype._withState = function(func) { if (this._unsafe) { // Unsafe mode already has the state stored in memory return func(this._state); } else { // Store state in memory var p = libopus._malloc(this._state.length); libopus.HEAPU8.set(this._state, p); // Call function try { return func(p); } finally { // Retrieve state from memory this._state.set(libopus.HEAPU8.subarray(p, p + this._state.length)); libopus._free(p); } } }; /** * Destroy this encoder. * This method should only be called if this encoder is in unsafe mode. * Any subsequent calls to any encode method will result in undefined behavior. */ Encoder.prototype.destroy = function() { if (this._unsafe) { libopus._free(this._state); } }; /** * Encodes an array of (interleaved) pcm samples. * One frame must be exatly 2.5, 5, 10, 20, 40 or 60ms. * * @param {Int16Array|Float32Array} pcm - Input samples * @returns {Buffer} The encoded output */ Encoder.prototype.encode = function(pcm) { var samples = pcm.length / this._channels; return this._withState(function(p_enc) { var encode; if (pcm instanceof Float32Array) { if (pcm.length * 4 > utils.p_pcm_len) { throw new Error('pcm array too large'); } libopus.HEAPF32.set(pcm, p_pcm >> 2); encode = libopus._opus_encode_float.bind(libopus); } else if (pcm instanceof Int16Array) { if (pcm.length * 2 > utils.p_pcm_len) { throw new Error('pcm array too large'); } libopus.HEAP16.set(pcm, p_pcm >> 1); encode = libopus._opus_encode.bind(libopus); } else { throw new TypeError('pcm must be Int16Array or Float32Array'); } var len = encode(p_enc, p_pcm, samples, p_data, utils.p_data_len); if (len < 0) { throw new Error(utils.stringifyError(len)); } return Buffer.from(libopus.HEAPU8.subarray(p_data, p_data + len)); }); }; /** * Creates a transform stream from this encoder. * Since the stream always receives a Buffer object, the actual sample * type has to be specified manually. * * @param [('Float32'|'Int16')] mode - Type of sample input * @returns {EncoderStream} */ Encoder.prototype.stream = function(mode) { return new EncoderStream(this, mode); }; function EncoderStream(encoder, mode) { Transform.call(this, {}); this._encoder = encoder; if (mode == 'Float32') { this._mode = Float32Array; } else if (mode == 'Int16') { this._mode = Int16Array; } else { throw new TypeError('mode cannot be ' + mode); } } util.inherits(EncoderStream, Transform); EncoderStream.prototype._transform = function(chunk, encoding, callback) { chunk = new this._mode(chunk.buffer, chunk.byteOffset, chunk.byteLength / this._mode.BYTES_PER_ELEMENT); var result; try { result = this._encoder.encode(chunk); } catch (err) { return callback(err); } callback(null, result); }; Encoder.Application = Application; module.exports = Encoder;