UNPKG

libopusutils

Version:

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

246 lines (223 loc) 7.76 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; var e = function(msg) { return new Error(msg); }; var p_pcm = utils.p_pcm; var p_data = utils.p_data; /** * Decoder for opus streams. * * @param {object} [opts={}] - Options for the decoder * @param {(8000|12000|16000|24000|48000)} [opts.rate=48000] - Sampling rate of output signal (Hz) * @param {number} [opts.channels=1] - Number of (interleaved) channels * @param {boolean} [opts.unsafe=false] - Mark this decoder as unsafe.<br> * Decoder in unsafe mode generally operate faster.<br> * Warning: {@link #destroy()} MUST be called on an unsafe decoder before * it is garbage collected. Otherwise it will leak memory. * @constructor */ function Decoder(opts) { // Allow use without new if (!(this instanceof Decoder)) return new Decoder(opts); opts = extend({ rate: 48000, channels: 1, unsafe: false }, opts); if (opts.channels < 1 || opts.channels > 2) { throw e("channels must be either 1 or 2"); } if ([8000, 12000, 16000, 24000, 48000].indexOf(opts.rate) == -1) { throw e("rate can only be 8k, 12k, 16k, 24k or 48k"); } this._rate = opts.rate; this._channels = opts.channels; this._unsafe = opts.unsafe; // Allocate space for the decoder state var size = libopus._opus_decoder_get_size(this._channels); var dec = libopus._malloc(size); // Initialize the decoder var ret = libopus._opus_decoder_init(dec, this._rate, this._channels); if (ret !== 0) { // Free allocated space and throw error libopus._free(dec); throw e(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 = dec; } else { this._state = libopus.HEAPU8.slice(dec, dec + size); libopus._free(dec); } } /** * Calls the specified function with the state loaded into memory. * * @param func - The function to be called * @returns The return value of func */ Decoder.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 decoder. * This method should only be called if this decoder is in unsafe mode. * Any subsequent calls to any encode method will result in undefined behavior. */ Decoder.prototype.destroy = function() { if (this._unsafe) { libopus._free(this._state); } }; /** * Decodes an opus packet and returns it as an Int16Array. * Packets have to be decoded in the same order they were encoded in and lost * packets must be indicated by passing the amount of lost samples as the data. * If more than 120ms of data are lost, calls to this method have to be split * in batches of at most 120ms. If a falsy value is passed in, the amount of * lost samples is estimated with the last packet. * * @param {Buffer|number} data - Encoded input data or number of lost samples * @returns {Int16Array} The decoded output */ Decoder.prototype.decodeInt16 = function(data) { return new Int16Array(this._decode(data, 2, libopus._opus_decode)); }; /** * Decodes an opus packet and returns it as an Float32Array. * Packets have to be decoded in the same order they were encoded in and lost * packets must be indicated by passing the amount of lost samples as the data. * If more than 120ms of data are lost, calls to this method have to be split * in batches of at most 120ms. If a falsy value is passed in, the amount of * lost samples is estimated with the last packet. * * @param {Buffer|number} data - Encoded input data or number of lost samples * @returns {Float32Array} The decoded output */ Decoder.prototype.decodeFloat32 = function(data) { return new Float32Array(this._decode(data, 4, libopus._opus_decode_float)); }; /** * Decode the input data and leave result on HEAP. * * @param {Buffer|number} data - Encoded input data * @param {number} bps - Bytes per sample * @param {function} doDecode - Opus decode function * @returns ArrayBuffer of decoded data */ Decoder.prototype._decode = function(data, bps, doDecode) { var self = this; return this._withState(function(p_dec) { data = data || self._getLastPacketDuration(p_dec); var ret; if (typeof data === 'number') { if (data * bps > utils.p_data_len) throw e('too much lost data'); // Signal packet loss ret = doDecode(p_dec, 0, 0, p_pcm, data, 0); } else if (data instanceof Buffer) { if (data.length > utils.p_data_len) throw e('data array too large'); // Decode input data libopus.HEAPU8.set(data, p_data); var maxFrameSize = utils.p_pcm_len / self._channels / bps; ret = doDecode(p_dec, p_data, data.length, p_pcm, maxFrameSize, 0); } else { // Invalid input data throw new TypeError('data must be number, Buffer or null'); } // Handle result if (ret < 0) { throw e(utils.stringifyError(ret)); } return libopus.HEAPU8.slice(p_pcm, p_pcm + ret * bps).buffer; }); }; /** * Get the duration of the last decoded or concealed packet. * * @returns {number} - Duration in samples */ Decoder.prototype.getLastPacketDuration = function() { return this._withState(this._getLastPacketDuration.bind(this)); }; Decoder.prototype._getLastPacketDuration = function(p_dec) { var p_res = libopus._malloc(4); var pp_res = libopus._malloc(4); try { libopus.HEAPU32[pp_res >> 2] = p_res; var err = libopus._opus_decoder_ctl(p_dec, 4039, pp_res); if (err) { throw e(utils.stringifyError(err)); } return libopus.HEAP32[p_res >> 2]; } finally { libopus._free(pp_res); libopus._free(p_res); } }; /** * Creates a transform stream from this decoder. * Lost packets should be indicated by an empty buffer. The length * of the lost packets will be estimated with the length of the last packet. * * @param [('Float32'|'Int16')] mode - Type of sample output * @returns {DecoderStream} */ Decoder.prototype.stream = function(mode) { return new DecoderStream(this, mode); }; function DecoderStream(decoder, mode) { Transform.call(this, {}); if (mode == 'Float32') { this._decode = decoder.decodeFloat32.bind(decoder); } else if (mode == 'Int16') { this._decode = decoder.decodeInt16.bind(decoder); } else { throw new TypeError('mode cannot be ' + mode); } } util.inherits(DecoderStream, Transform); DecoderStream.prototype._transform = function(chunk, encoding, callback) { var result; try { var array = this._decode(chunk); result = Buffer.from(array.buffer, array.byteOffset, array.byteLength); } catch (err) { return callback(err); } callback(null, result); }; Decoder.getNumberOfSamples = function(data, sampleRate) { if (!(data instanceof Buffer)) { // Invalid input data throw new TypeError('data must be a Buffer'); } if (data.length > utils.p_data_len) throw e('data array too large'); // Parse input data libopus.HEAPU8.set(data, p_data); var ret = libopus._opus_packet_get_nb_samples(p_data, data.length, sampleRate); // Handle result if (ret < 0) { throw e(utils.stringifyError(ret)); } return ret; }; module.exports = Decoder;