RustyOpus wraps pure-Rust Opus through Rustler.
- Ogg Opus blobs —
RustyOpus.reencode/2demuxes, re-encodes at a numeric bitrate withopus-rs, and remuxes via thin in-crate Ogg glue (ADR003). This is the path for real.ogg/audio/oggfiles. - Raw Opus packets and PCM —
encode/decode/transcodeand theEncoder/Decodermodules (viaopus-rs). No container on this path. - WebM/MP4 and ffmpeg are out of scope.
Data contract
- Ogg Opus is an RFC 7845 binary (starts with
OggS). - PCM is a binary of 32-bit little-endian IEEE-754
f32samples, interleaved for stereo. Sample count isbyte_size(pcm) / 4. - Opus packets are raw binaries, passed verbatim.
Ogg reencode (file-like)
{:ok, smaller} = RustyOpus.reencode(ogg_blob, bitrate: 20_000):bitrate is required (bits/s). Typical ladder: 8_000 … 32_000.
Speech-oriented defaults (FFmpeg analogues):
| Option | Default | FFmpeg |
|---|---|---|
:application | :voip | -application voip |
:complexity | 10 | -compression_level 10 |
:cbr | false (VBR) | -vbr on |
:frame_duration_ms | 20 | -frame_duration (FFmpeg often uses 60) |
frame_duration_ms may be 10 or 20. 40 and 60 are not supported by
opus-rs at 48 kHz on this path and return :invalid_settings.
Supported configuration (packet/PCM path)
| Parameter | Values |
|---|---|
| Sampling rate | 8000, 12000, 16000, 24000, 48000 Hz |
| Channels | 1 (mono) or 2 (stereo) |
| Application | :voip, :audio, :restricted_low_delay |
Frame sizes (samples per channel) are the standard Opus frame durations (e.g. 320 samples
at 16 kHz is a 20 ms frame). The PCM buffer passed to encode/3 must contain exactly
frame_size * channels samples.
Encoder
{:ok, encoder} =
RustyOpus.Encoder.new(
48_000,
2,
:audio,
bitrate: 128_000,
complexity: 9,
cbr: false,
fec: false,
packet_loss: 0
)
{:ok, packet} = RustyOpus.Encoder.encode(encoder, pcm, 960)
:ok = RustyOpus.Encoder.close(encoder)RustyOpus.Encoder.set/2 updates settings on a live encoder. Closing is idempotent;
calls after close return {:error, %RustyOpus.Error{reason: :closed}}.
Decoder
{:ok, decoder} = RustyOpus.Decoder.new(16_000, 1)
{:ok, pcm} = RustyOpus.Decoder.decode(decoder, packet, 320)A 1-byte (ToC-only) packet is treated as a lost/DTX frame and concealed with packet-loss concealment instead of erroring. Packets declaring a channel count different from the decoder are rejected with a stable tagged error.
Errors
Every failure is a %RustyOpus.Error{} with a stable :reason and a :message.
Panics inside the codec are contained at the NIF boundary and poison the affected
resource; they never crash the caller process.
Whole-stream facade
The default path encodes, decodes, or transcodes a whole buffer in one call (20 ms frames by default; a short last encode frame is padded with silence):
{:ok, packets} = RustyOpus.encode(pcm, 16_000, 1, quality: :medium)
{:ok, pcm} = RustyOpus.decode(packets, 16_000, 1)
{:ok, smaller} = RustyOpus.transcode(packets, 16_000, 1, :low)Single-frame helpers still create, use, and close a short-lived codec:
{:ok, packet} = RustyOpus.encode_pcm(pcm, 16_000, 1, bitrate: 32_000)
{:ok, pcm} = RustyOpus.decode_packet(packet, 16_000, 1, 320)