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().
Finalizedproves EOS and successful flush for the returned sink.Abortedreturns an incomplete sink without final drain/EOS/flush.Failed(error)returns an incomplete irreversible prefix with the original startup/pump/drain/EOS/flush error.
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