├── .gitignore ├── .npmignore ├── .gitmodules ├── package.json ├── LICENSE ├── Makefile ├── README.md ├── index.d.ts ├── src └── opusscript_encoder.cpp └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | test 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .git* 3 | Makefile 4 | opus-native 5 | src 6 | test 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "opus-native"] 2 | path = opus-native 3 | url = https://github.com/xiph/opus.git 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opusscript", 3 | "version": "0.1.1", 4 | "description": "JS bindings for libopus 1.4, ported with emscripten", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/abalabahaha/opusscript.git" 10 | }, 11 | "keywords": [ 12 | "libopus", 13 | "encoder", 14 | "emscripten" 15 | ], 16 | "author": "abalabahaha", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/abalabahaha/opusscript/issues" 20 | }, 21 | "homepage": "https://github.com/abalabahaha/opusscript#readme", 22 | "browser": { 23 | "fs": false, 24 | "path": false 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2021 abalabahaha 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | OPUS_NATIVE_DIR=./opus-native 2 | 3 | EMCC_OPTS=-Wall -O3 --llvm-lto 3 -flto --closure 1 -s ALLOW_MEMORY_GROWTH=1 --memory-init-file 0 -s NO_FILESYSTEM=1 -s EXPORTED_RUNTIME_METHODS="['setValue', 'getValue']" -s EXPORTED_FUNCTIONS="['_malloc', '_opus_strerror', '_free']" -s MODULARIZE=1 -s NODEJS_CATCH_EXIT=0 -s NODEJS_CATCH_REJECTION=0 4 | 5 | EMCC_NASM_OPTS=-s WASM=0 -s WASM_ASYNC_COMPILATION=0 6 | EMCC_WASM_OPTS=-s WASM=1 -s WASM_ASYNC_COMPILATION=0 -s WASM_BIGINT 7 | 8 | all: init compile 9 | autogen: 10 | cd $(OPUS_NATIVE_DIR); \ 11 | ./autogen.sh 12 | configure: 13 | cd $(OPUS_NATIVE_DIR); \ 14 | emconfigure ./configure --disable-extra-programs --disable-doc --disable-intrinsics --disable-stack-protector 15 | bind: 16 | cd $(OPUS_NATIVE_DIR); \ 17 | emmake make; \ 18 | rm -f a.wasm 19 | init: autogen configure bind 20 | compile: 21 | rm -rf ./build; \ 22 | mkdir -p ./build; \ 23 | em++ ${EMCC_OPTS} ${EMCC_NASM_OPTS} --bind -o build/opusscript_native_nasm.js src/opusscript_encoder.cpp ${OPUS_NATIVE_DIR}/.libs/libopus.a; \ 24 | em++ ${EMCC_OPTS} ${EMCC_WASM_OPTS} --bind -o build/opusscript_native_wasm.js src/opusscript_encoder.cpp ${OPUS_NATIVE_DIR}/.libs/libopus.a; \ 25 | cp -f opus-native/COPYING build/COPYING.libopus; 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpusScript 2 | 3 | JS bindings for libopus 1.4, ported with Emscripten. 4 | 5 | ---- 6 | 7 | ## Usage 8 | 9 | ```js 10 | var OpusScript = require("opusscript"); 11 | 12 | // 48kHz sampling rate, 20ms frame duration, stereo audio (2 channels) 13 | var samplingRate = 48000; 14 | var frameDuration = 20; 15 | var channels = 2; 16 | 17 | // Optimize encoding for audio. Available applications are VOIP, AUDIO, and RESTRICTED_LOWDELAY 18 | var encoder = new OpusScript(samplingRate, channels, OpusScript.Application.AUDIO); 19 | 20 | var frameSize = samplingRate * frameDuration / 1000; 21 | 22 | // Get PCM data from somewhere and encode it into opus 23 | var pcmData = new Buffer(pcmSource); 24 | var encodedPacket = encoder.encode(pcmData, frameSize); 25 | 26 | // Decode the opus packet back into PCM 27 | var decodedPacket = encoder.decode(encodedPacket); 28 | 29 | // Delete the encoder when finished with it (Emscripten does not automatically call C++ object destructors) 30 | encoder.delete(); 31 | ``` 32 | 33 | ## Note: WASM 34 | 35 | If your environment doesn't support WASM, you can try the JS-only module. Do note that the JS-only version barely has optimizations due to compiler/toolchain limitations, and should only be used as a last resort. 36 | 37 | ```js 38 | var encoder = new OpusScript(samplingRate, channels, OpusScript.Application.AUDIO, { 39 | wasm: false 40 | }); 41 | ``` 42 | 43 | ## Note: TypeScript 44 | 45 | Since this module wasn't written for TypeScript, you need to use `import = require` syntax. 46 | 47 | ```ts 48 | // Import using: 49 | import OpusScript = require("opusscript"); 50 | 51 | // and NOT: 52 | import OpusScript from "opusscript"; 53 | ``` 54 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'opusscript' { 2 | /** 3 | * Opus application type 4 | */ 5 | enum OpusApplication { 6 | /** 7 | * Voice Over IP 8 | */ 9 | VOIP = 2048, 10 | /** 11 | * Audio 12 | */ 13 | AUDIO = 2049, 14 | /** 15 | * Restricted Low-Delay 16 | */ 17 | RESTRICTED_LOWDELAY = 2051 18 | } 19 | enum OpusError { 20 | "OK" = 0, 21 | "Bad argument" = -1, 22 | "Buffer too small" = -2, 23 | "Internal error" = -3, 24 | "Invalid packet" = -4, 25 | "Unimplemented" = -5, 26 | "Invalid state" = -6, 27 | "Memory allocation fail" = -7 28 | } 29 | /** 30 | * Valid audio sampling rates 31 | */ 32 | type VALID_SAMPLING_RATES = 8000 | 12000 | 16000 | 24000 | 48000; 33 | /** 34 | * Maximum bytes in a frame 35 | */ 36 | type MAX_FRAME_SIZE = 2880; 37 | /** 38 | * Maximum bytes in a packet 39 | */ 40 | type MAX_PACKET_SIZE = 3828; 41 | /** 42 | * Constructor options for OpusScript 43 | */ 44 | interface OpusScriptOptions { 45 | /** 46 | * Whether or not to use the WASM-compiled version of OpusScript. This is true by default. 47 | */ 48 | wasm?: boolean; 49 | } 50 | class OpusScript { 51 | /** 52 | * Different Opus application types 53 | */ 54 | static Application: typeof OpusApplication; 55 | /** 56 | * Opus Error codes 57 | */ 58 | static Error: typeof OpusError; 59 | /** 60 | * Array of sampling rates that Opus can use 61 | */ 62 | static VALID_SAMPLING_RATES: [8000, 12000, 16000, 24000, 48000]; 63 | /** 64 | * The maximum size (in bytes) to send in a packet 65 | */ 66 | static MAX_PACKET_SIZE: MAX_PACKET_SIZE; 67 | 68 | /** 69 | * OpusScript options being used 70 | */ 71 | options: OpusScriptOptions; 72 | 73 | /** 74 | * Create a new Opus en/decoder 75 | */ 76 | constructor(samplingRate: VALID_SAMPLING_RATES, channels?: number, application?: OpusApplication, options?: OpusScriptOptions); 77 | /** 78 | * Encode a buffer into Opus 79 | */ 80 | encode(buffer: Buffer, frameSize: number): Buffer; 81 | /** 82 | * Decode an opus buffer 83 | */ 84 | decode(buffer: Buffer): Buffer; 85 | /** 86 | * Set the encoder bitrate 87 | */ 88 | setBitrate(bitrate: number): void; 89 | /** 90 | * Encoder/decoder parameters 91 | */ 92 | encoderCTL(ctl: number, arg: number): void; 93 | decoderCTL(ctl: number, arg: number): void; 94 | /** 95 | * Delete the opus object 96 | */ 97 | delete(): void; 98 | } 99 | export = OpusScript; 100 | } 101 | -------------------------------------------------------------------------------- /src/opusscript_encoder.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../opus-native/include/opus.h" 4 | 5 | #define APPLICATION OPUS_APPLICATION_AUDIO 6 | #define MAX_PACKET_SIZE 1276 * 3 7 | #define MAX_FRAME_SIZE 960 * 6 8 | 9 | using namespace emscripten; 10 | 11 | class OpusScriptHandler { 12 | private: 13 | int channels; 14 | 15 | OpusEncoder* encoder; 16 | OpusDecoder* decoder; 17 | 18 | opus_int16* out_pcm; 19 | public: 20 | OpusScriptHandler(opus_int32 sampling_rate, int channels, int application): 21 | channels(channels) { 22 | 23 | out_pcm = new opus_int16[MAX_FRAME_SIZE * channels * 2]; 24 | 25 | int encoder_error; 26 | encoder = opus_encoder_create(sampling_rate, channels, application, &encoder_error); 27 | if(encoder_error < 0) { 28 | throw encoder_error; 29 | } 30 | 31 | int decoder_error; 32 | decoder = opus_decoder_create(sampling_rate, channels, &decoder_error); 33 | if(decoder_error < 0) { 34 | throw decoder_error; 35 | } 36 | } 37 | 38 | ~OpusScriptHandler() { 39 | opus_encoder_destroy(encoder); 40 | opus_decoder_destroy(decoder); 41 | delete out_pcm; 42 | } 43 | 44 | int _encode(int input_buffer, int bytes, int output_buffer, int frame_size) { 45 | opus_int16* input = reinterpret_cast(input_buffer); 46 | unsigned char* output = reinterpret_cast(output_buffer); 47 | 48 | for(int i = 0; i < bytes; i++) { 49 | input[i] = input[2 * i + 1] << 8 | input[2 * i]; 50 | } 51 | 52 | return opus_encode(encoder, input, frame_size, output, MAX_PACKET_SIZE); 53 | } 54 | 55 | int _decode(int input_buffer, int bytes, int output_buffer) { 56 | unsigned char* input = reinterpret_cast(input_buffer); 57 | short* pcm = reinterpret_cast(output_buffer); 58 | 59 | int len = opus_decode(decoder, input, bytes, out_pcm, MAX_FRAME_SIZE, 0); 60 | 61 | for(int i = 0; i < len * channels; i++) { 62 | pcm[2 * i] = out_pcm[i] & 0xFF; 63 | pcm[2 * i + 1] = (out_pcm[i] >> 8) & 0xFF; 64 | } 65 | 66 | return len; 67 | } 68 | 69 | int _encoder_ctl(int ctl, int arg) { 70 | return opus_encoder_ctl(encoder, ctl, arg); 71 | } 72 | 73 | int _decoder_ctl(int ctl, int arg) { 74 | return opus_decoder_ctl(decoder, ctl, arg); 75 | } 76 | 77 | static void destroy_handler(OpusScriptHandler *handler) { 78 | delete handler; 79 | } 80 | }; 81 | 82 | EMSCRIPTEN_BINDINGS(OpusScriptHandler) { 83 | class_("OpusScriptHandler") 84 | .constructor() 85 | .function("_encode", &OpusScriptHandler::_encode) 86 | .function("_decode", &OpusScriptHandler::_decode) 87 | .function("_encoder_ctl", &OpusScriptHandler::_encoder_ctl) 88 | .function("_decoder_ctl", &OpusScriptHandler::_decoder_ctl) 89 | .class_function("destroy_handler", &OpusScriptHandler::destroy_handler, allow_raw_pointers()); 90 | } 91 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | let opusscript_native_nasm = null; 4 | let opusscript_native_wasm = null; 5 | 6 | var OpusApplication = { 7 | VOIP: 2048, 8 | AUDIO: 2049, 9 | RESTRICTED_LOWDELAY: 2051 10 | }; 11 | var OpusError = { 12 | "0": "OK", 13 | "-1": "Bad argument", 14 | "-2": "Buffer too small", 15 | "-3": "Internal error", 16 | "-4": "Invalid packet", 17 | "-5": "Unimplemented", 18 | "-6": "Invalid state", 19 | "-7": "Memory allocation fail" 20 | }; 21 | var VALID_SAMPLING_RATES = [8000, 12000, 16000, 24000, 48000]; 22 | var MAX_FRAME_SIZE = 48000 * 60 / 1000; 23 | var MAX_PACKET_SIZE = 1276 * 3; 24 | 25 | function OpusScript(samplingRate, channels, application, options) { 26 | if(!~VALID_SAMPLING_RATES.indexOf(samplingRate)) { 27 | throw new RangeError(`${samplingRate} is an invalid sampling rate.`); 28 | } 29 | this.options = Object.assign({ 30 | wasm: true 31 | }, options); 32 | 33 | this.samplingRate = samplingRate; 34 | this.channels = channels || 1; 35 | this.application = application || OpusApplication.AUDIO; 36 | 37 | let opusscript_native = null; 38 | if(this.options.wasm) { 39 | if(!opusscript_native_wasm) { 40 | opusscript_native_wasm = require("./build/opusscript_native_wasm.js")(); 41 | } 42 | opusscript_native = opusscript_native_wasm; 43 | } else { 44 | if(!opusscript_native_nasm) { 45 | opusscript_native_nasm = require("./build/opusscript_native_nasm.js")(); 46 | } 47 | opusscript_native = opusscript_native_nasm; 48 | } 49 | this.handler = new opusscript_native.OpusScriptHandler(this.samplingRate, this.channels, this.application); 50 | 51 | this.inPCMLength = MAX_FRAME_SIZE * this.channels * 2; 52 | this.inPCMPointer = opusscript_native._malloc(this.inPCMLength); 53 | this.inPCM = opusscript_native.HEAPU16.subarray(this.inPCMPointer, this.inPCMPointer + this.inPCMLength); 54 | 55 | this.inOpusPointer = opusscript_native._malloc(MAX_PACKET_SIZE); 56 | this.inOpus = opusscript_native.HEAPU8.subarray(this.inOpusPointer, this.inOpusPointer + MAX_PACKET_SIZE); 57 | 58 | this.outOpusPointer = opusscript_native._malloc(MAX_PACKET_SIZE); 59 | this.outOpus = opusscript_native.HEAPU8.subarray(this.outOpusPointer, this.outOpusPointer + MAX_PACKET_SIZE); 60 | 61 | this.outPCMLength = MAX_FRAME_SIZE * this.channels * 2; 62 | this.outPCMPointer = opusscript_native._malloc(this.outPCMLength); 63 | this.outPCM = opusscript_native.HEAPU16.subarray(this.outPCMPointer, this.outPCMPointer + this.outPCMLength); 64 | }; 65 | 66 | OpusScript.prototype.encode = function encode(buffer, frameSize) { 67 | this.inPCM.set(buffer); 68 | 69 | var len = this.handler._encode(this.inPCM.byteOffset, buffer.length, this.outOpusPointer, frameSize); 70 | if(len < 0) { 71 | throw new Error("Encode error: " + OpusError["" + len]); 72 | } 73 | 74 | return Buffer.from(this.outOpus.subarray(0, len)); 75 | }; 76 | 77 | OpusScript.prototype.decode = function decode(buffer) { 78 | this.inOpus.set(buffer); 79 | 80 | var len = this.handler._decode(this.inOpusPointer, buffer.length, this.outPCM.byteOffset); 81 | if(len < 0) { 82 | throw new Error("Decode error: " + OpusError["" + len]); 83 | } 84 | 85 | return Buffer.from(this.outPCM.subarray(0, len * this.channels * 2)); 86 | }; 87 | 88 | OpusScript.prototype.encoderCTL = function encoderCTL(ctl, arg) { 89 | var len = this.handler._encoder_ctl(ctl, arg); 90 | if(len < 0) { 91 | throw new Error("Encoder CTL error: " + OpusError["" + len]); 92 | } 93 | }; 94 | 95 | OpusScript.prototype.setBitrate = function setBitrate(bitrate) { 96 | this.encoderCTL(4002, bitrate); 97 | }; 98 | 99 | OpusScript.prototype.decoderCTL = function decoderCTL(ctl, arg) { 100 | var len = this.handler._decoder_ctl(ctl, arg); 101 | if(len < 0) { 102 | throw new Error("Decoder CTL error: " + OpusError["" + len]); 103 | } 104 | }; 105 | 106 | OpusScript.prototype.delete = function del() { 107 | let opusscript_native = this.options.wasm ? opusscript_native_wasm : opusscript_native_nasm; 108 | opusscript_native.OpusScriptHandler.destroy_handler(this.handler); 109 | opusscript_native._free(this.inPCMPointer); 110 | opusscript_native._free(this.inOpusPointer); 111 | opusscript_native._free(this.outOpusPointer); 112 | opusscript_native._free(this.outPCMPointer); 113 | }; 114 | 115 | OpusScript.Application = OpusApplication; 116 | OpusScript.Error = OpusError; 117 | OpusScript.VALID_SAMPLING_RATES = VALID_SAMPLING_RATES; 118 | OpusScript.MAX_PACKET_SIZE = MAX_PACKET_SIZE; 119 | 120 | module.exports = OpusScript; 121 | --------------------------------------------------------------------------------