Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8cc6f25
feat: add device_id support for macOS to DeviceTrait
xephyris Sep 14, 2025
9161bbb
fix: fix import statements for other APIs
xephyris Sep 15, 2025
845baac
wip: use DeviceId enum instead to work around conflicting device_id i…
xephyris Sep 16, 2025
bd9b071
feat: transition to using DeviceId for enum for better cross compatib…
xephyris Sep 17, 2025
60c3c26
docs: add description for DeviceId
xephyris Sep 18, 2025
2a93a70
feat: implemented device id for windows wasapi
xephyris Sep 20, 2025
fb1efd0
docs: update description
xephyris Sep 23, 2025
626b823
fix: reformat existing code and fix android build error
xephyris Sep 24, 2025
c9d6b05
feat: add ALSA support to device id() function
xephyris Sep 24, 2025
6f8587f
fix: resolve merge conflicts
xephyris Sep 25, 2025
24ef5ac
feat: add support for jack and aaudio (untested). fix naming structur…
xephyris Sep 25, 2025
8b5b8d0
docs: update changelog
xephyris Sep 25, 2025
068e171
fix: fix function names causing compile errors
xephyris Sep 25, 2025
f9301b8
fix: fix aaudio DeviceId type
xephyris Sep 25, 2025
2972b52
feat: implement macos DeviceId to use kAudioDevicePropertyDeviceUID …
xephyris Sep 26, 2025
99fc8b7
fmt: reformat with rustfmt
xephyris Sep 26, 2025
43bc3e0
docs: update changelog
xephyris Sep 27, 2025
6e93a1b
feat: add asio support and reformat macos id function
xephyris Sep 27, 2025
59f4a9f
fmt: reformat code
xephyris Sep 27, 2025
b811266
feat: return default for ios, emscripten, and webaudio
xephyris Sep 27, 2025
eefc6ef
fix: Merge branch 'master' into master
xephyris Sep 29, 2025
f40bb9d
feat: add in rest of audio APIs to from_str()
xephyris Sep 29, 2025
492a98e
fix: Merge branch 'master' into master
xephyris Sep 30, 2025
30645a5
fix: change catch-all to todo!
xephyris Oct 1, 2025
44ec96b
fix: resolve parse error on null and deviceid implementations
xephyris Oct 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Unreleased

- Add `HostTrait::id` method that returns a stable audio device ID.
- Add `HostTrait::device_by_id` to select a device by its stable ID.
- Add `Sample::bits_per_sample` method.
- Update `audio_thread_priority` to 0.34.
- AAudio: Configure buffer to ensure consistent callback buffer sizes.
Expand Down
29 changes: 29 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,35 @@ impl From<BackendSpecificError> for DevicesError {
}
}

/// An error that may occur while attempting to retrieve a device id.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum DeviceIdError {
/// See the [`BackendSpecificError`] docs for more information about this error variant.
BackendSpecific {
err: BackendSpecificError,
},
UnsupportedPlatform,
ParseError,
}

impl Display for DeviceIdError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::BackendSpecific { err } => err.fmt(f),
Self::UnsupportedPlatform => f.write_str("Device ids are unsupported for this OS"),
Self::ParseError => f.write_str("Failed to parse the device_id"),
}
}
}

impl Error for DeviceIdError {}

impl From<BackendSpecificError> for DeviceIdError {
fn from(err: BackendSpecificError) -> Self {
Self::BackendSpecific { err }
}
}

/// An error that may occur while attempting to retrieve a device name.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum DeviceNameError {
Expand Down
15 changes: 11 additions & 4 deletions src/host/aaudio/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ use java_interface::{AudioDeviceDirection, AudioDeviceInfo};

use crate::traits::{DeviceTrait, HostTrait, StreamTrait};
use crate::{
BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError,
DeviceNameError, DevicesError, InputCallbackInfo, InputStreamTimestamp, OutputCallbackInfo,
OutputStreamTimestamp, PauseStreamError, PlayStreamError, SampleFormat, SampleRate,
SizedSample, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig,
BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError, DeviceId,
DeviceIdError, DeviceNameError, DevicesError, InputCallbackInfo, InputStreamTimestamp,
OutputCallbackInfo, OutputStreamTimestamp, PauseStreamError, PlayStreamError, SampleFormat,
SampleRate, SizedSample, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig,
SupportedStreamConfigRange, SupportedStreamConfigsError,
};

Expand Down Expand Up @@ -329,6 +329,13 @@ impl DeviceTrait for Device {
}
}

fn id(&self) -> Result<DeviceId, DeviceIdError> {
match &self.0 {
None => Ok(DeviceId::AAudio(-1)), // Default device
Some(info) => Ok(DeviceId::AAudio(info.id)),
}
}

fn supported_input_configs(
&self,
) -> Result<Self::SupportedInputConfigs, SupportedStreamConfigsError> {
Expand Down
15 changes: 12 additions & 3 deletions src/host/alsa/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ pub use self::enumerate::{default_input_device, default_output_device, Devices};
use crate::{
traits::{DeviceTrait, HostTrait, StreamTrait},
BackendSpecificError, BufferSize, BuildStreamError, ChannelCount, Data,
DefaultStreamConfigError, DeviceNameError, DevicesError, FrameCount, InputCallbackInfo,
OutputCallbackInfo, PauseStreamError, PlayStreamError, Sample, SampleFormat, SampleRate,
StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig,
DefaultStreamConfigError, DeviceId, DeviceIdError, DeviceNameError, DevicesError, FrameCount,
InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, Sample, SampleFormat,
SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig,
SupportedStreamConfigRange, SupportedStreamConfigsError, I24, U24,
};

Expand Down Expand Up @@ -96,6 +96,10 @@ impl DeviceTrait for Device {
Device::name(self)
}

fn id(&self) -> Result<DeviceId, DeviceIdError> {
Device::id(self)
}

fn supported_input_configs(
&self,
) -> Result<Self::SupportedInputConfigs, SupportedStreamConfigsError> {
Expand Down Expand Up @@ -347,6 +351,11 @@ impl Device {
Ok(self.to_string())
}

#[inline]
fn id(&self) -> Result<DeviceId, DeviceIdError> {
Ok(DeviceId::ALSA(self.pcm_id.clone()))
}

fn supported_configs(
&self,
stream_t: alsa::Direction,
Expand Down
6 changes: 6 additions & 0 deletions src/host/asio/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ pub type SupportedOutputConfigs = std::vec::IntoIter<SupportedStreamConfigRange>
use super::sys;
use crate::BackendSpecificError;
use crate::DefaultStreamConfigError;
use crate::DeviceId;
use crate::DeviceIdError;
use crate::DeviceNameError;
use crate::DevicesError;
use crate::SampleFormat;
Expand Down Expand Up @@ -54,6 +56,10 @@ impl Device {
Ok(self.driver.name().to_string())
}

fn id(&self) -> Result<DeviceId, DeviceIdError> {
Ok(DeviceId::ASIO(self.driver.name().to_string()))
}

/// Gets the supported input configs.
/// TODO currently only supports the default.
/// Need to find all possible configs.
Expand Down
10 changes: 7 additions & 3 deletions src/host/asio/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ extern crate asio_sys as sys;

use crate::traits::{DeviceTrait, HostTrait, StreamTrait};
use crate::{
BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError,
InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat,
StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigsError,
BuildStreamError, Data, DefaultStreamConfigError, DeviceId, DeviceIdError, DeviceNameError,
DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError,
SampleFormat, StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigsError,
};

pub use self::device::{Device, Devices, SupportedInputConfigs, SupportedOutputConfigs};
Expand Down Expand Up @@ -62,6 +62,10 @@ impl DeviceTrait for Device {
Device::name(self)
}

fn id(&self) -> Result<DeviceId, DeviceIdError> {
Device::id(self)
}

fn supported_input_configs(
&self,
) -> Result<Self::SupportedInputConfigs, SupportedStreamConfigsError> {
Expand Down
18 changes: 14 additions & 4 deletions src/host/coreaudio/ios/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ use super::{asbd_from_config, frames_to_duration, host_time_to_stream_instant};
use crate::traits::{DeviceTrait, HostTrait, StreamTrait};

use crate::{
BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError,
DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError,
PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize,
SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError,
BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError, DeviceId,
DeviceIdError, DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo,
PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError,
SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange,
SupportedStreamConfigsError,
};

use self::enumerate::{
Expand Down Expand Up @@ -88,6 +89,10 @@ impl Device {
Ok("Default Device".to_owned())
}

fn id(&self) -> Result<DeviceId, DeviceIdError> {
Ok(DeviceId::IOS("default".to_string()))
}

#[inline]
fn supported_input_configs(
&self,
Expand Down Expand Up @@ -154,6 +159,11 @@ impl DeviceTrait for Device {
Device::name(self)
}

#[inline]
fn id(&self) -> Result<DeviceId, DeviceIdError> {
Device::id(self)
}

#[inline]
fn supported_input_configs(
&self,
Expand Down
49 changes: 46 additions & 3 deletions src/host/coreaudio/macos/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@ use crate::host::coreaudio::macos::StreamInner;
use crate::traits::DeviceTrait;
use crate::{
BackendSpecificError, BufferSize, BuildStreamError, ChannelCount, Data,
DefaultStreamConfigError, DeviceNameError, InputCallbackInfo, OutputCallbackInfo, SampleFormat,
SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig,
SupportedStreamConfigRange, SupportedStreamConfigsError,
DefaultStreamConfigError, DeviceId, DeviceIdError, DeviceNameError, InputCallbackInfo,
OutputCallbackInfo, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize,
SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError,
};
use coreaudio::audio_unit::render_callback::{self, data};
use coreaudio::audio_unit::{AudioUnit, Element, Scope};
use objc2_audio_toolbox::{
kAudioOutputUnitProperty_CurrentDevice, kAudioOutputUnitProperty_EnableIO,
kAudioUnitProperty_StreamFormat,
};
use objc2_core_audio::kAudioDevicePropertyDeviceUID;
use objc2_core_audio::kAudioObjectPropertyElementMain;
use objc2_core_audio::{
kAudioDevicePropertyAvailableNominalSampleRates, kAudioDevicePropertyBufferFrameSize,
kAudioDevicePropertyBufferFrameSizeRange, kAudioDevicePropertyDeviceIsAlive,
Expand All @@ -29,6 +31,8 @@ use objc2_core_audio::{
use objc2_core_audio_types::{
AudioBuffer, AudioBufferList, AudioStreamBasicDescription, AudioValueRange,
};
use objc2_core_foundation::CFString;
use objc2_core_foundation::Type;

pub use super::enumerate::{
default_input_device, default_output_device, SupportedInputConfigs, SupportedOutputConfigs,
Expand All @@ -44,6 +48,9 @@ use std::time::{Duration, Instant};

use super::property_listener::AudioObjectPropertyListener;
use coreaudio::audio_unit::macos_helpers::get_device_name;

type CFStringRef = *mut std::os::raw::c_void;

/// Attempt to set the device sample rate to the provided rate.
/// Return an error if the requested sample rate is not supported by the device.
fn set_sample_rate(
Expand Down Expand Up @@ -301,6 +308,10 @@ impl DeviceTrait for Device {
Device::name(self)
}

fn id(&self) -> Result<DeviceId, DeviceIdError> {
Device::id(self)
}

fn supported_input_configs(
&self,
) -> Result<Self::SupportedInputConfigs, SupportedStreamConfigsError> {
Expand Down Expand Up @@ -395,6 +406,38 @@ impl Device {
})
}

fn id(&self) -> Result<DeviceId, DeviceIdError> {
let property_address = AudioObjectPropertyAddress {
mSelector: kAudioDevicePropertyDeviceUID,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMain,
};
let mut uid: CFStringRef = std::ptr::null_mut();
let data_size = size_of::<CFStringRef>() as u32;
let status = unsafe {
AudioObjectGetPropertyData(
self.audio_device_id,
NonNull::from(&property_address),
0,
null(),
NonNull::from(&data_size),
NonNull::from(&mut uid).cast(),
)
};
check_os_status(status)?;
if !uid.is_null() {
let uid_string =
unsafe { CFString::wrap_under_get_rule(uid as *mut CFString).to_string() };
Ok(DeviceId::CoreAudio(uid_string))
} else {
Err(DeviceIdError::BackendSpecific {
err: BackendSpecificError {
description: "Device UID not found".to_string(),
},
})
}
}

// Logic re-used between `supported_input_configs` and `supported_output_configs`.
#[allow(clippy::cast_ptr_alignment)]
fn supported_configs(
Expand Down
5 changes: 1 addition & 4 deletions src/host/coreaudio/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@ pub use self::ios::{
};

#[cfg(target_os = "macos")]
pub use self::macos::{
enumerate::{Devices, SupportedInputConfigs, SupportedOutputConfigs},
Device, Host, Stream,
};
pub use self::macos::Host;

// Common helper methods used by both macOS and iOS

Expand Down
17 changes: 13 additions & 4 deletions src/host/emscripten/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ use web_sys::AudioContext;

use crate::traits::{DeviceTrait, HostTrait, StreamTrait};
use crate::{
BufferSize, BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError,
InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat,
SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig,
SupportedStreamConfigRange, SupportedStreamConfigsError,
BufferSize, BuildStreamError, Data, DefaultStreamConfigError, DeviceId, DeviceIdError,
DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError,
PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize,
SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError,
};

// The emscripten backend currently works by instantiating an `AudioContext` object per `Stream`.
Expand Down Expand Up @@ -69,6 +69,11 @@ impl Device {
Ok("Default Device".to_owned())
}

#[inline]
fn id(&self) -> Result<DeviceId, DeviceIdError> {
Ok(DeviceId::Emscripten("default".to_string()))
}

#[inline]
fn supported_input_configs(
&self,
Expand Down Expand Up @@ -144,6 +149,10 @@ impl DeviceTrait for Device {
Device::name(self)
}

fn id(&self) -> Result<DeviceId, DeviceIdError> {
Device::id(self)
}

fn supported_input_configs(
&self,
) -> Result<Self::SupportedInputConfigs, SupportedStreamConfigsError> {
Expand Down
16 changes: 12 additions & 4 deletions src/host/jack/device.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::traits::DeviceTrait;
use crate::{
BackendSpecificError, BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError,
InputCallbackInfo, OutputCallbackInfo, SampleFormat, SampleRate, StreamConfig, StreamError,
SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange,
SupportedStreamConfigsError,
BackendSpecificError, BuildStreamError, Data, DefaultStreamConfigError, DeviceId,
DeviceIdError, DeviceNameError, InputCallbackInfo, OutputCallbackInfo, SampleFormat,
SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig,
SupportedStreamConfigRange, SupportedStreamConfigsError,
};
use std::hash::{Hash, Hasher};
use std::time::Duration;
Expand Down Expand Up @@ -64,6 +64,10 @@ impl Device {
}
}

fn id(&self) -> Result<DeviceId, DeviceIdError> {
Ok(DeviceId::Jack(self.name.clone()))
}

pub fn default_output_device(
name: &str,
connect_ports_automatically: bool,
Expand Down Expand Up @@ -146,6 +150,10 @@ impl DeviceTrait for Device {
Ok(self.name.clone())
}

fn id(&self) -> Result<DeviceId, DeviceIdError> {
Device::id(self)
}

fn supported_input_configs(
&self,
) -> Result<Self::SupportedInputConfigs, SupportedStreamConfigsError> {
Expand Down
11 changes: 8 additions & 3 deletions src/host/null/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ use std::time::Duration;

use crate::traits::{DeviceTrait, HostTrait, StreamTrait};
use crate::{
BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError,
InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat,
StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigRange,
BuildStreamError, Data, DefaultStreamConfigError, DeviceId, DeviceIdError, DeviceNameError,
DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError,
SampleFormat, StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigRange,
SupportedStreamConfigsError,
};

Expand Down Expand Up @@ -47,6 +47,11 @@ impl DeviceTrait for Device {
Ok("null".to_owned())
}

#[inline]
fn id(&self) -> Result<DeviceId, DeviceIdError> {
Ok(DeviceId::Null)
}

#[inline]
fn supported_input_configs(
&self,
Expand Down
Loading