use std::{
fmt,
mem::{self, size_of},
ptr::{null, NonNull},
sync::{
mpsc::{channel, RecvTimeoutError},
Arc, Mutex,
},
time::{Duration, Instant},
};
use coreaudio::audio_unit::{
audio_format::LinearPcmFlags,
macos_helpers::{
audio_unit_from_device_id_uninitialized, find_matching_physical_format, get_device_name,
set_device_physical_stream_format, RateListener,
},
render_callback::{self, data},
AudioUnit, Element, SampleFormat as CoreAudioSampleFormat, Scope, StreamFormat,
};
use objc2_audio_toolbox::{
kAudioOutputUnitProperty_CurrentDevice, kAudioUnitProperty_StreamFormat,
};
use objc2_core_audio::{
kAudioAggregateDeviceClassID, kAudioDevicePropertyAvailableNominalSampleRates,
kAudioDevicePropertyBufferFrameSize, kAudioDevicePropertyBufferFrameSizeRange,
kAudioDevicePropertyDeviceUID, kAudioDevicePropertyLatency,
kAudioDevicePropertyNominalSampleRate, kAudioDevicePropertySafetyOffset,
kAudioDevicePropertyStreamConfiguration, kAudioDevicePropertyStreamFormat,
kAudioObjectPropertyClass, kAudioObjectPropertyElementMain, kAudioObjectPropertyElementMaster,
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyScopeInput,
kAudioObjectPropertyScopeOutput, AudioClassID, AudioDeviceID, AudioObjectGetPropertyData,
AudioObjectGetPropertyDataSize, AudioObjectID, AudioObjectPropertyAddress,
AudioObjectPropertyScope, AudioObjectSetPropertyData,
};
use objc2_core_audio_types::{
AudioBuffer, AudioBufferList, AudioStreamBasicDescription, AudioValueRange,
};
use objc2_core_foundation::{CFString, Type};
pub use super::enumerate::{SupportedInputConfigs, SupportedOutputConfigs};
use super::{
asbd_from_config, check_os_status, host_time_to_stream_instant, DefaultOutputMonitor,
DisconnectManager, Monitor, Stream,
};
use crate::{
host::{
coreaudio::macos::{loopback::LoopbackDevice, StreamInner},
frames_to_duration, try_emit_error, ErrorCallbackArc,
},
traits::DeviceTrait,
BufferSize, ChannelCount, Data, DeviceDescription, DeviceDescriptionBuilder, DeviceId, Error,
ErrorKind, FrameCount, InputCallbackInfo, InputStreamTimestamp, InterfaceType,
OutputCallbackInfo, OutputStreamTimestamp, ResultExt, SampleFormat, SampleRate, StreamConfig,
StreamInstant, SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange,
};
fn set_physical_format(
device_id: AudioDeviceID,
sample_rate: SampleRate,
channels: ChannelCount,
sample_format: SampleFormat,
) -> Result<AudioStreamBasicDescription, coreaudio::Error> {
let core_format = match sample_format {
SampleFormat::I8 => CoreAudioSampleFormat::I8,
SampleFormat::I16 => CoreAudioSampleFormat::I16,
SampleFormat::I24 => CoreAudioSampleFormat::I24,
SampleFormat::I32 => CoreAudioSampleFormat::I32,
SampleFormat::F32 => CoreAudioSampleFormat::F32,
_ => return Err(coreaudio::Error::UnsupportedStreamFormat),
};
let stream_format = StreamFormat {
sample_rate: sample_rate as f64,
sample_format: core_format,
flags: LinearPcmFlags::empty(),
channels: channels as u32,
};
let asbd = find_matching_physical_format(device_id, stream_format)
.ok_or(coreaudio::Error::UnsupportedStreamFormat)?;
set_device_physical_stream_format(device_id, asbd).map(|_| asbd)
}
fn set_sample_rate(
audio_device_id: AudioObjectID,
target_sample_rate: SampleRate,
timeout: Option<Duration>,
) -> Result<(), Error> {
let mut property_address = AudioObjectPropertyAddress {
mSelector: kAudioDevicePropertyNominalSampleRate,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
let mut sample_rate: f64 = 0.0;
let mut data_size = mem::size_of::<f64>() as u32;
let status = unsafe {
AudioObjectGetPropertyData(
audio_device_id,
NonNull::from(&property_address),
0,
null(),
NonNull::from(&mut data_size),
NonNull::from(&mut sample_rate).cast(),
)
};
coreaudio::Error::from_os_status(status)?;
if (sample_rate - target_sample_rate as f64).abs() >= 1.0 {
property_address.mSelector = kAudioDevicePropertyAvailableNominalSampleRates;
let mut data_size = 0u32;
let status = unsafe {
AudioObjectGetPropertyDataSize(
audio_device_id,
NonNull::from(&property_address),
0,
null(),
NonNull::from(&mut data_size),
)
};
coreaudio::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);
let status = unsafe {
AudioObjectGetPropertyData(
audio_device_id,
NonNull::from(&property_address),
0,
null(),
NonNull::from(&mut data_size),
NonNull::new(ranges.as_mut_ptr()).unwrap().cast(),
)
};
coreaudio::Error::from_os_status(status)?;
unsafe {
ranges.set_len(n_ranges);
}
let sample_rate = target_sample_rate;
if !ranges
.iter()
.any(|r| sample_rate as f64 >= r.mMinimum && sample_rate as f64 <= r.mMaximum)
{
return Err(Error::with_message(
ErrorKind::UnsupportedConfig,
format!("Sample rate {sample_rate} Hz is not supported"),
));
}
let (sender, receiver) = channel::<f64>();
let mut listener = RateListener::new(audio_device_id, Some(sender));
listener.register()?;
property_address.mSelector = kAudioDevicePropertyNominalSampleRate;
let rate = sample_rate as f64;
let data_size = mem::size_of::<f64>() as u32;
let status = unsafe {
AudioObjectSetPropertyData(
audio_device_id,
NonNull::from(&property_address),
0,
null(),
data_size,
NonNull::from(&rate).cast(),
)
};
coreaudio::Error::from_os_status(status)?;
let mut remaining = timeout.unwrap_or(Duration::from_secs(1));
let start = Instant::now();
loop {
match receiver.recv_timeout(remaining) {
Ok(reported_rate) => {
if (reported_rate - target_sample_rate as f64).abs() < 1.0 {
break;
}
}
Err(RecvTimeoutError::Timeout) => {
return Err(Error::with_message(
ErrorKind::DeviceNotAvailable,
"Sample rate update timed out",
));
}
Err(RecvTimeoutError::Disconnected) => {
return Err(Error::with_message(
ErrorKind::StreamInvalidated,
"Sample rate listener disconnected unexpectedly",
));
}
}
remaining = remaining
.checked_sub(start.elapsed())
.unwrap_or(Duration::ZERO);
}
}
Ok(())
}
#[derive(Clone, Copy)]
enum AudioUnitMode {
Input,
Output,
DefaultOutput,
}
fn audio_unit_from_device(
device: &Device,
mode: AudioUnitMode,
) -> Result<AudioUnit, coreaudio::Error> {
match mode {
AudioUnitMode::DefaultOutput => {
AudioUnit::new_uninitialized(coreaudio::audio_unit::IOType::DefaultOutput)
}
AudioUnitMode::Input => {
audio_unit_from_device_id_uninitialized(device.audio_device_id, true)
}
AudioUnitMode::Output => {
let mut audio_unit =
AudioUnit::new_uninitialized(coreaudio::audio_unit::IOType::HalOutput)?;
audio_unit.set_property(
kAudioOutputUnitProperty_CurrentDevice,
Scope::Global,
Element::Output,
Some(&device.audio_device_id),
)?;
Ok(audio_unit)
}
}
}
fn get_io_buffer_frame_size_range(device_id: AudioDeviceID) -> Result<SupportedBufferSize, Error> {
let property_address = AudioObjectPropertyAddress {
mSelector: kAudioDevicePropertyBufferFrameSizeRange,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
let mut range: AudioValueRange = unsafe { mem::zeroed() };
let mut data_size = mem::size_of::<AudioValueRange>() as u32;
let status = unsafe {
AudioObjectGetPropertyData(
device_id,
NonNull::from(&property_address),
0,
null(),
NonNull::from(&mut data_size),
NonNull::from(&mut range).cast(),
)
};
check_os_status(status)?;
Ok(SupportedBufferSize::Range {
min: range.mMinimum as u32,
max: range.mMaximum as u32,
})
}
impl DeviceTrait for Device {
type SupportedInputConfigs = SupportedInputConfigs;
type SupportedOutputConfigs = SupportedOutputConfigs;
type Stream = Stream;
fn description(&self) -> Result<DeviceDescription, Error> {
Device::description(self)
}
fn id(&self) -> Result<DeviceId, Error> {
Device::id(self)
}
fn supported_input_configs(&self) -> Result<Self::SupportedInputConfigs, Error> {
Device::supported_input_configs(self)
}
fn supported_output_configs(&self) -> Result<Self::SupportedOutputConfigs, Error> {
Device::supported_output_configs(self)
}
fn default_input_config(&self) -> Result<SupportedStreamConfig, Error> {
Device::default_input_config(self)
}
fn default_output_config(&self) -> Result<SupportedStreamConfig, Error> {
Device::default_output_config(self)
}
fn build_input_stream_raw<D, E>(
&self,
config: StreamConfig,
sample_format: SampleFormat,
data_callback: D,
error_callback: E,
timeout: Option<Duration>,
) -> Result<Self::Stream, Error>
where
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
E: FnMut(Error) + Send + 'static,
{
Device::build_input_stream_raw(
self,
config,
sample_format,
data_callback,
error_callback,
timeout,
)
}
fn build_output_stream_raw<D, E>(
&self,
config: StreamConfig,
sample_format: SampleFormat,
data_callback: D,
error_callback: E,
timeout: Option<Duration>,
) -> Result<Self::Stream, Error>
where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
E: FnMut(Error) + Send + 'static,
{
Device::build_output_stream_raw(
self,
config,
sample_format,
data_callback,
error_callback,
timeout,
)
}
}
#[derive(Clone)]
pub struct Device {
pub(crate) audio_device_id: AudioDeviceID,
pub(crate) is_default_output: bool,
}
impl Device {
pub fn new(audio_device_id: AudioDeviceID) -> Self {
Self {
audio_device_id,
is_default_output: false,
}
}
fn is_aggregate_device(&self) -> bool {
let property_address = AudioObjectPropertyAddress {
mSelector: kAudioObjectPropertyClass,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMain,
};
let mut class_id: AudioClassID = 0;
let data_size = size_of::<AudioClassID>() as u32;
let status = unsafe {
AudioObjectGetPropertyData(
self.audio_device_id,
NonNull::from(&property_address),
0,
null(),
NonNull::from(&data_size),
NonNull::from(&mut class_id).cast(),
)
};
status == 0 && class_id == kAudioAggregateDeviceClassID
}
fn description(&self) -> Result<crate::DeviceDescription, Error> {
let name = get_device_name(self.audio_device_id).context("Failed to get device name")?;
let input_configs = self
.supported_input_configs()
.map(|configs| configs.count() as ChannelCount)
.ok();
let output_configs = self
.supported_output_configs()
.map(|configs| configs.count() as ChannelCount)
.ok();
let direction =
crate::device_description::direction_from_counts(input_configs, output_configs);
let mut builder = DeviceDescriptionBuilder::new(name).direction(direction);
if self.is_aggregate_device() {
builder = builder.interface_type(InterfaceType::Aggregate);
}
Ok(builder.build())
}
fn id(&self) -> Result<DeviceId, Error> {
let property_address = AudioObjectPropertyAddress {
mSelector: kAudioDevicePropertyDeviceUID,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMain,
};
let mut uid: *mut CFString = std::ptr::null_mut();
let mut data_size = size_of::<*mut CFString>() as u32;
let status = unsafe {
AudioObjectGetPropertyData(
self.audio_device_id,
NonNull::from(&property_address),
0,
null(),
NonNull::from(&mut data_size),
NonNull::from(&mut uid).cast(),
)
};
check_os_status(status)?;
if !uid.is_null() {
let uid_string = unsafe { CFString::wrap_under_create_rule(uid).to_string() };
Ok(DeviceId::new(
crate::platform::HostId::CoreAudio,
uid_string,
))
} else {
Err(ErrorKind::DeviceNotAvailable.into())
}
}
#[allow(clippy::cast_ptr_alignment)]
fn supported_configs(
&self,
scope: AudioObjectPropertyScope,
) -> Result<SupportedOutputConfigs, Error> {
let mut property_address = AudioObjectPropertyAddress {
mSelector: kAudioDevicePropertyStreamConfiguration,
mScope: scope,
mElement: kAudioObjectPropertyElementMaster,
};
unsafe {
let mut data_size = 0u32;
let status = AudioObjectGetPropertyDataSize(
self.audio_device_id,
NonNull::from(&property_address),
0,
null(),
NonNull::from(&mut data_size),
);
check_os_status(status)?;
let mut audio_buffer_list: Vec<u8> = vec![];
audio_buffer_list.reserve_exact(data_size as usize);
let status = AudioObjectGetPropertyData(
self.audio_device_id,
NonNull::from(&property_address),
0,
null(),
NonNull::from(&mut data_size),
NonNull::new(audio_buffer_list.as_mut_ptr()).unwrap().cast(),
);
check_os_status(status)?;
let audio_buffer_list = audio_buffer_list.as_mut_ptr() as *mut AudioBufferList;
let nb_ptr = core::ptr::addr_of!((*audio_buffer_list).mNumberBuffers);
let n_buffers = core::ptr::read_unaligned(nb_ptr) as usize;
if n_buffers == 0 {
return Ok(vec![].into_iter());
}
let first_buf_ptr =
core::ptr::addr_of!((*audio_buffer_list).mBuffers) as *const AudioBuffer;
let mut n_channels = 0usize;
for i in 0..n_buffers {
let buf_ptr = first_buf_ptr.add(i);
let buf: AudioBuffer = core::ptr::read_unaligned(buf_ptr);
n_channels += buf.mNumberChannels as usize;
}
let sample_format = SampleFormat::F32;
property_address.mSelector = kAudioDevicePropertyAvailableNominalSampleRates;
let mut data_size = 0u32;
let status = AudioObjectGetPropertyDataSize(
self.audio_device_id,
NonNull::from(&property_address),
0,
null(),
NonNull::from(&mut data_size),
);
check_os_status(status)?;
let n_ranges = data_size as usize / mem::size_of::<AudioValueRange>();
let mut ranges: Vec<AudioValueRange> = Vec::with_capacity(n_ranges);
let status = AudioObjectGetPropertyData(
self.audio_device_id,
NonNull::from(&property_address),
0,
null(),
NonNull::from(&mut data_size),
NonNull::new(ranges.as_mut_ptr()).unwrap().cast(),
);
check_os_status(status)?;
ranges.set_len(n_ranges);
#[allow(non_upper_case_globals)]
match scope {
kAudioObjectPropertyScopeInput | kAudioObjectPropertyScopeOutput => {}
_ => {
return Err(Error::with_message(
ErrorKind::UnsupportedOperation,
"Unexpected audio property scope",
))
}
}
let buffer_size = get_io_buffer_frame_size_range(self.audio_device_id)?;
let fmts: Vec<_> = ranges
.iter()
.map(|range| SupportedStreamConfigRange {
channels: n_channels as ChannelCount,
min_sample_rate: range.mMinimum as u32,
max_sample_rate: range.mMaximum as u32,
buffer_size,
sample_format,
})
.collect();
Ok(fmts.into_iter())
}
}
fn supported_input_configs(&self) -> Result<SupportedOutputConfigs, Error> {
self.supported_configs(kAudioObjectPropertyScopeInput)
}
fn supported_output_configs(&self) -> Result<SupportedOutputConfigs, Error> {
self.supported_configs(kAudioObjectPropertyScopeOutput)
}
fn default_config(
&self,
scope: AudioObjectPropertyScope,
) -> Result<SupportedStreamConfig, Error> {
let property_address = AudioObjectPropertyAddress {
mSelector: kAudioDevicePropertyStreamFormat,
mScope: scope,
mElement: kAudioObjectPropertyElementMaster,
};
unsafe {
let mut asbd: AudioStreamBasicDescription = mem::zeroed();
let mut data_size = mem::size_of::<AudioStreamBasicDescription>() as u32;
let status = AudioObjectGetPropertyData(
self.audio_device_id,
NonNull::from(&property_address),
0,
null(),
NonNull::from(&mut data_size),
NonNull::from(&mut asbd).cast(),
);
check_os_status(status)?;
let sample_format = {
let audio_format = coreaudio::audio_unit::AudioFormat::from_format_and_flag(
asbd.mFormatID,
Some(asbd.mFormatFlags),
);
let flags = match audio_format {
Some(coreaudio::audio_unit::AudioFormat::LinearPCM(flags)) => flags,
_ => {
return Err(Error::with_message(
ErrorKind::UnsupportedConfig,
"Audio format is not linear PCM",
))
}
};
let maybe_sample_format =
coreaudio::audio_unit::SampleFormat::from_flags_and_bits_per_sample(
flags,
asbd.mBitsPerChannel,
);
match maybe_sample_format {
Some(coreaudio::audio_unit::SampleFormat::F32) => SampleFormat::F32,
Some(coreaudio::audio_unit::SampleFormat::I16) => SampleFormat::I16,
_ => {
return Err(Error::with_message(
ErrorKind::UnsupportedConfig,
"Sample format is not supported; supported formats are F32 and I16",
))
}
}
};
#[allow(non_upper_case_globals)]
match scope {
kAudioObjectPropertyScopeInput | kAudioObjectPropertyScopeOutput => {}
_ => {
return Err(Error::with_message(
ErrorKind::UnsupportedOperation,
"Unexpected audio property scope",
))
}
}
let buffer_size = get_io_buffer_frame_size_range(self.audio_device_id)?;
let config = SupportedStreamConfig {
sample_rate: asbd.mSampleRate as _,
channels: asbd.mChannelsPerFrame as _,
buffer_size,
sample_format,
};
Ok(config)
}
}
fn default_input_config(&self) -> Result<SupportedStreamConfig, Error> {
self.default_config(kAudioObjectPropertyScopeInput)
}
fn default_output_config(&self) -> Result<SupportedStreamConfig, Error> {
self.default_config(kAudioObjectPropertyScopeOutput)
}
fn supports_input(&self) -> bool {
self.supported_input_configs()
.map(|mut configs| configs.next().is_some())
.unwrap_or(false)
}
}
impl Device {
#[allow(clippy::cast_ptr_alignment)]
#[allow(clippy::while_immutable_condition)]
#[allow(clippy::float_cmp)]
fn build_input_stream_raw<D, E>(
&self,
config: StreamConfig,
sample_format: SampleFormat,
mut data_callback: D,
error_callback: E,
timeout: Option<Duration>,
) -> Result<Stream, Error>
where
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
E: FnMut(Error) + Send + 'static,
{
crate::validate_stream_config(&config)?;
let scope = Scope::Output;
let element = Element::Input;
if set_physical_format(
self.audio_device_id,
config.sample_rate,
config.channels,
sample_format,
)
.is_err()
{
set_sample_rate(self.audio_device_id, config.sample_rate, timeout)?;
}
let mut loopback_aggregate: Option<LoopbackDevice> = None;
let mut audio_unit = if self.supports_input() {
audio_unit_from_device(self, AudioUnitMode::Input)?
} else {
loopback_aggregate.replace(LoopbackDevice::from_device(self)?);
audio_unit_from_device(
&loopback_aggregate.as_ref().unwrap().aggregate_device,
AudioUnitMode::Input,
)?
};
let effective_device_id = loopback_aggregate
.as_ref()
.map(|l| l.aggregate_device.audio_device_id)
.unwrap_or(self.audio_device_id);
configure_stream_format_and_buffer(
&mut audio_unit,
config,
sample_format,
scope,
element,
effective_device_id,
)?;
let error_callback: ErrorCallbackArc = Arc::new(Mutex::new(error_callback));
let error_callback_disconnect = error_callback.clone();
let (bytes_per_channel, sample_rate, device_buffer_frames, extra_latency_frames) =
setup_callback_vars(&audio_unit, config, sample_format, Scope::Input);
type Args = render_callback::Args<data::Raw>;
audio_unit.set_input_callback(move |args: Args| unsafe {
let AudioBuffer {
mNumberChannels: channels,
mDataByteSize: data_byte_size,
mData: data,
} = (*args.data.data).mBuffers[0];
let data = data as *mut ();
let len = data_byte_size as usize / bytes_per_channel;
let data = Data::from_parts(data, len, sample_format);
let callback = match host_time_to_stream_instant(args.time_stamp.mHostTime) {
Err(err) => {
let _ = try_emit_error(&error_callback, err);
return Err(());
}
Ok(cb) => cb,
};
let buffer_frames = len / channels as usize;
let latency_frames =
device_buffer_frames.unwrap_or(buffer_frames) + extra_latency_frames;
let delay = frames_to_duration(latency_frames as FrameCount, sample_rate);
let capture = callback.checked_sub(delay).unwrap_or(StreamInstant::ZERO);
let timestamp = InputStreamTimestamp { callback, capture };
let info = InputCallbackInfo { timestamp };
data_callback(&data, &info);
Ok(())
})?;
audio_unit.initialize()?;
let inner_arc = Arc::new(Mutex::new(StreamInner {
playing: false,
audio_unit,
_device_id: self.audio_device_id,
_loopback_device: loopback_aggregate,
}));
let weak_inner = Arc::downgrade(&inner_arc);
let monitor: Box<dyn Monitor> = Box::new(DisconnectManager::new(
self.audio_device_id,
weak_inner,
error_callback_disconnect,
)?);
let stream = Stream::new(inner_arc, monitor);
stream.signal_ready();
Ok(stream)
}
fn build_output_stream_raw<D, E>(
&self,
config: StreamConfig,
sample_format: SampleFormat,
mut data_callback: D,
error_callback: E,
timeout: Option<Duration>,
) -> Result<Stream, Error>
where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
E: FnMut(Error) + Send + 'static,
{
crate::validate_stream_config(&config)?;
if set_physical_format(
self.audio_device_id,
config.sample_rate,
config.channels,
sample_format,
)
.is_err()
{
set_sample_rate(self.audio_device_id, config.sample_rate, timeout)?;
}
let mode = if self.is_default_output {
AudioUnitMode::DefaultOutput
} else {
AudioUnitMode::Output
};
let mut audio_unit = audio_unit_from_device(self, mode)?;
let scope = Scope::Input;
let element = Element::Output;
configure_stream_format_and_buffer(
&mut audio_unit,
config,
sample_format,
scope,
element,
self.audio_device_id,
)?;
let error_callback: ErrorCallbackArc = Arc::new(Mutex::new(error_callback));
let error_callback_for_render = error_callback.clone();
let (bytes_per_channel, sample_rate, device_buffer_frames, extra_latency_frames) =
setup_callback_vars(&audio_unit, config, sample_format, Scope::Output);
type Args = render_callback::Args<data::Raw>;
audio_unit.set_render_callback(move |args: Args| unsafe {
let AudioBuffer {
mNumberChannels: channels,
mDataByteSize: data_byte_size,
mData: data,
} = (*args.data.data).mBuffers[0];
let data = data as *mut ();
let len = data_byte_size as usize / bytes_per_channel;
let mut data = Data::from_parts(data, len, sample_format);
let callback = match host_time_to_stream_instant(args.time_stamp.mHostTime) {
Err(err) => {
let _ = try_emit_error(&error_callback_for_render, err);
return Err(());
}
Ok(cb) => cb,
};
let buffer_frames = len / channels as usize;
let latency_frames =
device_buffer_frames.unwrap_or(buffer_frames) + extra_latency_frames;
let delay = frames_to_duration(latency_frames as FrameCount, sample_rate);
let playback = callback + delay;
let timestamp = OutputStreamTimestamp { callback, playback };
let info = OutputCallbackInfo { timestamp };
data_callback(&mut data, &info);
Ok(())
})?;
audio_unit.initialize()?;
let inner_arc = Arc::new(Mutex::new(StreamInner {
playing: false,
audio_unit,
_device_id: self.audio_device_id,
_loopback_device: None,
}));
let weak_inner = Arc::downgrade(&inner_arc);
let monitor: Box<dyn Monitor> = if matches!(mode, AudioUnitMode::DefaultOutput) {
Box::new(DefaultOutputMonitor::new(weak_inner, error_callback)?)
} else {
Box::new(DisconnectManager::new(
self.audio_device_id,
weak_inner,
error_callback,
)?)
};
let stream = Stream::new(inner_arc, monitor);
stream.signal_ready();
Ok(stream)
}
}
impl PartialEq for Device {
fn eq(&self, other: &Self) -> bool {
self.audio_device_id == other.audio_device_id
}
}
impl Eq for Device {}
impl fmt::Display for Device {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let desc = self.description().map_err(|_| fmt::Error)?;
f.write_str(desc.name())
}
}
impl std::hash::Hash for Device {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.audio_device_id.hash(state);
}
}
impl fmt::Debug for Device {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Device")
.field("audio_device_id", &self.audio_device_id)
.field("name", &self.description().map(|d| d.name().to_owned()))
.finish()
}
}
fn configure_stream_format_and_buffer(
audio_unit: &mut AudioUnit,
config: StreamConfig,
sample_format: SampleFormat,
scope: Scope,
element: Element,
device_id: AudioDeviceID,
) -> Result<(), Error> {
let asbd = asbd_from_config(config, sample_format);
audio_unit.set_property(kAudioUnitProperty_StreamFormat, scope, element, Some(&asbd))?;
if let BufferSize::Fixed(buffer_size) = config.buffer_size {
if let Ok(SupportedBufferSize::Range { min, max }) =
get_io_buffer_frame_size_range(device_id)
{
if !(min..=max).contains(&buffer_size) {
return Err(Error::with_message(
ErrorKind::UnsupportedConfig,
format!(
"Buffer size {buffer_size} is not in the supported range {min}..={max}"
),
));
}
}
audio_unit.set_property(
kAudioDevicePropertyBufferFrameSize,
Scope::Global,
Element::Output,
Some(&buffer_size),
)?;
}
Ok(())
}
fn get_device_extra_latency_frames(audio_unit: &AudioUnit, scope: Scope) -> usize {
let device_latency: u32 = audio_unit
.get_property(kAudioDevicePropertyLatency, scope, Element::Output)
.unwrap_or(0);
let safety_offset: u32 = audio_unit
.get_property(kAudioDevicePropertySafetyOffset, scope, Element::Output)
.unwrap_or(0);
(device_latency + safety_offset) as usize
}
fn setup_callback_vars(
audio_unit: &AudioUnit,
config: StreamConfig,
sample_format: SampleFormat,
scope: Scope,
) -> (usize, SampleRate, Option<usize>, usize) {
let bytes_per_channel = sample_format.sample_size();
let sample_rate = config.sample_rate;
let device_buffer_frames = get_device_buffer_frame_size(audio_unit).ok();
let extra_latency_frames = get_device_extra_latency_frames(audio_unit, scope);
(
bytes_per_channel,
sample_rate,
device_buffer_frames,
extra_latency_frames,
)
}
pub(crate) fn get_device_buffer_frame_size(
audio_unit: &AudioUnit,
) -> Result<usize, coreaudio::Error> {
let frames: u32 = audio_unit.get_property(
kAudioDevicePropertyBufferFrameSize,
Scope::Global,
Element::Output,
)?;
Ok(frames as usize)
}