mod.rs (4966B)
1 //! Thin, safe-ish wrappers over the CoreAudio HAL C API. 2 //! 3 //! We lean on `coreaudio-sys` for the stable base types/functions and `core-foundation` 4 //! for CFString/CFDictionary, but spell out the property selectors ourselves as 5 //! four-char-codes — that keeps us independent of whatever the pinned SDK happens to 6 //! export, which matters for the macOS 14.4+ process-tap selectors. 7 8 pub mod hal; 9 pub mod process; 10 pub mod shim; 11 12 use anyhow::{bail, Result}; 13 use coreaudio_sys as sys; 14 use std::mem; 15 use std::ptr; 16 17 pub use sys::AudioObjectID; 18 19 /// The well-known system object that owns the device/process lists. 20 pub const SYSTEM_OBJECT: AudioObjectID = sys::kAudioObjectSystemObject as AudioObjectID; 21 22 /// Build a four-char-code constant the way CoreAudio defines its selectors. 23 pub const fn fourcc(s: &[u8; 4]) -> u32 { 24 ((s[0] as u32) << 24) | ((s[1] as u32) << 16) | ((s[2] as u32) << 8) | (s[3] as u32) 25 } 26 27 /// Property selectors (verified four-char-codes from `AudioHardware.h`). 28 pub mod sel { 29 use super::fourcc; 30 pub const DEVICES: u32 = fourcc(b"dev#"); 31 pub const DEFAULT_OUTPUT: u32 = fourcc(b"dOut"); 32 pub const DEFAULT_INPUT: u32 = fourcc(b"dIn "); 33 pub const TRANSLATE_UID_TO_DEVICE: u32 = fourcc(b"uidd"); 34 pub const DEVICE_UID: u32 = fourcc(b"uid "); 35 pub const NAME: u32 = fourcc(b"lnam"); 36 pub const STREAM_CONFIG: u32 = fourcc(b"slay"); 37 pub const NOMINAL_SAMPLE_RATE: u32 = fourcc(b"nsrt"); 38 // Process taps (macOS 14.4+). 39 pub const PROCESS_LIST: u32 = fourcc(b"prs#"); 40 pub const TRANSLATE_PID_TO_PROCESS: u32 = fourcc(b"id2p"); 41 pub const PROCESS_PID: u32 = fourcc(b"ppid"); 42 pub const PROCESS_BUNDLE_ID: u32 = fourcc(b"pbid"); 43 } 44 45 /// Property scopes. 46 pub mod scope { 47 use super::fourcc; 48 pub const GLOBAL: u32 = fourcc(b"glob"); 49 pub const INPUT: u32 = fourcc(b"inpt"); 50 pub const OUTPUT: u32 = fourcc(b"outp"); 51 } 52 53 /// The "main"/master element. 54 pub const ELEMENT_MAIN: u32 = 0; 55 56 /// Construct a property address (the most common shape: global scope, main element). 57 pub fn addr(selector: u32, scope: u32, element: u32) -> sys::AudioObjectPropertyAddress { 58 sys::AudioObjectPropertyAddress { mSelector: selector, mScope: scope, mElement: element } 59 } 60 61 /// Byte size CoreAudio reports for a property. 62 /// 63 /// # Safety 64 /// `obj` must be a valid AudioObjectID and `a` a valid address. 65 pub unsafe fn data_size(obj: AudioObjectID, a: &sys::AudioObjectPropertyAddress) -> Result<usize> { 66 let mut size: u32 = 0; 67 let st = sys::AudioObjectGetPropertyDataSize(obj, a, 0, ptr::null(), &mut size); 68 if st != 0 { 69 bail!("AudioObjectGetPropertyDataSize(sel={:#x}) -> OSStatus {st}", a.mSelector); 70 } 71 Ok(size as usize) 72 } 73 74 /// Read a property whose value is an array of POD `T` (e.g. a list of object IDs). 75 /// 76 /// # Safety 77 /// `T` must be a plain-old-data type matching the property's element layout. 78 pub unsafe fn get_array<T: Copy>(obj: AudioObjectID, a: &sys::AudioObjectPropertyAddress) -> Result<Vec<T>> { 79 let size = data_size(obj, a)?; 80 let count = size / mem::size_of::<T>(); 81 let mut buf: Vec<T> = Vec::with_capacity(count); 82 let mut io = size as u32; 83 let st = sys::AudioObjectGetPropertyData(obj, a, 0, ptr::null(), &mut io, buf.as_mut_ptr() as *mut _); 84 if st != 0 { 85 bail!("AudioObjectGetPropertyData(sel={:#x}) -> OSStatus {st}", a.mSelector); 86 } 87 buf.set_len(io as usize / mem::size_of::<T>()); 88 Ok(buf) 89 } 90 91 /// Read a fixed-size scalar property (e.g. a `u32`, `f64`, or an `AudioObjectID`). 92 /// 93 /// # Safety 94 /// `T` must match the property's exact value layout. 95 pub unsafe fn get_scalar<T: Copy>(obj: AudioObjectID, a: &sys::AudioObjectPropertyAddress) -> Result<T> { 96 let mut val: mem::MaybeUninit<T> = mem::MaybeUninit::zeroed(); 97 let mut size = mem::size_of::<T>() as u32; 98 let st = sys::AudioObjectGetPropertyData(obj, a, 0, ptr::null(), &mut size, val.as_mut_ptr() as *mut _); 99 if st != 0 { 100 bail!("AudioObjectGetPropertyData(sel={:#x}) -> OSStatus {st}", a.mSelector); 101 } 102 Ok(val.assume_init()) 103 } 104 105 /// Read a CFString property and return it as an owned Rust `String`. 106 /// 107 /// # Safety 108 /// `a` must address a property whose value is a `CFStringRef`. 109 pub unsafe fn get_cfstring(obj: AudioObjectID, a: &sys::AudioObjectPropertyAddress) -> Result<String> { 110 use core_foundation::base::TCFType; 111 use core_foundation::string::CFString; 112 use core_foundation_sys::string::CFStringRef; 113 114 let mut s: CFStringRef = ptr::null(); 115 let mut size = mem::size_of::<CFStringRef>() as u32; 116 let st = sys::AudioObjectGetPropertyData(obj, a, 0, ptr::null(), &mut size, &mut s as *mut _ as *mut _); 117 if st != 0 || s.is_null() { 118 bail!("AudioObjectGetPropertyData(cfstring, sel={:#x}) -> OSStatus {st}", a.mSelector); 119 } 120 // CoreAudio follows the create rule for returned CFStrings: we own this reference. 121 let cf = CFString::wrap_under_create_rule(s); 122 Ok(cf.to_string()) 123 }