Skip to content

Releases

opus-pure 0.2.2

Choose a tag to compare

@stephenberry stephenberry released this 28 Sep 09:35

A looping playback (read, decode, trim, and at the end rewind, reset, new Trim) now allocates nothing after its first pass, which matters on an audio thread where an allocation can take a lock.

Added

  • OggOpusReader::read_packet_into fills an OggPacket the caller keeps, where read_packet returns a new one. OggPacket::default() is the empty one to start from.
  • OggOpusReader::rewind returns a seekable source to the first audio packet without reading the header pages again. It replaces rebuilding the reader to loop.

Changed

  • Reading and decoding no longer allocate once warm, loop points included: the Ogg reader reassembles packets in one reused buffer, the packet parser holds its frame table inline, the range decoder borrows the packet, and the multistream decoder reuses one buffer per stream. tests/ogg_playback_alloc.rs holds it to zero.
  • OpusDecoder::reset_state resets in place instead of building a new decoder, so it no longer allocates; a reset decoder still decodes bit for bit as a new one.
  • The Ogg reader refuses a stream whose comment header does not finish its page, as RFC 7845 §3 requires and libopusfile enforces, and caps a reassembled packet at 16 MiB, so a hostile chain of continued pages cannot grow its buffer without limit.
  • RangeCoder::shrink checks its size preconditions in release builds.

Additive otherwise: nothing was removed from the 0.2.1 public surface.

opus-pure 0.2.1

Choose a tag to compare

@stephenberry stephenberry released this 31 Aug 23:18

A streaming playback path can now hold its position inside a decoded packet without copying the packet out.

Added

  • Trim::keep_range — the same cut as keep, returned as indices into the decoded PCM rather than as a subslice of it. A player fills whatever buffer the audio device asks for, whenever it asks for it, so what is left of a packet has to survive until the next call: a slice borrows the decode buffer and cannot be held beside it, and the offset is not recoverable afterwards, because the pre-skip shortens the slice from the front and the end-trim from the back. Holding the range instead leaves the decode buffer usable as the staging buffer, and each sample is written once, straight into the device's. keep is now a wrapper over it, so the two cannot drift.

The README gains a worked Playback example that fills caller buffers of any size with no staging copy, compiled and run with the rest of its doctests.

Purely additive: nothing was removed or changed from the 0.2.0 public surface.

opus-pure 0.2.0

Choose a tag to compare

@stephenberry stephenberry released this 31 Aug 22:20

Reading an .opus file back now returns exactly the audio that was encoded, and writing one puts exactly that audio in. Both halves of the documented round trip were wrong before, in ways that were silent.

Fixed

The documented decode recipe dropped the end-trim. README, crate docs and examples/decode.rs applied the pre-skip but not RFC 7845 §4.4, leaving up to a frame of padding past the end of the audio on any file carrying one — which every opusenc file does. Inaudible on a clip played once; an audible gap at every seam of a loop.

The documented encode recipe lost the tail. It padded only to the frame boundary, so the encoder's algorithmic delay was never flushed and the last few milliseconds of every clip stayed inside the encoder. write_packet_with_duration, which states the end-trim, appeared in no example.

Cross-checked against ffmpeg, which implements the same trimming independently and decodes the output to the same sample count.

Added

  • Trim — applies the pre-skip (§4.2) and end-trim (§4.4) to decoded PCM. Both are a few lines every consumer of the container would otherwise write, and both fail silently when written wrong.
  • OpusHead::decoder — a decoder carrying the header's channel count and output gain. The gain is the one that fails silently: the file plays at the wrong level with no other symptom.
  • OggOpusWriter::granule — the granule position reached, so declaring an end-trim is a subtraction rather than a derivation that assumes uniform packet durations.
  • MAX_PACKET_SAMPLES — decode-buffer size, the companion to MAX_PACKET_BYTES.
  • OggPacket::new — construct one without muxing a stream, so code that consumes packets is testable.
  • OpusMSDecoder::streams / streams_mut — mirroring the encoder. Without them a surround stream's declared output gain could not be applied at all, which RFC 7845 §5.1 asks a player to do whatever the mapping family.

Purely additive: nothing was removed or changed from the 0.1.0 public surface.

Notes

The Ogg reader still has no seek. It documents rewinding for playback loops, and the README says why a seek without §4.2 pre-roll would be worse than none.

Full changelog: v0.1.0...v0.2.0

v0.1.0

Choose a tag to compare

@stephenberry stephenberry released this 27 Aug 02:43

First release of opus-pure: a complete Opus codec in Rust, with no C, no FFI, no build.rs and no dependencies. Adding the crate is the whole install, and it cross-compiles anywhere Rust does.

What's in it

  • A complete codec. Encoder and decoder for all three Opus coding modes — SILK for speech, CELT for music, and the hybrid of both — at 8, 12, 16, 24 and 48 kHz, mono and stereo, at every one of the nine Opus frame sizes.
  • A real container. Ogg mux and demux, with OpusHead, OpusTags, page CRCs and correct granule positions, so it reads and writes .opus files rather than only raw packets.
  • More than the basics. Multistream encoding and decoding, a repacketizer, packet inspection without decoding, and parallel encoding.

Correctness

Byte-exact with libopus 1.6.1 everywhere the format allows it, and measured against it everywhere it does not. Files it writes pass opusinfo and decode with opusdec; files opusenc writes read back through this crate.

Speed

Encoding runs 1.1–1.8x libopus on the same machine and the same audio; decoding runs at 0.84–1.02x of it.

Install

[dependencies]
opus-pure = "0.1"

Requires Rust 1.88 or newer.

Attribution

Derived from rusty-opus, itself a fork of opus-rs, both Rust ports of the reference libopus by the Xiph.Org Foundation and others. See ATTRIBUTION.md. BSD-3-Clause.