npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

libopus-node

v1.3.1

Published

libopus bindings for Node

Readme

libopus-node

CI npm version npm downloads

libopus-node is a thin, modern wrapper around the official libopus 1.5.2 reference implementation.

It exposes a small JavaScript API backed by N-API native bindings so you can encode or decode Opus frames directly in Node.js without spawning external processes or shipping a separate binary.

Key traits:

  • ESM-only: the package uses native ES modules; you must import it (no require()).
  • Prebuilt binaries: GitHub Actions publishes binaries for Linux, macOS, Windows, and OpenBSD (including Linux musl), so installation never requires a compiler.
  • Node 24+: built against current Node headers and includes prebuilds for the active LTS line.
  • Minimal surface: exposes an OpusEncoder class with encode/decode helpers plus passthrough CTL access—ideal for bots, streaming services, or testing harnesses that want full control over buffers.

Prebuilt binaries are provided for the platforms listed below; you do not need a system libopus installed.

This package is prebuild-only. An installation target without a matching prebuild is unsupported.


Installation

pnpm add libopus-node
# or
npm install libopus-node

Usage

[!NOTE] This package is ESM-only. Use import/export syntax or dynamic import() - require('libopus-node') is not supported.

import { OpusEncoder } from "libopus-node";

const encoder = new OpusEncoder(48000, 2);

const encoded = encoder.encode(pcm);
const decoded = encoder.decode(encoded);
const decodedFloat = encoder.decodeFloat(encoded);

// 20 ms of interleaved stereo float32 PCM at 48 kHz.
const floatPcm = Buffer.from(new Float32Array(960 * 2).buffer);
const encodedFloat = encoder.encodeFloat(floatPcm);

API

new OpusEncoder(sampleRate: number, channels: number)

Create a new encoder/decoder instance.

  • sampleRate – one of the Opus sample rates, typically 48000. (Valid Opus sample rates are 8000, 12000, 16000, 24000, 48000.)

  • channels – number of channels:

    • 1 = mono
    • 2 = stereo

If the arguments are invalid (e.g. channels ≤ 0, sampleRate ≤ 0 or > 48000), the constructor throws.

Each OpusEncoder instance maintains its own internal encoder/decoder state and can be reused across many frames.


encoder.encode(pcm: Buffer): Buffer

Encode a single frame of PCM into an Opus packet.

Input (encode)

  • pcm – Node Buffer containing signed 16-bit PCM samples:

    • Interleaved by channel (LRLRLR… for stereo).
    • Little-endian.
  • The number of samples must be a multiple of channels.

  • Maximum frame size:

    • Up to 5760 samples per channel per call (≈120 ms at 48 kHz).

If the input length is invalid (e.g. not a multiple of channels, or too many samples), an error is thrown.

Output (encode)

  • Returns a Buffer containing a single Opus packet.
  • The buffer length is the exact size of the encoded packet.

Typical usage is to call encode on small frames, e.g. 20 ms of audio at 48 kHz:

// 20 ms @ 48 kHz = 960 samples per channel
const samplesPerFrame = 960;
const channels = 2;
const frameSamples = samplesPerFrame * channels;

// Int16 PCM -> Buffer
const int16 = new Int16Array(frameSamples);
// ... fill int16 with samples ...

const pcm = Buffer.from(int16.buffer);
const packet = encoder.encode(pcm);

encoder.encodeFloat(pcm: Buffer): Buffer

Encode a single frame of 32-bit floating-point PCM into an Opus packet.

Input (encodeFloat)

  • pcm – Node Buffer containing finite IEEE 754 float32 PCM samples:

    • Interleaved by channel (LRLRLR… for stereo).
    • Native little-endian byte order on the supported platforms.
    • Exactly four bytes per sample and a total byte length divisible by channels * 4.
  • Each call must contain exactly one valid Opus frame: 2.5, 5, 10, 20, 40, or 60 ms per channel. The buffer must not be empty.

The method safely accepts Buffer slices with unaligned byte offsets; it copies the PCM into aligned native storage before calling libopus.

Output (encodeFloat)

  • Returns a Buffer containing a single Opus packet.
  • The buffer length is the exact size of the encoded packet.

For example, encode 20 ms of mono float PCM at 16 kHz:

const samplesPerFrame = 320;
const pcm = Buffer.alloc(samplesPerFrame * Float32Array.BYTES_PER_ELEMENT);
for (let index = 0; index < samplesPerFrame; index++) {
  pcm.writeFloatLE(Math.sin((2 * Math.PI * index) / 40) * 0.5, index * 4);
}

const packet = encoder.encodeFloat(pcm);

encoder.decode(packet: Buffer): Buffer

Decode a single Opus packet into PCM.

Input (decode)

  • packet – Buffer containing a single Opus packet (as returned by encode, or received from elsewhere).

Output (decode)

  • Returns a Buffer containing signed 16-bit PCM:

    • Interleaved by channel.
    • Little-endian.
  • The number of samples produced depends on the packet and encoder configuration.

You can wrap the result in a Int16Array if you prefer:

const decoded = encoder.decode(packet);
const samples = new Int16Array(
  decoded.buffer,
  decoded.byteOffset,
  decoded.byteLength / Int16Array.BYTES_PER_ELEMENT,
);

// `samples.length` is frames * channels

Packet loss concealment and FEC

decode() also supports packet loss recovery. These modes require the exact number of samples per channel in the missing packet. The value must be a multiple of 2.5 ms at the decoder's sample rate (for example, 960 is 20 ms at 48 kHz) and cannot exceed 120 ms.

const frameSize = 960; // 20 ms per channel at 48 kHz

// Recover a lost packet immediately with packet loss concealment (PLC).
const concealed = encoder.decode(null, { frameSize });

// Or, when the next packet carries in-band FEC, recover the preceding packet.
const recovered = encoder.decode(nextPacket, { fec: true, frameSize });

// Decode that next packet normally after its FEC data has been consumed.
const current = encoder.decode(nextPacket);

FEC must be enabled by the sender. If the packet has no FEC data, requesting fec: true falls back to PLC, as defined by libopus.


encoder.decodeFloat(packet: Buffer): Buffer

Decode a single Opus packet to 32-bit floating-point PCM.

Input (decodeFloat)

  • packet – Buffer containing a single Opus packet (as returned by encode, or received from elsewhere).

Output (decodeFloat)

  • Returns a Buffer containing IEEE 754 float32 PCM samples:

    • Interleaved by channel.
    • Native little-endian byte order on the supported platforms.
    • Normalized approximately to the range -1.0 to 1.0.
  • The buffer contains four bytes per sample. Wrap it in a Float32Array to work with the samples directly:

const decoded = encoder.decodeFloat(packet);
const samples = new Float32Array(
  decoded.buffer,
  decoded.byteOffset,
  decoded.byteLength / Float32Array.BYTES_PER_ELEMENT,
);

decode() continues to return signed 16-bit PCM. Use decodeFloat() when the following audio pipeline expects floating-point samples and you want to avoid integer conversion in JavaScript.


encoder.setBitrate(bitrate: number): number

Set the target bitrate for the encoder.

  • bitrate – bits per second (e.g. 64000, 96000, 128000).

Returns the libopus status code (0 on success). On failure, throws an error.

The actual effective bitrate also depends on the application mode and audio content, as per libopus.

encoder.getBitrate(): number

Get the current encoder bitrate in bits per second.

  • Returns a number (e.g. 64000 for 64 kbps).
  • On error, throws.

encoder.applyEncoderCTL(ctl: number, value: number): number

Low-level access to libopus encoder CTL (control) options.

  • ctl – numeric Opus encoder CTL constant.
  • value – integer value for that CTL.

Returns the libopus status code (0 on success). On error, throws.

⚠ Note: This helper is intended for advanced users. Only CTLs that take a single integer argument are supported. CTLs that expect pointers or no argument are not supported through this method.

Typical uses include options like complexity, VBR mode, etc. Refer to the libopus documentation for valid CTL constants and values.


encoder.applyDecoderCTL(ctl: number, value: number): number

Low-level access to libopus decoder CTL options.

  • Same semantics as applyEncoderCTL, but operates on the decoder side.

Error handling

All methods throw JavaScript Error instances when something goes wrong, for example:

  • Invalid arguments (wrong buffer size, wrong types).
  • libopus errors (invalid packet, internal error, etc).

You should wrap encode/decode in try/catch if working with untrusted or external data:

try {
  const packet = encoder.encode(pcm);
  const decoded = encoder.decode(packet);
} catch (err) {
  console.error("Opus error:", err);
}

Platform support

⚠ Node.js 24.0.0 or newer is required.

Prebuilt binaries are provided for:

| Operating System | Architectures | | ---------------- | ------------- | | Linux (glibc) | x64, arm64 | | Linux (musl) | x64, arm64 | | macOS | x64, arm64 | | Windows | x64, arm64 | | OpenBSD | x64 |

If a prebuilt binary is not available for your platform, the package cannot be used on that platform.


Relationship to discordjs/opus

This project is a complete rewrite of discordjs/opus with only the libopus/config and libopus/binding.gyp being shared:

  • ESM-first: the original package targets CommonJS, while this fork is ESM-only with modern TypeScript definitions emitted from the source.
  • Updated toolchain: build scripts, GitHub workflows, and packaging have been rewritten to supply musl binaries, automated npm publishing, and Node 24+ headers.
  • Rewritten native wrapper: the C++ addon (opus_encoder.cpp) was rewritten for RAII-style memory management, stricter parameter validation, lazy encoder/decoder init, and clearer error propagation (mapping libopus error codes to JS exceptions). The encode/decode paths now guard frame sizes, buffer alignment, and expose CTL helpers with bit-rate accessors, which keeps behavior deterministic across platforms.
  • Native surface tweaks: additional libopus APIs (packet extensions, CTL helpers) and better error reporting have been integrated into the binding layer.
  • Repository layout: dependencies, config headers, and prebuild infrastructure were heavily edited to streamline maintenance and make cross-platform builds reproducible.

If you need CommonJS compatibility or want a drop-in replacement for older Discord bot stacks, the upstream discordjs/opus may still be a better fit. For modern Node environments that prefer ESM and automated prebuilds, libopus-node is the recommended path.