UNPKG

opus-decoder

Version:
240 lines (199 loc) 6.59 kB
import { WASMAudioDecoderCommon } from "@wasm-audio-decoders/common"; import EmscriptenWASM from "./EmscriptenWasm.js"; export default function OpusDecoder(options = {}) { // static properties if (!OpusDecoder.errors) { // prettier-ignore Object.defineProperties(OpusDecoder, { errors: { value: new Map([ [-1, "OPUS_BAD_ARG: One or more invalid/out of range arguments"], [-2, "OPUS_BUFFER_TOO_SMALL: Not enough bytes allocated in the buffer"], [-3, "OPUS_INTERNAL_ERROR: An internal error was detected"], [-4, "OPUS_INVALID_PACKET: The compressed data passed is corrupted"], [-5, "OPUS_UNIMPLEMENTED: Invalid/unsupported request number"], [-6, "OPUS_INVALID_STATE: An encoder or decoder structure is invalid or already freed"], [-7, "OPUS_ALLOC_FAIL: Memory allocation has failed"], ]), }, }); } // injects dependencies when running as a web worker // async this._init = () => new this._WASMAudioDecoderCommon(this) .instantiate(this._EmscriptenWASM, this._module) .then((common) => { this._common = common; this._inputBytes = 0; this._outputSamples = 0; this._frameNumber = 0; this._input = this._common.allocateTypedArray( this._inputSize, Uint8Array, ); this._output = this._common.allocateTypedArray( this._outputChannels * this._outputChannelSize, Float32Array, ); const mapping = this._common.allocateTypedArray( this._channels, Uint8Array, ); mapping.buf.set(this._channelMappingTable); this._decoder = this._common.wasm.opus_frame_decoder_create( this._sampleRate, this._channels, this._streamCount, this._coupledStreamCount, mapping.ptr, this._preSkip, this._forceStereo, ); }); Object.defineProperty(this, "ready", { enumerable: true, get: () => this._ready, }); // async this.reset = () => { this.free(); return this._init(); }; this.free = () => { this._common.free(); this._common.wasm.opus_frame_decoder_destroy(this._decoder); this._common.wasm.free(this._decoder); }; this._decode = (opusFrame) => { if (!(opusFrame instanceof Uint8Array)) throw Error( "Data to decode must be Uint8Array. Instead got " + typeof opusFrame, ); this._input.buf.set(opusFrame); let samplesDecoded = this._common.wasm.opus_frame_decode_float_deinterleaved( this._decoder, this._input.ptr, opusFrame.length, this._output.ptr, ); let error; if (samplesDecoded < 0) { error = "libopus " + samplesDecoded + " " + (OpusDecoder.errors.get(samplesDecoded) || "Unknown Error"); console.error(error); samplesDecoded = 0; } return { outputBuffer: this._common.getOutputChannels( this._output.buf, this._outputChannels, samplesDecoded, ), samplesDecoded: samplesDecoded, error: error, }; }; this.decodeFrame = (opusFrame) => { let errors = []; const decoded = this._decode(opusFrame); if (decoded.error) this._common.addError( errors, decoded.error, opusFrame.length, this._frameNumber, this._inputBytes, this._outputSamples, ); this._frameNumber++; this._inputBytes += opusFrame.length; this._outputSamples += decoded.samplesDecoded; return this._WASMAudioDecoderCommon.getDecodedAudioMultiChannel( errors, [decoded.outputBuffer], this._outputChannels, decoded.samplesDecoded, this._sampleRate, ); }; this.decodeFrames = (opusFrames) => { let outputBuffers = [], errors = [], samplesDecoded = 0, i = 0; while (i < opusFrames.length) { const opusFrame = opusFrames[i++]; const decoded = this._decode(opusFrame); outputBuffers.push(decoded.outputBuffer); samplesDecoded += decoded.samplesDecoded; if (decoded.error) this._common.addError( errors, decoded.error, opusFrame.length, this._frameNumber, this._inputBytes, this._outputSamples, ); this._frameNumber++; this._inputBytes += opusFrame.length; this._outputSamples += decoded.samplesDecoded; } return this._WASMAudioDecoderCommon.getDecodedAudioMultiChannel( errors, outputBuffers, this._outputChannels, samplesDecoded, this._sampleRate, ); }; // injects dependencies when running as a web worker this._isWebWorker = OpusDecoder.isWebWorker; this._WASMAudioDecoderCommon = OpusDecoder.WASMAudioDecoderCommon || WASMAudioDecoderCommon; this._EmscriptenWASM = OpusDecoder.EmscriptenWASM || EmscriptenWASM; this._module = OpusDecoder.module; const MAX_FORCE_STEREO_CHANNELS = 8; const isNumber = (param) => typeof param === "number"; const sampleRate = options.sampleRate; const channels = options.channels; const streamCount = options.streamCount; const coupledStreamCount = options.coupledStreamCount; const channelMappingTable = options.channelMappingTable; const preSkip = options.preSkip; const forceStereo = options.forceStereo ? 1 : 0; // channel mapping family >= 1 if ( channels > 2 && (!isNumber(streamCount) || !isNumber(coupledStreamCount) || !Array.isArray(channelMappingTable)) ) { throw new Error("Invalid Opus Decoder Options for multichannel decoding."); } // libopus sample rate this._sampleRate = [8e3, 12e3, 16e3, 24e3, 48e3].includes(sampleRate) ? sampleRate : 48000; // channel mapping family 0 this._channels = isNumber(channels) ? channels : 2; this._streamCount = isNumber(streamCount) ? streamCount : 1; this._coupledStreamCount = isNumber(coupledStreamCount) ? coupledStreamCount : this._channels - 1; this._channelMappingTable = channelMappingTable || (this._channels === 2 ? [0, 1] : [0]); this._preSkip = preSkip || 0; this._forceStereo = channels <= MAX_FORCE_STEREO_CHANNELS && channels != 2 ? forceStereo : 0; this._inputSize = 32000 * 0.12 * this._channels; // 256kbs per channel this._outputChannelSize = 120 * 48; this._outputChannels = this._forceStereo ? 2 : this._channels; this._ready = this._init(); return this; }