diff --git a/CHANGELOG.md b/CHANGELOG.md index bf88d2465..ac726bfe1 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 a1e48964b..3da8753ef 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, }; @@ -335,6 +335,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 075a26e9b..6cbb004b0 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 { @@ -369,6 +373,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 521cfb376..aba697804 100644 --- a/src/host/coreaudio/ios/mod.rs +++ b/src/host/coreaudio/ios/mod.rs @@ -14,10 +14,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::{ @@ -70,6 +71,10 @@ impl Device { Ok("Default Device".to_owned()) } + fn id(&self) -> Result { + Ok(DeviceId::IOS("default".to_string())) + } + #[inline] fn supported_input_configs( &self, @@ -113,6 +118,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 154a4b1f1..337bb0f0c 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, kAudioDevicePropertyNominalSampleRate, @@ -28,6 +30,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, @@ -43,6 +47,7 @@ use std::time::{Duration, Instant}; use super::invoke_error_callback; use super::property_listener::AudioObjectPropertyListener; use coreaudio::audio_unit::macos_helpers::get_device_name; + /// 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( @@ -261,6 +266,10 @@ impl DeviceTrait for Device { Device::name(self) } + fn id(&self) -> Result { + Device::id(self) + } + fn supported_input_configs( &self, ) -> Result { @@ -355,6 +364,44 @@ impl Device { }) } + fn id(&self) -> Result { + let property_address = AudioObjectPropertyAddress { + mSelector: kAudioDevicePropertyDeviceUID, + mScope: kAudioObjectPropertyScopeGlobal, + mElement: kAudioObjectPropertyElementMain, + }; + + // CFString is copied from the audio object, use wrap_under_create_rule + let mut uid: *mut CFString = std::ptr::null_mut(); + let data_size = size_of::<*mut CFString>() as u32; + + // SAFETY: AudioObjectGetPropertyData is documented to write a CFString pointer + // for kAudioDevicePropertyDeviceUID. We check the status code before use. + 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)?; + + // SAFETY: We verified uid is non-null and the status was successful + if !uid.is_null() { + let uid_string = unsafe { CFString::wrap_under_create_rule(uid).to_string() }; + Ok(DeviceId::CoreAudio(uid_string)) + } else { + Err(DeviceIdError::BackendSpecific { + err: BackendSpecificError { + description: "Device UID is null".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 ed276eca0..f49d2d63b 100644 --- a/src/host/coreaudio/mod.rs +++ b/src/host/coreaudio/mod.rs @@ -3,6 +3,7 @@ use objc2_core_audio_types::{ kAudioFormatLinearPCM, AudioStreamBasicDescription, }; +use crate::host::coreaudio::macos::Stream; use crate::DefaultStreamConfigError; use crate::{BuildStreamError, SupportedStreamConfigsError}; @@ -20,10 +21,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 dafc041af..b8efb3bc5 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`. @@ -75,6 +75,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, @@ -150,6 +155,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 ba96e8662..adedc288a 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, @@ -160,6 +164,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 16d83fb32..ba71635b1 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, }; @@ -50,6 +50,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 01ea5967e..a3a60b967 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,22 @@ 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::BackendSpecific { + err: BackendSpecificError { + description: format!("Failed to convert device ID to string: {}", e), + }, + }), + }, + 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 ec4ed560f..29be46b80 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}; @@ -90,6 +91,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, @@ -148,6 +154,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 5acd3b951..149bae1c8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -223,6 +223,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()