coreaudio 0.1.1

A safe and simple wrapper around the CoreAudio HAL
docs.rs failed to build coreaudio-0.1.1
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.

coreaudio

A safe, idiomatic Rust wrapper around the macOS CoreAudio Hardware Abstraction Layer (HAL).

This crate provides typed access to audio devices, streams, and system-level audio objects, with compile-time guarantees around property access permissions and listener support.

Features

  • Type-safe object modelAudioObject<System>, AudioObject<Device>, and AudioObject<Stream> expose only the operations valid for each object type.
  • Compile-time property safety — Properties carry phantom types encoding their value type, owning object, read/write access, and listenability. Attempting to write a read-only property or listen to a non-listenable one is a compile error.
  • Property listeners — Subscribe to property changes with add_listener, then poll with latest(), collect with all_since_last_check(), or block with block_until_change().
  • IO Procs — Register audio render callbacks on devices with add_io_proc and control playback with play() / pause().
  • Structured error handling — All CoreAudio OSStatus codes are mapped to a typed ErrorKind enum with human-readable four-character-code formatting.
  • Format support — Rich enums for audio format IDs (Linear PCM, AAC variants, ALAC, AC3, Opus, MP3, etc.), format flags, sample formats, and sample resampling utilities.

Requirements

Quick start

use coreaudio::{AudioObject, System, Scope, DEVICE_NAME};

fn main() -> Result<(), coreaudio::CoreAudioError> {
    let system = AudioObject::<System>::default();

    // List all output devices
    let devices = system.devices_with_scope(Scope::Output)?;

    for device in &devices {
        let name: String = device.get_property(DEVICE_NAME)?;
        println!("{}", name);
    }

    Ok(())
}

Usage

Querying device properties

use coreaudio::{
    AudioObject, System, Scope,
    DEVICE_NAME, DEVICE_UID, DEVICE_NOMINAL_SAMPLE_RATE,
    DEVICE_IS_ALIVE, DEVICE_BUFFER_FRAME_SIZE,
};

let system = AudioObject::<System>::default();
let device = system.current_device(Scope::Output)?;

let name: String = device.get_property(DEVICE_NAME)?;
let uid: String = device.get_property(DEVICE_UID)?;
let sample_rate: f64 = device.get_property(DEVICE_NOMINAL_SAMPLE_RATE)?;
let alive: bool = device.get_property(DEVICE_IS_ALIVE)?;

println!("{name} ({uid}) — {sample_rate} Hz, alive: {alive}");

Setting writable properties

use coreaudio::{AudioObject, System, Scope, DEVICE_NOMINAL_SAMPLE_RATE, DEVICE_BUFFER_FRAME_SIZE};

let system = AudioObject::<System>::default();
let device = system.current_device(Scope::Output)?;

device.set_property(DEVICE_NOMINAL_SAMPLE_RATE, 48000.0)?;
device.set_property(DEVICE_BUFFER_FRAME_SIZE, 512)?;

Listening for property changes

use coreaudio::{AudioObject, System, Scope, DEVICE_NOMINAL_SAMPLE_RATE};
use std::time::Duration;

let system = AudioObject::<System>::default();
let device = system.current_device(Scope::Output)?;

let listener = device.add_listener(DEVICE_NOMINAL_SAMPLE_RATE)?;

// Non-blocking check
if let Some(new_rate) = listener.latest() {
    println!("Sample rate changed to {new_rate}");
}

// Blocking with timeout
match listener.block_for_duration(Duration::from_secs(5)) {
    Ok(rate) => println!("Changed to {rate}"),
    Err(e) => println!("Timed out or error: {e}"),
}

Working with streams

use coreaudio::{AudioObject, System, Scope, STREAM_NAME, STREAM_IS_ACTIVE, STREAM_DIRECTION};

let system = AudioObject::<System>::default();
let device = system.current_device(Scope::Output)?;
let streams = device.streams_with_scope(Scope::Output)?;

for stream in &streams {
    let format = stream.stream_virtual_format()?;
    println!(
        "Format: {:?}, Sample rate: {}, Channels: {}",
        format.format_id(),
        format.sample_rate(),
        format.channels_per_frame(),
    );
}

Registering an IO proc (audio callback)

use coreaudio::{AudioObject, System, Scope};

let system = AudioObject::<System>::default();
let device = system.current_device(Scope::Output)?;

let io_proc = device.add_io_proc(|buffers| {
    for buffer in buffers {
        // Fill with silence
        buffer.data.fill(0.0);
    }
})?;

io_proc.play()?;
// ...
io_proc.pause()?;
io_proc.remove();

Available buffer sizes and sample rates

let buffer_range = device.avaliable_buffer_sizes()?;
println!("Valid buffer sizes: {:?}", buffer_range.valid_sizes());

let sample_rates = device.avaliable_sample_rates()?;
for range in &sample_rates {
    println!("{}{} Hz", range.min(), range.max());
}

Property reference

Device properties

Constant Type Access Listenable
DEVICE_NAME String Read No
DEVICE_UID String Read No
DEVICE_IS_ALIVE bool Read Yes
DEVICE_IS_RUNNING bool Read Yes
DEVICE_NOMINAL_SAMPLE_RATE f64 Read/Write Yes
DEVICE_BUFFER_FRAME_SIZE u32 Read/Write Yes
DEVICE_INPUT_LATENCY u32 Read No
DEVICE_OUTPUT_LATENCY u32 Read No
DEVICE_HOG_MODE i32 Read/Write Yes

Stream properties

Constant Type Access Listenable
STREAM_NAME String Read No
STREAM_IS_ACTIVE bool Read Yes
STREAM_DIRECTION u32 Read No
STREAM_LATENCY u32 Read No

System properties

Constant Type Access Listenable
SYSTEM_NAME String Read No
SYSTEM_IS_INITING_OR_EXITING bool Read No
SYSTEM_SLEEPING_IS_ALLOWED bool Read/Write Yes
SYSTEM_POWER_HINT u32 Read/Write No

Error handling

All fallible operations return Result<T, CoreAudioError>. The error type wraps a typed ErrorKind enum and the raw OSStatus code. You can match on the kind or inspect the four-character code string:

use coreaudio::ErrorKind;

match device.get_property(DEVICE_NAME) {
    Ok(name) => println!("{name}"),
    Err(e) => match e.kind() {
        ErrorKind::BadDevice => println!("Invalid device"),
        ErrorKind::Permissions => println!("Device is hogged by another process"),
        _ => println!("Error: {e}"),
    }
}

Supported audio formats

The FormatId enum covers Linear PCM, AAC (Standard, HE, HEv2, LD, ELD, ELDv2, ELD+SBR, Spatial), Apple Lossless, AC-3, Enhanced AC-3, APAC, AES3, A-Law, AMR, AMR-WB, Opus, and MP3. Unrecognised format IDs are preserved as FormatId::Unknown(u32).

Roadmap

  • Sample rate validationSampleRateRange currently exposes raw min/max values. A future release will add a validation method that checks whether a given sample rate falls within a device's supported ranges and snaps to the nearest valid rate.
  • Control properties — Expose device-level controls such as volume, mute, stereo pan, and per-channel gain through the existing property system.
  • More properties — Expand coverage of the CoreAudio HAL property surface, including transport controls, clock source selection, and safety/alive offsets.
  • Semantic newtypes for integer properties — Properties like SYSTEM_POWER_HINT, STREAM_DIRECTION, and DEVICE_HOG_MODE currently return raw integer types. These will be replaced with dedicated enums or wrapper types that give the values meaningful names.

License

See LICENSE file for details.