# audio-io-bsd
[](./LICENSE)
[](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
| `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
| `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
| `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