use super::OSStatus;
use super::Stream;
use super::{asbd_from_config, check_os_status, frames_to_duration, host_time_to_stream_instant};
use crate::host::coreaudio::macos::loopback::LoopbackDevice;
use crate::host::coreaudio::macos::StreamInner;
use crate::traits::DeviceTrait;
use crate::{
BackendSpecificError, BufferSize, BuildStreamError, ChannelCount, Data,
DefaultStreamConfigError, DeviceId, DeviceIdError, DeviceNameError, InputCallbackInfo,
OutputCallbackInfo, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize,
SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError,
};
use coreaudio::audio_unit::render_callback::{self, data};
use coreaudio::audio_unit::{AudioUnit, Element, Scope};
use objc2_audio_toolbox::{
kAudioOutputUnitProperty_CurrentDevice, kAudioOutputUnitProperty_EnableIO,
kAudioUnitProperty_StreamFormat,
};
use objc2_core_audio::kAudioDevicePropertyDeviceUID;
use objc2_core_audio::kAudioObjectPropertyElementMain;
use objc2_core_audio::{
kAudioAggregateDeviceClassID, kAudioDevicePropertyAvailableNominalSampleRates,
kAudioDevicePropertyBufferFrameSize, kAudioDevicePropertyBufferFrameSizeRange,
kAudioDevicePropertyNominalSampleRate, kAudioDevicePropertyStreamConfiguration,
kAudioDevicePropertyStreamFormat, kAudioObjectPropertyClass, 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;
use objc2_core_foundation::Type;
pub use super::enumerate::{
default_input_device, default_output_device, SupportedInputConfigs, SupportedOutputConfigs,
};
use std::fmt;
use std::mem::{self, size_of};
use std::ptr::{null, NonNull};
use std::sync::mpsc::{channel, RecvTimeoutError};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use super::invoke_error_callback;
use super::property_listener::AudioObjectPropertyListener;
use coreaudio::audio_unit::macos_helpers::get_device_name;
fn set_sample_rate(
audio_device_id: AudioObjectID,
target_sample_rate: SampleRate,
) -> Result<(), BuildStreamError> {
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 as u32 != target_sample_rate {
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(BuildStreamError::StreamConfigNotSupported);
}
let (send, recv) = channel::<Result<f64, coreaudio::Error>>();
let sample_rate_address = AudioObjectPropertyAddress {
mSelector: kAudioDevicePropertyNominalSampleRate,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
let sample_rate_handler = move || {
let mut rate: f64 = 0.0;
let mut data_size = mem::size_of::<f64>() as u32;
let result = unsafe {
AudioObjectGetPropertyData(
audio_device_id,
NonNull::from(&sample_rate_address),
0,
null(),
NonNull::from(&mut data_size),
NonNull::from(&mut rate).cast(),
)
};
send.send(coreaudio::Error::from_os_status(result).map(|_| rate))
.ok();
};
let listener = AudioObjectPropertyListener::new(
audio_device_id,
sample_rate_address,
sample_rate_handler,
)?;
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 timeout = Duration::from_secs(1);
let start = Instant::now();
loop {
match recv.recv_timeout(timeout) {
Err(err) => {
let description = match err {
RecvTimeoutError::Disconnected => {
"sample rate listener channel disconnected unexpectedly"
}
RecvTimeoutError::Timeout => {
"timeout waiting for sample rate update for device"
}
}
.to_string();
return Err(BackendSpecificError { description }.into());
}
Ok(Ok(reported_sample_rate)) => {
if reported_sample_rate == target_sample_rate as f64 {
break;
}
}
Ok(Err(_)) => {
}
};
timeout = timeout
.checked_sub(start.elapsed())
.unwrap_or(Duration::ZERO);
}
listener.remove()?;
}
Ok(())
}
fn audio_unit_from_device(device: &Device, input: bool) -> Result<AudioUnit, coreaudio::Error> {
let output_type = if !input && is_default_output_device(device) {
coreaudio::audio_unit::IOType::DefaultOutput
} else {
coreaudio::audio_unit::IOType::HalOutput
};
let mut audio_unit = AudioUnit::new(output_type)?;
if input {
let enable_input = 1u32;
audio_unit.set_property(
kAudioOutputUnitProperty_EnableIO,
Scope::Input,
Element::Input,
Some(&enable_input),
)?;
let disable_output = 0u32;
audio_unit.set_property(
kAudioOutputUnitProperty_EnableIO,
Scope::Output,
Element::Output,
Some(&disable_output),
)?;
}
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(
audio_unit: &AudioUnit,
) -> Result<SupportedBufferSize, coreaudio::Error> {
let buffer_size_range: AudioValueRange = audio_unit.get_property(
kAudioDevicePropertyBufferFrameSizeRange,
Scope::Global,
Element::Output,
)?;
Ok(SupportedBufferSize::Range {
min: buffer_size_range.mMinimum as u32,
max: buffer_size_range.mMaximum as u32,
})
}
impl DeviceTrait for Device {
type SupportedInputConfigs = SupportedInputConfigs;
type SupportedOutputConfigs = SupportedOutputConfigs;
type Stream = Stream;
fn description(&self) -> Result<crate::DeviceDescription, DeviceNameError> {
Device::description(self)
}
fn id(&self) -> Result<DeviceId, DeviceIdError> {
Device::id(self)
}
fn supported_input_configs(
&self,
) -> Result<Self::SupportedInputConfigs, SupportedStreamConfigsError> {
Device::supported_input_configs(self)
}
fn supported_output_configs(
&self,
) -> Result<Self::SupportedOutputConfigs, SupportedStreamConfigsError> {
Device::supported_output_configs(self)
}
fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
Device::default_input_config(self)
}
fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
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, BuildStreamError>
where
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + 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, BuildStreamError>
where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
Device::build_output_stream_raw(
self,
config,
sample_format,
data_callback,
error_callback,
timeout,
)
}
}
#[derive(Clone, Eq, Hash, PartialEq)]
pub struct Device {
pub(crate) audio_device_id: AudioDeviceID,
}
fn is_default_input_device(device: &Device) -> bool {
default_input_device().is_some_and(|d| d.audio_device_id == device.audio_device_id)
}
fn is_default_output_device(device: &Device) -> bool {
default_output_device().is_some_and(|d| d.audio_device_id == device.audio_device_id)
}
impl Device {
pub fn new(audio_device_id: AudioDeviceID) -> Self {
Self { audio_device_id }
}
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, DeviceNameError> {
let name = get_device_name(self.audio_device_id).map_err(|err| {
DeviceNameError::BackendSpecific {
err: BackendSpecificError {
description: err.to_string(),
},
}
})?;
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 = crate::DeviceDescriptionBuilder::new(name).direction(direction);
if self.is_aggregate_device() {
builder = builder.interface_type(crate::InterfaceType::Aggregate);
}
Ok(builder.build())
}
fn id(&self) -> Result<DeviceId, DeviceIdError> {
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(crate::platform::HostId::CoreAudio, uid_string))
} else {
Err(DeviceIdError::BackendSpecific {
err: BackendSpecificError {
description: "Device UID is null".to_string(),
},
})
}
}
#[allow(clippy::cast_ptr_alignment)]
fn supported_configs(
&self,
scope: AudioObjectPropertyScope,
) -> Result<SupportedOutputConfigs, SupportedStreamConfigsError> {
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)]
let input = match scope {
kAudioObjectPropertyScopeInput => Ok(true),
kAudioObjectPropertyScopeOutput => Ok(false),
_ => Err(BackendSpecificError {
description: format!("unexpected scope (neither input nor output): {scope:?}"),
}),
}?;
let audio_unit = audio_unit_from_device(self, input)?;
let buffer_size = get_io_buffer_frame_size_range(&audio_unit)?;
let contains_different_sample_rates = ranges.iter().any(|r| r.mMinimum != r.mMaximum);
if ranges.is_empty() {
Ok(vec![].into_iter())
} else if contains_different_sample_rates {
let res = 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,
});
Ok(res.collect::<Vec<_>>().into_iter())
} else {
let fmt = SupportedStreamConfigRange {
channels: n_channels as ChannelCount,
min_sample_rate: ranges
.iter()
.map(|v| v.mMinimum as u32)
.min()
.expect("the list must not be empty"),
max_sample_rate: ranges
.iter()
.map(|v| v.mMaximum as u32)
.max()
.expect("the list must not be empty"),
buffer_size,
sample_format,
};
Ok(vec![fmt].into_iter())
}
}
}
fn supported_input_configs(
&self,
) -> Result<SupportedOutputConfigs, SupportedStreamConfigsError> {
self.supported_configs(kAudioObjectPropertyScopeInput)
}
fn supported_output_configs(
&self,
) -> Result<SupportedOutputConfigs, SupportedStreamConfigsError> {
self.supported_configs(kAudioObjectPropertyScopeOutput)
}
fn default_config(
&self,
scope: AudioObjectPropertyScope,
) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
fn default_config_error_from_os_status(
status: OSStatus,
) -> Result<(), DefaultStreamConfigError> {
let err = match coreaudio::Error::from_os_status(status) {
Err(err) => err,
Ok(_) => return Ok(()),
};
match err {
coreaudio::Error::AudioUnit(
coreaudio::error::AudioUnitError::FormatNotSupported,
)
| coreaudio::Error::AudioCodec(_)
| coreaudio::Error::AudioFormat(_) => {
Err(DefaultStreamConfigError::StreamTypeNotSupported)
}
coreaudio::Error::AudioUnit(coreaudio::error::AudioUnitError::NoConnection) => {
Err(DefaultStreamConfigError::DeviceNotAvailable)
}
err => {
let description = format!("{err}");
let err = BackendSpecificError { description };
Err(err.into())
}
}
}
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(),
);
default_config_error_from_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(DefaultStreamConfigError::StreamTypeNotSupported),
};
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(DefaultStreamConfigError::StreamTypeNotSupported),
}
};
#[allow(non_upper_case_globals)]
let input = match scope {
kAudioObjectPropertyScopeInput => Ok(true),
kAudioObjectPropertyScopeOutput => Ok(false),
_ => Err(BackendSpecificError {
description: format!("unexpected scope (neither input nor output): {scope:?}"),
}),
}?;
let audio_unit = audio_unit_from_device(self, input)?;
let buffer_size = get_io_buffer_frame_size_range(&audio_unit)?;
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, DefaultStreamConfigError> {
self.default_config(kAudioObjectPropertyScopeInput)
}
fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
self.default_config(kAudioObjectPropertyScopeOutput)
}
fn supports_input(&self) -> bool {
self.supported_input_configs()
.map(|mut configs| configs.next().is_some())
.unwrap_or(false)
}
}
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.name())
.finish()
}
}
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, BuildStreamError>
where
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
let scope = Scope::Output;
let element = Element::Input;
set_sample_rate(self.audio_device_id, config.sample_rate)?;
let mut loopback_aggregate: Option<LoopbackDevice> = None;
let mut audio_unit = if self.supports_input() {
audio_unit_from_device(self, true)?
} else {
loopback_aggregate.replace(LoopbackDevice::from_device(self)?);
audio_unit_from_device(&loopback_aggregate.as_ref().unwrap().aggregate_device, true)?
};
configure_stream_format_and_buffer(&mut audio_unit, config, sample_format, scope, element)?;
let error_callback = Arc::new(Mutex::new(error_callback));
let error_callback_disconnect = error_callback.clone();
let (bytes_per_channel, sample_rate, device_buffer_frames) =
setup_callback_vars(&audio_unit, config, sample_format);
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) => {
invoke_error_callback(&error_callback, err.into());
return Err(());
}
Ok(cb) => cb,
};
let buffer_frames = len / channels as usize;
let latency_frames = device_buffer_frames.unwrap_or(
buffer_frames,
);
let delay = frames_to_duration(latency_frames, sample_rate);
let capture = callback
.sub(delay)
.expect("`capture` occurs before origin of alsa `StreamInstant`");
let timestamp = crate::InputStreamTimestamp { callback, capture };
let info = InputCallbackInfo { timestamp };
data_callback(&data, &info);
Ok(())
})?;
let error_callback_for_stream: super::ErrorCallback = if is_default_input_device(self) {
Box::new(|_: StreamError| {})
} else {
let error_callback_clone = error_callback_disconnect.clone();
Box::new(move |err: StreamError| {
invoke_error_callback(&error_callback_clone, err);
})
};
let stream = Stream::new(
StreamInner {
playing: true,
audio_unit,
device_id: self.audio_device_id,
_loopback_device: loopback_aggregate,
},
error_callback_for_stream,
)?;
stream
.inner
.lock()
.map_err(|_| BuildStreamError::BackendSpecific {
err: BackendSpecificError {
description: "A cpal stream operation panicked while holding the lock - this is a bug, please report it".to_string(),
},
})?
.audio_unit
.start()?;
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, BuildStreamError>
where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
let mut audio_unit = audio_unit_from_device(self, false)?;
let scope = Scope::Input;
let element = Element::Output;
configure_stream_format_and_buffer(&mut audio_unit, config, sample_format, scope, element)?;
let error_callback = Arc::new(Mutex::new(error_callback));
let error_callback_disconnect = error_callback.clone();
let (bytes_per_channel, sample_rate, device_buffer_frames) =
setup_callback_vars(&audio_unit, config, sample_format);
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) => {
invoke_error_callback(&error_callback, err.into());
return Err(());
}
Ok(cb) => cb,
};
let buffer_frames = len / channels as usize;
let latency_frames = device_buffer_frames.unwrap_or(
buffer_frames,
);
let delay = frames_to_duration(latency_frames, sample_rate);
let playback = callback
.add(delay)
.expect("`playback` occurs beyond representation supported by `StreamInstant`");
let timestamp = crate::OutputStreamTimestamp { callback, playback };
let info = OutputCallbackInfo { timestamp };
data_callback(&mut data, &info);
Ok(())
})?;
let error_callback_for_stream: super::ErrorCallback = if is_default_output_device(self) {
Box::new(|_: StreamError| {})
} else {
let error_callback_clone = error_callback_disconnect.clone();
Box::new(move |err: StreamError| {
invoke_error_callback(&error_callback_clone, err);
})
};
let stream = Stream::new(
StreamInner {
playing: true,
audio_unit,
device_id: self.audio_device_id,
_loopback_device: None,
},
error_callback_for_stream,
)?;
stream
.inner
.lock()
.map_err(|_| BuildStreamError::BackendSpecific {
err: BackendSpecificError {
description: "A cpal stream operation panicked while holding the lock - this is a bug, please report it".to_string(),
},
})?
.audio_unit
.start()?;
Ok(stream)
}
}
fn configure_stream_format_and_buffer(
audio_unit: &mut AudioUnit,
config: &StreamConfig,
sample_format: SampleFormat,
scope: Scope,
element: Element,
) -> Result<(), BuildStreamError> {
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 {
audio_unit.set_property(
kAudioDevicePropertyBufferFrameSize,
Scope::Global,
Element::Output,
Some(&buffer_size),
)?;
}
Ok(())
}
fn setup_callback_vars(
audio_unit: &AudioUnit,
config: &StreamConfig,
sample_format: SampleFormat,
) -> (usize, crate::SampleRate, Option<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();
(bytes_per_channel, sample_rate, device_buffer_frames)
}
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)
}