@pojagi/opus-decoder-wasm
v0.0.1
Published
RFC 7845-compliant Ogg/Opus decoder for WebAssembly with multichannel support
Maintainers
Readme
@pojagi/opus-decoder-wasm
RFC 7845-compliant Ogg/Opus decoder compiled to WebAssembly.
⚠️ Beta Software: This package is in early development (v0.0.x). The API is stable but it has not been extensively tested in production. Please report any issues you encounter.
Overview
This package provides a complete Ogg/Opus audio decoder that runs in the browser via WebAssembly. It correctly implements the full RFC 7845 specification, including sample-accurate start and end trimming that many JavaScript decoders get wrong.
Features
- RFC 7845 Compliant: Proper pre-skip trimming at the start and granule-based end trimming for sample-accurate decoding
- Multichannel Support: Handles up to 255 channels using the Opus multistream decoder API
- Pure Rust/WASM: No native dependencies or toolchain required at runtime
- Self-Contained: Zero external module imports—works with any bundler (Vite, webpack, etc.)
- TypeScript: Full type definitions included
How It Works
The decoder is built entirely in Rust and compiled to WebAssembly:
- unopus: The reference libopus 1.3.1 implementation, mechanically transpiled from C to pure Rust using c2rust. This provides bit-exact compatibility with native libopus while requiring no C toolchain or native libraries.
- ogg: Pure Rust Ogg container demuxer for parsing the Ogg bitstream.
- wasm-bindgen: Generates JavaScript bindings for the WASM module.
Why unopus?
Most WASM Opus decoders either:
- Use Emscripten to compile libopus, which introduces dependencies on an
envmodule that bundlers can't resolve - Use pure JavaScript implementations that may have subtle differences from the reference
By using unopus (libopus transpiled to Rust), we get:
- Bit-exact decoding matching the reference C implementation
- No native toolchain required for building
- Self-contained WASM with no external imports
- ~187KB module size (gzipped: ~95KB)
Building
Requires:
- Rust with
wasm32-unknown-unknowntarget:rustup target add wasm32-unknown-unknown - wasm-pack:
cargo install wasm-pack
./build.shUsage
High-Level API
import { OggOpusDecoder } from '@pojagi/opus-decoder-wasm/wrapper';
const decoder = new OggOpusDecoder();
await decoder.ready;
const result = await decoder.decode(oggOpusData);
console.log(`Decoded ${result.samplesDecoded} samples at ${result.sampleRate}Hz`);
console.log(`Channels: ${result.channelData.length}`);Low-Level API
import { initialize, decode, getInfo } from '@pojagi/opus-decoder-wasm';
// Initialize WASM module (required once)
await initialize();
// Decode a complete Ogg/Opus file
const audio = await decode(oggOpusData);
// audio.channelData: Float32Array[] - one array per channel
// audio.sampleRate: number - always 48000 for Opus
// audio.numberOfChannels: number
// audio.length: number - samples per channel
// Get file metadata without decoding
const info = await getInfo(oggOpusData);
// info.channels, info.preSkip, info.finalGranulePosition, etc.API Reference
OggOpusDecoder
High-level decoder class with a familiar API.
class OggOpusDecoder {
ready: Promise<void>;
decode(data: Uint8Array): Promise<{
channelData: Float32Array[];
samplesDecoded: number;
sampleRate: number;
errors: never[];
}>;
reset(): Promise<void>;
free(): Promise<void>;
}DecodedAudio
interface DecodedAudio {
channelData: Float32Array[]; // One array per channel
sampleRate: number; // Always 48000
numberOfChannels: number;
length: number; // Samples per channel
}OggOpusInfo
interface OggOpusInfo {
channels: number;
preSkip: number;
inputSampleRate: number; // Original sample rate (informational)
outputGain: number; // Gain in dB
channelMappingFamily: number; // 0, 1, or 255
packetCount: number;
finalGranulePosition: bigint;
expectedOutputSamples: bigint;
}RFC 7845 Compliance
This decoder properly implements:
- Pre-skip trimming: Discards the encoder delay samples specified in the OpusHead
- End trimming: Uses the final page's granule position to discard padding samples at the end
- Channel mapping: Supports mapping families 0, 1, and 255 for proper multichannel decoding
These details are critical for gapless playback and accurate sample counts, especially when decoding audio segments for streaming applications.
License
MIT
