#![allow(unsafe_code)]
use crate::{AudioObject, CoreAudioError, Device, ErrorKind, errors::OSStatusCheck, property::DEVICE_UID};
use std::{ffi::CStr, sync::atomic::{AtomicU32, Ordering}};
use core_foundation::{array::CFArray, base::{CFType, TCFType}, boolean::CFBoolean, dictionary::CFDictionary, string::CFString};
use objc2_core_audio::{
AudioHardwareCreateAggregateDevice, AudioHardwareDestroyAggregateDevice, AudioObjectID,
kAudioAggregateDeviceIsPrivateKey, kAudioAggregateDeviceIsStackedKey,
kAudioAggregateDeviceMainSubDeviceKey, kAudioAggregateDeviceNameKey,
kAudioAggregateDeviceSubDeviceListKey, kAudioAggregateDeviceUIDKey,
kAudioSubDeviceDriftCompensationKey, kAudioSubDeviceUIDKey,
};
static NEXT_UID: AtomicU32 = AtomicU32::new(0);
pub struct DeviceAggregateDevice {
device: AudioObject<Device>,
}
impl DeviceAggregateDevice {
pub fn new(
name: &str,
devices: Vec<AudioObject<Device>>,
private: bool,
) -> Result<Self, CoreAudioError> {
let uids = devices
.iter()
.map(|device| device.get_property(DEVICE_UID))
.collect::<Result<Vec<String>, CoreAudioError>>()?;
let description = aggregate_description(name, &next_uid(), &uids, private)
.ok_or_else(|| CoreAudioError::from_error_kind(ErrorKind::BadDevice))?;
let mut id: AudioObjectID = 0;
unsafe {
AudioHardwareCreateAggregateDevice(
&*description.as_concrete_TypeRef().cast(),
(&mut id).into(),
).check()?;
}
Ok(Self { device: AudioObject::<Device>::from(id) })
}
pub fn device(&self) -> AudioObject<Device> {
self.device
}
}
impl Drop for DeviceAggregateDevice {
fn drop(&mut self) {
unsafe {
AudioHardwareDestroyAggregateDevice(self.device.id());
}
}
}
fn key(key: &CStr) -> CFString {
CFString::new(&key.to_string_lossy())
}
fn next_uid() -> String {
format!(
"coreaudio.aggregate.{}.{}",
std::process::id(),
NEXT_UID.fetch_add(1, Ordering::Relaxed),
)
}
fn aggregate_description(
name: &str,
uid: &str,
device_uids: &[String],
private: bool,
) -> Option<CFDictionary<CFString, CFType>> {
let main = device_uids.first()?;
let sub_devices: Vec<CFDictionary<CFString, CFType>> = device_uids
.iter()
.enumerate()
.map(|(index, device_uid)| {
let drift_corrected = if index == 0 {
CFBoolean::false_value()
} else {
CFBoolean::true_value()
};
CFDictionary::from_CFType_pairs(&[
(key(kAudioSubDeviceUIDKey), CFString::new(device_uid).as_CFType()),
(key(kAudioSubDeviceDriftCompensationKey), drift_corrected.as_CFType()),
])
})
.collect();
let private = if private {
CFBoolean::true_value().as_CFType()
} else {
CFBoolean::false_value().as_CFType()
};
Some(CFDictionary::from_CFType_pairs(&[
(key(kAudioAggregateDeviceNameKey), CFString::new(name).as_CFType()),
(key(kAudioAggregateDeviceUIDKey), CFString::new(uid).as_CFType()),
(key(kAudioAggregateDeviceMainSubDeviceKey), CFString::new(main).as_CFType()),
(key(kAudioAggregateDeviceIsPrivateKey), private),
(key(kAudioAggregateDeviceIsStackedKey), CFBoolean::false_value().as_CFType()),
(key(kAudioAggregateDeviceSubDeviceListKey), CFArray::from_CFTypes(&sub_devices).as_CFType()),
]))
}