AUDIO
Source snapshot · 12 October 2026

WAV/AIFF streams and audio handoff buffers

Optional bounded Ogg Vorbis worker encoding

Enable ravet-audio-file's default-disabled vorbis-encode feature for encode_vorbis, VorbisWriter and VorbisFormat. The backend is pinned safe vorbis_rs 0.5.6 with default features disabled, over vendored BSD libogg and aoTuV/libvorbis. Ordinary builds require a C compiler through cc and supplied bindings; they do not require a system codec library, bindgen/libclang or random serial generation. Preserve the backend notices.

Callers select mono/stereo Vorbis I channel order, encoded rate (mono 8000..96000 Hz, stereo 16000..96000 Hz), finite perceptual quality -0.2..1 and an explicit i32 Ogg stream serial. Quality controls the backend's lossy encoding; no exact PCM or subjective quality promise is made. Finite interleaved samples must be within [-1,1]; overload/nonfinite input rejects instead of clipping. No implicit resampling, channel mapping, path, clock, device, thread or serial policy is selected. Logical streams chained or multiplexed by consumers need distinct serials; that container policy is separate.

Stereo rates below 16000 Hz reject before sink output. At maximum quality, the selected backend's 8000 Hz synthetic stereo fixture showed sustained second-channel loss: whole-channel SNR 7.26 dB and interior SNR 6.98 dB, compared with first-channel 46.25 dB. Direct backend and reframed native decoding agreed; lag/gain fitting did not repair the loss. The public wrapper excludes that profile instead of changing input rate or silently choosing another quality/coupling policy. Its safe builder does not expose channel-coupling control. Mono 8000 Hz remains supported; selected 16000/22050/48000 Hz stereo fixtures retained more than 25 dB whole-stream SNR. These measured synthetic figures describe the reviewed backend on macOS arm64, without a perceptual or cross-platform guarantee.

encode_vorbis(samples, format) produces a complete nonempty owned byte stream from at most 4,194,304 f32 samples with output capped at 64 MiB. VorbisWriter::new(destination, format, maximum_frames, maximum_bytes) prepares an incremental writer, with total frames 1..16,777,216 and output bytes 1..67,108,864. It writes the initial headers at the sink's current position; the caller supplies the intended destination/offset and owns truncation policy. Header-output failure can consume the sink and leave an irreversible prefix.

Whole calls to write_frames preflight shape, samples and per-call/total counts before accepting input. Each call accepts at most 65536 frames and is divided into prepared 4096-frame planar batches. Empty input is a no-op; it does not signal EOS. Wrapper scratch remains fixed, including one physical Ogg page at most 65,307 bytes; backend working allocations remain its own contract. The encoder output is framed with a separate zero-granule priming packet page, preserving every packet/lacing/body/serial and exact final granule while rewriting sequence/CRC. Header packets and the priming packet must terminate within a physical page; continued headers, mixed header/audio pages and spanning priming packets reject. This boundary is tested for the selected mono/stereo backend profiles and is not an arbitrary Ogg repackaging API. accepted_frames includes submitted batches even on a later encoding failure; written_bytes counts bytes actually accepted by the sink, including failed partial writes. Neither count proves a complete file.

Preflight failures preserve a usable writer. Codec, I/O or output-budget failures after acceptance poison it; earlier output cannot be rolled back. Explicit nonempty finish writes EOS and flushes once before returning the sink. A failed consuming finish loses the sink and has no complete-file or retry guarantee. The private sink gate latches every parser/partial-page/sink/budget failure and disables writes before Drop, so the backend's automatic-drop EOS does not write or flush the caller sink. User-supplied sink destructors retain their own contract.

Create, use, finish and drop an incremental writer on one worker thread: the safe backend's raw-pointer ownership is not Send/Sync. A consumer may move the input format/sink into a worker and construct the writer there. Every codec method and teardown belongs outside callbacks. The dedicated VorbisRecordingWorker below creates the codec locally for automatic prepared capture; it does not implement the movable RecordingPump contract or establish an audio deadline.

Tests cover mono/stereo rates and short/final tails through independent Symphonia decoding, declared page CRC/serial/BOS/EOS/exact final granules, lossy synthetic PCM alignment and SNR, partitioned writes/preflight preservation, sink/output budget poison, original errors and write/flush counters across ordinary/failed Drop. An explicitly invoked ignored test exercises the independent FFmpeg native Vorbis decoder using generated synthetic streams:

RAVET_TEST_FFMPEG=/path/to/ffmpeg cargo test -p ravet-audio-file --all-features --test vorbis --locked -- --ignored ffmpeg_native_decoder_independently_agrees_on_pcm_timing_and_channel_order

This synthetic codec evidence does not validate physical capture, subjective listening quality, host devices, deadlines or another target toolchain. See decision 0181.

Automatic Vorbis capture ownership

With vorbis-encode, VorbisRecordingWorker prepares a selected Send sink and exact capture-ring consumer, then constructs, uses and destroys the !Send Vorbis codec on the named worker. new validates format/queue/budgets and prepares 1..4096-frame source scratch without codec/header/thread creation. start acknowledges OS spawn only; inspect status and the eventual outcome for startup header errors. OS spawn failure retains preparation for retry or abort.

Capture callbacks keep prepared AudioProducer::push or PreparedCapture ownership. A full ring accepts a prefix; the caller explicitly retries or drops the suffix. Pause waits for worker acknowledgement after active sink I/O; after success it leaves queued frames untouched. Resume acknowledges the same running codec. These control methods and join can block indefinitely on selected sink I/O; they do not cancel I/O or provide a real-time deadline.

Stop and reclaim the exact producer before finish_stopped, or drop the hidden callback producer before finish_after_producer_retired. Foreign/live/leaked producers reject before changing worker state. Finish drains at most the stopped fixed-ring tail, emits EOS, validates complete pages and flushes on the codec's owning worker. Even a never-started controller creates the codec there. Its Ok result means ownership was recovered: inspect the private-constructed VorbisRecordingCompletion through completion().

The exit also returns the owned consumer and accepted encoder-frame, dequeued-ring-frame and actual sink-byte counts. A rejected dequeued batch can make source/dequeue counts differ; failed workers stop further ring consumption until explicit finish/abort. abort recovers partial ownership without encoding or flushing more input. Drop requests gated abort and joins but discards the returned ownership; sink destructor behavior remains caller-owned. Panic loses the unwound worker-local sink/source and reports Failed/Poisoned rather than pretending they can be reclaimed. Existing VorbisWriter::new/finish retain their consuming-failure signatures.

The default-disabled device feature vorbis-recording forwards the audio-file encoder for PreparedCapture integration. Synthetic callback tests cover hidden producer retirement, allocation-free publication and automatic bounded final ring drain; physical CPAL shutdown timing and recording deadlines remain unvalidated. Rates/quality/PCM/output/framing and backend-memory limits remain those of the Vorbis writer above.

See decision 0186.

cargo test -p ravet-audio-file --all-features --test vorbis_recording --locked
cargo test -p ravet-device --features vorbis-recording --test vorbis_recording --locked