diff --git a/CHANGELOG.md b/CHANGELOG.md index 3235eced8..bcd691440 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- Add `DeviceTrait::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. diff --git a/src/error.rs b/src/error.rs index 2fed3622b..d39b071d7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -65,6 +65,35 @@ impl From 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 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 { diff --git a/src/host/aaudio/mod.rs b/src/host/aaudio/mod.rs index b87c86a36..d6880b4b2 100644 --- a/src/host/aaudio/mod.rs +++ b/src/host/aaudio/mod.rs @@ -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, }; @@ -329,6 +329,13 @@ impl DeviceTrait for Device { } } + fn id(&self) -> Result { + match &self.0 { + None => Ok(DeviceId::AAudio(-1)), // Default device + Some(info) => Ok(DeviceId::AAudio(info.id)), + } + } + fn supported_input_configs( &self, ) -> Result { diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index 5560c71fc..b2b9ca384 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -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, }; @@ -96,6 +96,10 @@ impl DeviceTrait for Device { Device::name(self) } + fn id(&self) -> Result { + Device::id(self) + } + fn supported_input_configs( &self, ) -> Result { @@ -347,6 +351,11 @@ impl Device { Ok(self.to_string()) } + #[inline] + fn id(&self) -> Result { + Ok(DeviceId::ALSA(self.pcm_id.clone())) + } + fn supported_configs( &self, stream_t: alsa::Direction, diff --git a/src/host/asio/device.rs b/src/host/asio/device.rs index dd854136e..85d851039 100644 --- a/src/host/asio/device.rs +++ b/src/host/asio/device.rs @@ -4,6 +4,8 @@ pub type SupportedOutputConfigs = std::vec::IntoIter use super::sys; use crate::BackendSpecificError; use crate::DefaultStreamConfigError; +use crate::DeviceId; +use crate::DeviceIdError; use crate::DeviceNameError; use crate::DevicesError; use crate::SampleFormat; @@ -54,6 +56,10 @@ impl Device { Ok(self.driver.name().to_string()) } + fn id(&self) -> Result { + 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. diff --git a/src/host/asio/mod.rs b/src/host/asio/mod.rs index 73d936d99..5c56b155f 100644 --- a/src/host/asio/mod.rs +++ b/src/host/asio/mod.rs @@ -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}; @@ -62,6 +62,10 @@ impl DeviceTrait for Device { Device::name(self) } + fn id(&self) -> Result { + Device::id(self) + } + fn supported_input_configs( &self, ) -> Result { diff --git a/src/host/coreaudio/ios/mod.rs b/src/host/coreaudio/ios/mod.rs index f3ad2834c..3b034170f 100644 --- a/src/host/coreaudio/ios/mod.rs +++ b/src/host/coreaudio/ios/mod.rs @@ -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::{ @@ -88,6 +89,10 @@ impl Device { Ok("Default Device".to_owned()) } + fn id(&self) -> Result { + Ok(DeviceId::IOS("default".to_string())) + } + #[inline] fn supported_input_configs( &self, @@ -154,6 +159,11 @@ impl DeviceTrait for Device { Device::name(self) } + #[inline] + fn id(&self) -> Result { + Device::id(self) + } + #[inline] fn supported_input_configs( &self, diff --git a/src/host/coreaudio/macos/device.rs b/src/host/coreaudio/macos/device.rs index 9b6549628..ca969fee5 100644 --- a/src/host/coreaudio/macos/device.rs +++ b/src/host/coreaudio/macos/device.rs @@ -6,9 +6,9 @@ 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}; @@ -16,6 +16,8 @@ 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, @@ -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, @@ -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( @@ -301,6 +308,10 @@ impl DeviceTrait for Device { Device::name(self) } + fn id(&self) -> Result { + Device::id(self) + } + fn supported_input_configs( &self, ) -> Result { @@ -395,6 +406,38 @@ impl Device { }) } + fn id(&self) -> Result { + let property_address = AudioObjectPropertyAddress { + mSelector: kAudioDevicePropertyDeviceUID, + mScope: kAudioObjectPropertyScopeGlobal, + mElement: kAudioObjectPropertyElementMain, + }; + let mut uid: CFStringRef = std::ptr::null_mut(); + let data_size = size_of::() 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( diff --git a/src/host/coreaudio/mod.rs b/src/host/coreaudio/mod.rs index 4310b6940..0c09249fe 100644 --- a/src/host/coreaudio/mod.rs +++ b/src/host/coreaudio/mod.rs @@ -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 diff --git a/src/host/emscripten/mod.rs b/src/host/emscripten/mod.rs index 4d6b3daf8..b3cdd54d0 100644 --- a/src/host/emscripten/mod.rs +++ b/src/host/emscripten/mod.rs @@ -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`. @@ -69,6 +69,11 @@ impl Device { Ok("Default Device".to_owned()) } + #[inline] + fn id(&self) -> Result { + Ok(DeviceId::Emscripten("default".to_string())) + } + #[inline] fn supported_input_configs( &self, @@ -144,6 +149,10 @@ impl DeviceTrait for Device { Device::name(self) } + fn id(&self) -> Result { + Device::id(self) + } + fn supported_input_configs( &self, ) -> Result { diff --git a/src/host/jack/device.rs b/src/host/jack/device.rs index 7a911dce4..3d7175d98 100644 --- a/src/host/jack/device.rs +++ b/src/host/jack/device.rs @@ -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; @@ -64,6 +64,10 @@ impl Device { } } + fn id(&self) -> Result { + Ok(DeviceId::Jack(self.name.clone())) + } + pub fn default_output_device( name: &str, connect_ports_automatically: bool, @@ -146,6 +150,10 @@ impl DeviceTrait for Device { Ok(self.name.clone()) } + fn id(&self) -> Result { + Device::id(self) + } + fn supported_input_configs( &self, ) -> Result { diff --git a/src/host/null/mod.rs b/src/host/null/mod.rs index 0a9752b70..031911ba3 100644 --- a/src/host/null/mod.rs +++ b/src/host/null/mod.rs @@ -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, }; @@ -47,6 +47,11 @@ impl DeviceTrait for Device { Ok("null".to_owned()) } + #[inline] + fn id(&self) -> Result { + Ok(DeviceId::Null) + } + #[inline] fn supported_input_configs( &self, diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index cd82fd993..c83583f34 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -1,8 +1,8 @@ use crate::FrameCount; use crate::{ - BackendSpecificError, BufferSize, Data, DefaultStreamConfigError, DeviceNameError, - DevicesError, InputCallbackInfo, OutputCallbackInfo, SampleFormat, SampleRate, StreamConfig, - SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, + BackendSpecificError, BufferSize, Data, DefaultStreamConfigError, DeviceId, DeviceIdError, + DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, SampleFormat, SampleRate, + StreamConfig, SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, COMMON_SAMPLE_RATES, }; use std::ffi::OsString; @@ -58,6 +58,10 @@ impl DeviceTrait for Device { Device::name(self) } + fn id(&self) -> Result { + Device::id(self) + } + fn supports_input(&self) -> bool { self.data_flow() == Audio::eCapture } @@ -322,6 +326,18 @@ impl Device { } } + fn id(&self) -> Result { + unsafe { + match self.device.GetId() { + Ok(pwstr) => match pwstr.to_string() { + Ok(id_str) => Ok(DeviceId::WASAPI(id_str)), + Err(_e) => Err(DeviceIdError::ParseError), + }, + Err(e) => Err(DeviceIdError::BackendSpecific { err: e.into() }), + } + } + } + #[inline] fn from_immdevice(device: Audio::IMMDevice) -> Self { Device { diff --git a/src/host/webaudio/mod.rs b/src/host/webaudio/mod.rs index fb835b735..e9152ed3f 100644 --- a/src/host/webaudio/mod.rs +++ b/src/host/webaudio/mod.rs @@ -7,10 +7,11 @@ use self::wasm_bindgen::JsCast; use self::web_sys::{AudioContext, AudioContextOptions}; 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 std::ops::DerefMut; use std::sync::{Arc, Mutex, RwLock}; @@ -84,6 +85,11 @@ impl Device { Ok("Default Device".to_owned()) } + #[inline] + fn id(&self) -> Result { + Ok(DeviceId::WebAudio("default".to_string())) + } + #[inline] fn supported_input_configs( &self, @@ -142,6 +148,11 @@ impl DeviceTrait for Device { Device::name(self) } + #[inline] + fn id(&self) -> Result { + Device::id(self) + } + #[inline] fn supported_input_configs( &self, diff --git a/src/lib.rs b/src/lib.rs index 6843d6e7b..eca1e963c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -220,6 +220,65 @@ where /// one frame contains two samples (left and right channels). pub type FrameCount = u32; +/// The device ID of the audio device, on supported OSs +/// Currently only supports macOS and Windows (WASAPI) + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum DeviceId { + CoreAudio(String), + WASAPI(String), + ASIO(String), + ALSA(String), + AAudio(i32), + Jack(String), + WebAudio(String), + Emscripten(String), + IOS(String), + Null, +} + +impl std::fmt::Display for DeviceId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DeviceId::WASAPI(guid) => write!(f, "wasapi:{}", guid), + DeviceId::ASIO(guid) => write!(f, "asio:{}", guid), + DeviceId::CoreAudio(uid) => write!(f, "coreaudio:{}", uid), + DeviceId::ALSA(pcm_id) => write!(f, "alsa:{}", pcm_id), + DeviceId::AAudio(id) => write!(f, "aaudio:{}", id), + DeviceId::Jack(name) => write!(f, "jack:{}", name), + DeviceId::WebAudio(default) => write!(f, "webaudio:{}", default), + DeviceId::Emscripten(default) => write!(f, "emscripten:{}", default), + DeviceId::IOS(default) => write!(f, "ios:{}", default), + DeviceId::Null => write!(f, "null:null"), + } + } +} + +impl std::str::FromStr for DeviceId { + type Err = DeviceIdError; + + fn from_str(s: &str) -> Result { + let (platform, data) = s.split_once(':').ok_or(DeviceIdError::ParseError)?; + + match platform { + "wasapi" => Ok(DeviceId::WASAPI(data.to_string())), + "asio" => Ok(DeviceId::ASIO(data.to_string())), + "coreaudio" => Ok(DeviceId::CoreAudio(data.to_string())), + "alsa" => Ok(DeviceId::ALSA(data.to_string())), + "aaudio" => { + let id = data.parse().map_err(|_| DeviceIdError::ParseError)?; + Ok(DeviceId::AAudio(id)) + } + "jack" => Ok(DeviceId::Jack(data.to_string())), + "webaudio" => Ok(DeviceId::WebAudio(data.to_string())), + "emscripten" => Ok(DeviceId::Emscripten(data.to_string())), + "ios" => Ok(DeviceId::IOS(data.to_string())), + "null" => Ok(DeviceId::Null), + &_ => todo!("implement DeviceId::FromStr for {platform}"), + } + } +} + /// The buffer size requests the callback size for audio streams. /// /// This controls the approximate size of the audio buffer passed to your callback. diff --git a/src/platform/mod.rs b/src/platform/mod.rs index fd12eaaac..503f17e01 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -314,6 +314,15 @@ macro_rules! impl_platform_host { } } + fn id(&self) -> Result { + match self.0 { + $( + $(#[cfg($feat)])? + DeviceInner::$HostVariant(ref d) => d.id(), + )* + } + } + fn supports_input(&self) -> bool { match self.0 { $( diff --git a/src/traits.rs b/src/traits.rs index 2f1bd3469..5b702f3bc 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -3,10 +3,10 @@ use std::time::Duration; use crate::{ - BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError, - InputCallbackInfo, InputDevices, OutputCallbackInfo, OutputDevices, PauseStreamError, - PlayStreamError, SampleFormat, SizedSample, StreamConfig, StreamError, SupportedStreamConfig, - SupportedStreamConfigRange, SupportedStreamConfigsError, + BuildStreamError, Data, DefaultStreamConfigError, DeviceId, DeviceIdError, DeviceNameError, + DevicesError, InputCallbackInfo, InputDevices, OutputCallbackInfo, OutputDevices, + PauseStreamError, PlayStreamError, SampleFormat, SizedSample, StreamConfig, StreamError, + SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, }; /// A [`Host`] provides access to the available audio devices on the system. @@ -44,6 +44,15 @@ pub trait HostTrait { /// Can be empty if the system does not support audio in general. fn devices(&self) -> Result; + /// Fetches a [`Device`](DeviceTrait) based on a [`DeviceId`](DeviceId) if available + /// + /// Returns `None` if no device matching the id is found + fn device_by_id(&self, id: &DeviceId) -> Option { + self.devices() + .ok()? + .find(|device| device.id().ok().as_ref() == Some(id)) + } + /// The default input audio device on the system. /// /// Returns `None` if no input device is available. @@ -89,6 +98,9 @@ pub trait DeviceTrait { /// The human-readable name of the device. fn name(&self) -> Result; + /// The device-id of the device. (For supported OSs only) + fn id(&self) -> Result; + /// True if the device supports audio input, otherwise false fn supports_input(&self) -> bool { self.supported_input_configs()