Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 115 additions & 5 deletions src/audio_unit/macos_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ use objc2_core_audio::{
kAudioDevicePropertyAvailableNominalSampleRates, kAudioDevicePropertyDeviceIsAlive,
kAudioDevicePropertyDeviceNameCFString, kAudioDevicePropertyHogMode,
kAudioDevicePropertyNominalSampleRate, kAudioDevicePropertyScopeOutput,
kAudioDevicePropertyStreamConfiguration, kAudioHardwareNoError,
kAudioHardwarePropertyDefaultInputDevice, kAudioHardwarePropertyDefaultOutputDevice,
kAudioHardwarePropertyDevices, kAudioObjectPropertyElementMaster,
kAudioObjectPropertyElementWildcard, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyScopeInput, kAudioObjectPropertyScopeOutput, kAudioObjectSystemObject,
kAudioDevicePropertyStreamConfiguration, kAudioDevicePropertyTransportType,
kAudioHardwareNoError, kAudioHardwarePropertyDefaultInputDevice,
kAudioHardwarePropertyDefaultOutputDevice, kAudioHardwarePropertyDevices,
kAudioObjectPropertyElementMaster, kAudioObjectPropertyElementWildcard,
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyScopeInput,
kAudioObjectPropertyScopeOutput, kAudioObjectSystemObject,
kAudioStreamPropertyAvailablePhysicalFormats, kAudioStreamPropertyPhysicalFormat,
AudioDeviceID, AudioObjectAddPropertyListener, AudioObjectGetPropertyData,
AudioObjectGetPropertyDataSize, AudioObjectID, AudioObjectPropertyAddress,
Expand Down Expand Up @@ -188,6 +189,37 @@ fn test_get_audio_device_ids() {
let _ = get_audio_device_ids().expect("Failed to get audio device ids");
}

#[test]
fn test_get_available_sample_rates() {
if let Some(device_id) = get_default_device_id(false) {
let rates = get_available_sample_rates(device_id).expect("Failed to get sample rates");
assert!(
!rates.is_empty(),
"Default output device should support at least one sample rate"
);
for range in &rates {
assert!(
range.mMinimum > 0.0,
"Sample rate minimum should be positive"
);
assert!(
range.mMaximum >= range.mMinimum,
"Maximum should be >= minimum"
);
}
}
}

#[test]
fn test_get_device_transport_type() {
if let Some(device_id) = get_default_device_id(false) {
let transport = get_device_transport_type(device_id).expect("Failed to get transport type");
// Transport type should be a non-zero FourCC value (or 0 for unknown)
// The default output device typically has a known transport type
let _ = transport;
}
}

#[test]
fn test_get_audio_device_ids_for_scope() {
for scope in &[
Expand Down Expand Up @@ -577,6 +609,84 @@ pub fn get_supported_physical_stream_formats(
Ok(allformats)
}

/// Get the available nominal sample rate ranges for a device.
///
/// Returns a vector of [`AudioValueRange`] where each entry describes a supported range.
/// For devices that support discrete rates, `mMinimum` and `mMaximum` will be equal.
/// For devices that support continuous ranges, they will differ.
pub fn get_available_sample_rates(device_id: AudioDeviceID) -> Result<Vec<AudioValueRange>, Error> {
let property_address = AudioObjectPropertyAddress {
mSelector: kAudioDevicePropertyAvailableNominalSampleRates,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};

let mut data_size = 0u32;
// SAFETY: AudioObjectGetPropertyDataSize is a CoreAudio C API that reads
// the size of the property data into data_size. All pointers passed are
// valid stack references via NonNull::from.
let status = unsafe {
AudioObjectGetPropertyDataSize(
device_id,
NonNull::from(&property_address),
0,
null(),
NonNull::from(&mut data_size),
)
};
Error::from_os_status(status)?;

let n_ranges = data_size as usize / mem::size_of::<AudioValueRange>();
let mut ranges: Vec<AudioValueRange> = Vec::with_capacity(n_ranges);
// SAFETY: AudioObjectGetPropertyData writes exactly n_ranges elements
// (validated by data_size from the previous call) into the pre-allocated
// buffer. AudioValueRange is a plain C struct with no drop semantics.
let status = unsafe {
ranges.set_len(n_ranges);
AudioObjectGetPropertyData(
device_id,
NonNull::from(&property_address),
0,
null(),
NonNull::from(&data_size),
NonNull::new(ranges.as_mut_ptr()).unwrap().cast(),
)
};
Error::from_os_status(status)?;
Ok(ranges)
}

/// Get the transport type of a device.
///
/// The returned value is one of the `kAudioDeviceTransportType*` constants
/// (e.g. `kAudioDeviceTransportTypeUSB`, `kAudioDeviceTransportTypeBuiltIn`).
pub fn get_device_transport_type(device_id: AudioDeviceID) -> Result<u32, Error> {
let property_address = AudioObjectPropertyAddress {
mSelector: kAudioDevicePropertyTransportType,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};

let mut transport_type: u32 = 0;
let data_size = mem::size_of::<u32>() as u32;
// SAFETY: AudioObjectGetPropertyData is a CoreAudio C API that reads
// the transport type (a u32) into transport_type. All pointers passed
// are valid stack references via NonNull::from. data_size matches the
// size of the output buffer.
let status = unsafe {
AudioObjectGetPropertyData(
device_id,
NonNull::from(&property_address),
0,
null(),
NonNull::from(&data_size),
NonNull::from(&mut transport_type).cast(),
)
};
Error::from_os_status(status)?;
Ok(transport_type)
}

/// Changing the sample rate is an asynchronous process.
/// A RateListener can be used to get notified when the rate is changed.
pub struct RateListener {
Expand Down
Loading