audio-io-bsd 0.2.0

Audio I/O backend abstraction (AudioBackend trait) with a cpal ALSA/OSS backend for FreeBSD-first real-time audio
# audio-io-bsd

[![License: BSD-2-Clause](https://img.shields.io/badge/license-BSD--2--Clause-blue.svg)](./LICENSE)
[![Rust](https://img.shields.io/badge/rust-1.85%2B-orange.svg)](https://www.rust-lang.org)

Audio I/O backend abstraction for real-time audio. It provides the
`AudioBackend` trait — the single contract a host application (an audio engine,
a test, or any consumer) uses to reach the kernel audio layer — plus the
plain-data types (`StreamParams`, `DeviceInfo`) that flow across it. On FreeBSD
the concrete backend reaches OSS `/dev/dsp` *indirectly* through the cpal ALSA
backend (FreeBSD's `alsa-lib` port translates ALSA calls onto OSS).

> The doc comments on each public item are the primary reference. This README is
> an overview only.

## Overview

`audio-io-bsd` decouples audio *consumers* (audio engines, gateway code, tests)
from the concrete device driver. The default build has **no** system-audio
dependency: the trait surface and an in-memory [`NullBackend`] test double
compile and test anywhere. Two concrete backends are opt-in: [`CpalBackend`]
(Linux/macOS fallback via cpal) and [`OssBackend`] (FreeBSD-native direct OSS).

### Real-time safety boundary

`OutputSink::write` is designed to be **callable from the real-time audio
thread**: it must not allocate, lock, panic, or perform I/O. Both concrete
backends follow the same pattern — `write` pushes interleaved samples into a
lock-free [`rtrb`] ring (wait-free); a dedicated audio thread drains it and
performs the device I/O (a cpal callback, or a blocking `write(2)` to
`/dev/dsp`). The heavy device I/O never happens on the caller's RT thread.
Capture (`InputSource::read`) is intended for a worker thread.

## Core types

| Type            | Description                                                              |
|-----------------|--------------------------------------------------------------------------|
| `AudioBackend`  | The public trait: enumerate devices, open output/input streams.          |
| `OutputSink`    | Write side of a playback stream (RT-thread-safe).                        |
| `InputSource`   | Read side of a capture stream (worker thread).                           |
| `StreamParams`  | Sample rate / channels / format / buffer size to open a stream with.     |
| `DeviceInfo`    | Capabilities of a discovered device.                                     |
| `NullBackend`   | Device-free in-memory backend for tests (always available).              |
| `CpalBackend`   | cpal backend: ALSA on Linux, CoreAudio on macOS (`cpal-backend` feat).   |
| `OssBackend`    | FreeBSD-native direct OSS `/dev/dsp` backend (`oss` feat, FreeBSD only). |
| `IoError`       | Error enum (thiserror-backed).                                           |

## Feature flags

| Feature        | Default | Purpose                                                          |
|----------------|---------|------------------------------------------------------------------|
| `cpal-backend` | off     | Pull in `cpal` + `rtrb` and the `CpalBackend` (Linux/macOS).     |
| `oss`          | off     | Pull in `libc` + `rtrb` and the FreeBSD-native `OssBackend`.     |

With neither feature enabled the crate links no system audio library, so it
builds cleanly on docs.rs and CI hosts without ALSA headers or `/dev/dsp`. On
FreeBSD prefer `oss` (native); on Linux/macOS prefer `cpal-backend`.

## Dependencies

| Dependency       | Version | Purpose                              | License           | Optional |
|------------------|---------|--------------------------------------|-------------------|----------|
| `audio-core-bsd` | 0.1.0   | `AudioFrame`, `SampleFormat`, errors | BSD-2-Clause      | no       |
| `thiserror`      | 2.0     | `IoError` derive                     | MIT OR Apache-2.0 | no       |
| `cpal`           | 0.15    | ALSA/CoreAudio backend (`cpal-backend`) | Apache-2.0     | yes      |
| `libc`           | 0.2     | raw ioctl for OSS backend (`oss`)    | MIT OR Apache-2.0 | yes      |
| `rtrb`           | 0.3     | lock-free ring buffer (RT↔device)    | MIT OR Apache-2.0 | yes      |
| `proptest` (dev) | 1.11.0  | property tests                      | MIT OR Apache-2.0 ||

> `cpal` on Linux links ALSA (**LGPL-2.1**, runtime only), compatible with the
> BSD-2-Clause license of this crate. The `oss` backend links only `libc`
> (no ALSA, no cpal) on FreeBSD.

## Status

**0.x — experimental.** The API is not yet frozen. The core trait surface
(`AudioBackend`/`OutputSink`/`InputSource`) and `NullBackend` are implemented and
tested. `CpalBackend` requires the `cpal-backend` feature and is validated on a
FreeBSD CI gate (native `cargo test --all-features` on FreeBSD 14.2 / 15.1).

> **Note:** cpal is pinned to **0.15** because cpal 0.18's ALSA backend does not
> compile on FreeBSD (`libc::ESTRPIPE` is Linux-only). See the
> [`CpalBackend`]cpal_backend/struct.CpalBackend.html docs for details.

- edition: 2021
- MSRV: 1.85
- license: BSD-2-Clause

## Example

Open the default output and push silence (works on any host via `NullBackend`):

```rust
use audio_io_bsd::{AudioBackend, NullBackend, StreamParams};

let backend = NullBackend::new();
let dev = backend.default_output().unwrap();
let mut sink = backend.open_output(&dev.name, StreamParams::pcm_48k_stereo())?;
let silence = audio_core_bsd::AudioFrame::silence(2, 256, 48_000);
sink.write(&silence)?;
# Ok::<(), audio_io_bsd::IoError>(())
```

For real audio, replace `NullBackend` with `CpalBackend::new()` and enable the
`cpal-backend` feature (see `examples/play_sine.rs`).

## License

BSD-2-Clause. See [`LICENSE`](./LICENSE).

[`rtrb`]: https://crates.io/crates/rtrb
[`NullBackend`]: https://docs.rs/audio-io-bsd/latest/audio_io_bsd/null_backend/struct.NullBackend.html
[`CpalBackend`]: https://docs.rs/audio-io-bsd/latest/audio_io_bsd/cpal_backend/struct.CpalBackend.html