hal.rs (2836B)
1 //! Device enumeration. Read-only HAL queries — no special entitlement or TCC consent 2 //! required, so this works the moment the daemon starts. 3 4 use anyhow::Result; 5 use hydra_ipc::AudioDevice; 6 7 use super::{addr, get_array, get_cfstring, get_scalar, scope, sel, AudioObjectID, ELEMENT_MAIN, SYSTEM_OBJECT}; 8 9 /// The system's current default output device, or `Ok(0)` if none is set. 10 pub fn default_output_device() -> AudioObjectID { 11 unsafe { get_scalar(SYSTEM_OBJECT, &addr(sel::DEFAULT_OUTPUT, scope::GLOBAL, ELEMENT_MAIN)).unwrap_or(0) } 12 } 13 14 /// Enumerate every audio device CoreAudio knows about. 15 pub fn list_devices() -> Result<Vec<AudioDevice>> { 16 let default_out = default_output_device(); 17 let ids: Vec<AudioObjectID> = 18 unsafe { get_array(SYSTEM_OBJECT, &addr(sel::DEVICES, scope::GLOBAL, ELEMENT_MAIN))? }; 19 20 let mut devices = Vec::with_capacity(ids.len()); 21 for id in ids { 22 let name = device_name(id); 23 let uid = device_uid(id); 24 devices.push(AudioDevice { 25 uid, 26 name, 27 input_channels: channel_count(id, scope::INPUT), 28 output_channels: channel_count(id, scope::OUTPUT), 29 is_default_output: id == default_out, 30 }); 31 } 32 Ok(devices) 33 } 34 35 /// A device's human-readable name (falls back to a placeholder). 36 pub fn device_name(id: AudioObjectID) -> String { 37 unsafe { get_cfstring(id, &addr(sel::NAME, scope::GLOBAL, ELEMENT_MAIN)) } 38 .unwrap_or_else(|_| format!("device {id}")) 39 } 40 41 /// A device's persistent UID (stable across reboots; used to re-resolve devices). 42 pub fn device_uid(id: AudioObjectID) -> String { 43 unsafe { get_cfstring(id, &addr(sel::DEVICE_UID, scope::GLOBAL, ELEMENT_MAIN)) }.unwrap_or_default() 44 } 45 46 /// Total channels a device exposes in the given scope (`scope::INPUT` / `scope::OUTPUT`), 47 /// summed across all its streams. Returns 0 if the device has none. 48 pub fn channel_count(id: AudioObjectID, scope: u32) -> u32 { 49 use std::ptr; 50 let a = addr(sel::STREAM_CONFIG, scope, ELEMENT_MAIN); 51 unsafe { 52 let size = match super::data_size(id, &a) { 53 Ok(s) if s > 0 => s, 54 _ => return 0, 55 }; 56 let mut buf = vec![0u8; size]; 57 let mut io = size as u32; 58 let st = coreaudio_sys::AudioObjectGetPropertyData( 59 id, 60 &a, 61 0, 62 ptr::null(), 63 &mut io, 64 buf.as_mut_ptr() as *mut _, 65 ); 66 if st != 0 { 67 return 0; 68 } 69 // Reinterpret the bytes as an AudioBufferList and sum each buffer's channels. 70 let abl = buf.as_ptr() as *const coreaudio_sys::AudioBufferList; 71 let n = (*abl).mNumberBuffers as usize; 72 let first = (*abl).mBuffers.as_ptr(); 73 (0..n).map(|i| (*first.add(i)).mNumberChannels).sum() 74 } 75 }