cjopus
Version:
Emscripten bindings for libopus. Encode / Decode your audio to opus with pure JS.
138 lines (109 loc) • 4.34 kB
JavaScript
var Module = require('./CJOpus.js');
var Opus = {};
Opus.APPLICATIONS = {
VOIP: 2048,
AUDIO: 2049,
RESTRICTED_LOWDELAY:2051
};
Opus.ERRORS = {
OK: 0,
BAD_ARG: -1,
BUFFER_TOO_SMALL: -2,
INTERNAL_ERROR: -3,
INVALID_PACKET: -4,
UNIMPLEMENTED: -5,
INVALID_STATE: -6,
ALLOC_FAIL: -7
};
Opus.SAMPLE_RATES = [8000, 12000, 16000, 24000, 48000];
Opus.CHANNELS = [1, 2];
Opus.OpusEncoder = OpusEncoder;
var APPLICATION_CODES = Object.keys(Opus.APPLICATIONS).map(function(a) { return Opus.APPLICATIONS[a]; });
var ERROR_CODES = Object.keys(Opus.ERRORS).map(function(e) { return Opus.ERRORS[e]; });
function OpusEncoder(sample_rate, channels, application) {
if (Opus.CHANNELS.indexOf(channels) < 0) channels = 2;
if (Opus.SAMPLE_RATES.indexOf(sample_rate) < 0) sample_rate = 48000;
if (APPLICATION_CODES.indexOf(application) < 0) application = Opus.APPLICATIONS.AUDIO;
Object.defineProperty(this, 'encoder', {
value: Module._create_encoder_and_decoder(sample_rate, channels, application)
});
var encoderError = Module._get_encoder_error(this.encoder);
var decoderError = Module._get_decoder_error(this.encoder);
if (encoderError || decoderError) {
throw new OpusError( encoderError || decoderError );
}
Object.defineProperties(this, {
channels: {value: channels},
//Is this correct?
encodeFrameSize: {value: sample_rate / 50},
// --Encoding--
pcmOffset: {value: Module._get_in_pcm_offset(this.encoder)},
encodedOffset: {value: Module._get_encoded_offset(this.encoder)},
// --Decoding--
opusOffset: {value: Module._get_in_opus_offset(this.encoder)},
decodedOffset: {value: Module._get_decoded_little_endian_offset(this.encoder)}
});
this.destroyed = false;
}
OpusEncoder.prototype.encode = function( PCM ) {
if ( this.destroyed ) {
throw new Error( "Attempting to encode using a destroyed decoder" );
}
if ( !isExpectedType(PCM) ) {
throw new TypeError( Object.prototype.toString.call(PCM) + " is not a valid type (Buffer, TypedArray, Array)");
}
Module.HEAPU8.set( PCM, this.pcmOffset );
var length = Module._encode(this.encoder, PCM.length, this.encodeFrameSize);
if (length < 0) throw new OpusError(length);
return Module.HEAPU8.slice( this.encodedOffset, this.encodedOffset + length );
}
OpusEncoder.prototype.decode = function( OPUS ) {
if ( this.destroyed ) {
throw new Error( "Attempting to decode using a destroyed decoder" );
}
if ( !isExpectedType(OPUS) ) {
throw new TypeError( Object.prototype.toString.call(OPUS) + " is not a valid type (Buffer, TypedArray, Array)");
}
Module.HEAPU8.set( OPUS, this.opusOffset );
var frameSize = Module._decode( this.encoder, OPUS.length );
if (frameSize < 0) throw new OpusError(frameSize);
return Module.HEAP16.slice( this.decodedOffset / 2, (this.decodedOffset /2) + (frameSize * 2 * this.channels) );
}
OpusEncoder.prototype.encodeUnsafe = function( PCM ) {
Module.HEAPU8.set( PCM, this.pcmOffset );
return Module.HEAPU8.subarray(
this.encodedOffset,
this.encodedOffset + Module._encode(
this.encoder,
PCM.length,
this.encodeFrameSize
)
);
}
OpusEncoder.prototype.decodeUnsafe = function( OPUS ) {
Module.HEAPU8.set( OPUS, this.opusOffset );
return Module.HEAP16.subarray(
this.decodedOffset / 2,
(this.decodedOffset / 2) +
(Module._decode(
this.encoder,
OPUS.length
) * 2 * this.channels)
);
}
OpusEncoder.prototype.destroy = function() {
if ( this.destroyed ) {
throw new Error( "Decoder already destroyed" );
}
Module._destroy_encoder(this.encoder);
this.destroyed = true;
}
function OpusError(code) {
var error = new Error(Object.keys(Opus.ERRORS)[ERROR_CODES.indexOf(code)]);
error.code = code;
return error;
}
function isExpectedType(t) {
return t instanceof Buffer || ArrayBuffer.isView(t) || Array.isArray(t);
}
module.exports = Opus;