use std::ffi::c_void;
use std::ptr::NonNull;
use flexaudio_core::clock::monotonic_now_ns;
use flexaudio_core::types::Error;
use objc2_core_audio::{
kAudioHardwarePropertyTranslatePIDToProcessObject, kAudioObjectPropertyElementMain,
kAudioObjectPropertyScopeGlobal, kAudioObjectSystemObject, kAudioTapPropertyFormat,
AudioObjectGetPropertyData, AudioObjectID, AudioObjectPropertyAddress,
};
use objc2_core_audio_types::{kAudioFormatFlagIsFloat, AudioStreamBasicDescription};
pub(crate) const NO_ERR: i32 = 0;
pub(crate) const FALLBACK_FORMAT: (u32, u16) = (48_000, 2);
pub(crate) fn now_ns() -> i64 {
monotonic_now_ns()
}
pub(crate) fn map_os_status(ctx: &str, status: i32) -> Error {
const ILLEGAL_OPERATION: i32 = 0x6e6f7065; const NOT_RUNNING: i32 = 0x73746f70; const BAD_OBJECT: i32 = 0x216f626a; const BAD_DEVICE: i32 = 0x21646576;
match status {
ILLEGAL_OPERATION => Error::PermissionDenied,
BAD_DEVICE => Error::DeviceNotFound,
NOT_RUNNING => Error::Backend(format!("{ctx}: CoreAudio not running (OSStatus 'stop')")),
BAD_OBJECT => Error::Backend(format!("{ctx}: bad audio object (OSStatus '!obj')")),
other => {
let be = (other as u32).to_be_bytes();
if be.iter().all(|&b| (0x20..=0x7e).contains(&b)) {
Error::Backend(format!(
"{ctx}: OSStatus '{}' ({other})",
String::from_utf8_lossy(&be)
))
} else {
Error::Backend(format!("{ctx}: OSStatus {other}"))
}
}
}
}
fn global_address(selector: u32) -> AudioObjectPropertyAddress {
AudioObjectPropertyAddress {
mSelector: selector,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMain,
}
}
pub(crate) fn translate_pid_to_object(pid: i32) -> Result<AudioObjectID, Error> {
let address = global_address(kAudioHardwarePropertyTranslatePIDToProcessObject);
let mut out_object: AudioObjectID = 0;
let mut size = core::mem::size_of::<AudioObjectID>() as u32;
let status = unsafe {
AudioObjectGetPropertyData(
kAudioObjectSystemObject as AudioObjectID,
NonNull::from(&address),
core::mem::size_of::<i32>() as u32,
(&pid as *const i32).cast::<c_void>(),
NonNull::from(&mut size),
NonNull::new_unchecked((&mut out_object as *mut AudioObjectID).cast::<c_void>()),
)
};
if status != NO_ERR {
return Err(map_os_status(
"AudioObjectGetPropertyData(TranslatePIDToProcessObject)",
status,
));
}
Ok(out_object)
}
fn read_tap_asbd(tap_id: AudioObjectID) -> Option<AudioStreamBasicDescription> {
let address = global_address(kAudioTapPropertyFormat);
let mut asbd: AudioStreamBasicDescription = unsafe { core::mem::zeroed() };
let mut size = core::mem::size_of::<AudioStreamBasicDescription>() as u32;
let status = unsafe {
AudioObjectGetPropertyData(
tap_id,
NonNull::from(&address),
0,
core::ptr::null(),
NonNull::from(&mut size),
NonNull::new_unchecked(
(&mut asbd as *mut AudioStreamBasicDescription).cast::<c_void>(),
),
)
};
if status != NO_ERR {
return None;
}
Some(asbd)
}
pub(crate) fn tap_native_format(tap_id: AudioObjectID) -> Option<(u32, u16)> {
let asbd = read_tap_asbd(tap_id)?;
let rate = asbd.mSampleRate as u32;
let channels = asbd.mChannelsPerFrame as u16;
if rate == 0 || channels == 0 {
return None;
}
Some((rate, channels))
}
pub(crate) fn tap_format_is_float(tap_id: AudioObjectID) -> Option<bool> {
let asbd = read_tap_asbd(tap_id)?;
Some((asbd.mFormatFlags & kAudioFormatFlagIsFloat) != 0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn map_os_status_maps_known_codes() {
assert!(matches!(
map_os_status("x", 0x6e6f7065),
Error::PermissionDenied
));
assert!(matches!(
map_os_status("x", 0x21646576),
Error::DeviceNotFound
));
assert!(matches!(map_os_status("x", 0x73746f70), Error::Backend(_)));
let e = map_os_status("ctx", i32::from_be_bytes(*b"abcd"));
assert!(format!("{e}").contains("abcd"));
}
#[test]
fn fallback_format_is_48k_stereo() {
assert_eq!(FALLBACK_FORMAT, (48_000, 2));
}
}