use std::{
fmt,
ptr::NonNull,
sync::{Arc, Mutex},
time::Duration,
};
use coreaudio::audio_unit::{
render_callback::{self, data},
AudioUnit, Element, Scope,
};
use objc2_audio_toolbox::{kAudioOutputUnitProperty_EnableIO, kAudioUnitProperty_StreamFormat};
use objc2_avf_audio::AVAudioSession;
use objc2_core_audio_types::AudioBuffer;
use self::enumerate::{
default_input_device, default_output_device, Devices, SupportedInputConfigs,
SupportedOutputConfigs,
};
use super::{asbd_from_config, host_time_to_stream_instant};
use crate::{
host::{frames_to_duration, latch::Latch, try_emit_error, ErrorCallbackArc},
traits::{DeviceTrait, HostTrait, StreamTrait},
BufferSize, ChannelCount, Data, DeviceDescription, DeviceDescriptionBuilder, DeviceId, Error,
ErrorKind, FrameCount, InputCallbackInfo, InputStreamTimestamp, OutputCallbackInfo,
OutputStreamTimestamp, ResultExt, SampleFormat, SampleRate, StreamConfig, StreamInstant,
SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange,
};
pub mod enumerate;
mod session_event_manager;
use session_event_manager::SessionEventManager;
const SUPPORTED_SAMPLE_FORMAT: SampleFormat = SampleFormat::F32;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct 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())
}
}
pub struct Host;
impl Host {
pub fn new() -> Result<Self, Error> {
Ok(Host)
}
}
impl HostTrait for Host {
type Devices = Devices;
type Device = Device;
fn is_available() -> bool {
true
}
fn devices(&self) -> Result<Self::Devices, Error> {
Ok(Devices(vec![Device].into_iter()))
}
fn default_input_device(&self) -> Option<Self::Device> {
default_input_device()
}
fn default_output_device(&self) -> Option<Self::Device> {
default_output_device()
}
}
impl Device {
fn description(&self) -> Result<DeviceDescription, Error> {
let direction = unsafe {
let audio_session = AVAudioSession::sharedInstance();
let input_channels = Some(audio_session.inputNumberOfChannels() as ChannelCount);
let output_channels = Some(audio_session.outputNumberOfChannels() as ChannelCount);
crate::device_description::direction_from_counts(input_channels, output_channels)
};
Ok(DeviceDescriptionBuilder::new("Default Device")
.direction(direction)
.build())
}
fn id(&self) -> Result<DeviceId, Error> {
Ok(DeviceId::new(crate::platform::HostId::CoreAudio, "default"))
}
fn supported_input_configs(&self) -> Result<SupportedInputConfigs, Error> {
Ok(get_supported_stream_configs(true))
}
fn supported_output_configs(&self) -> Result<SupportedOutputConfigs, Error> {
Ok(get_supported_stream_configs(false))
}
fn default_input_config(&self) -> Result<SupportedStreamConfig, Error> {
let range = get_supported_stream_configs(true).next().ok_or_else(|| {
Error::with_message(
ErrorKind::UnsupportedConfig,
"No supported input configuration",
)
})?;
Ok(range
.try_with_standard_sample_rate()
.unwrap_or_else(|| range.with_max_sample_rate()))
}
fn default_output_config(&self) -> Result<SupportedStreamConfig, Error> {
let range = get_supported_stream_configs(false)
.max_by(|a, b| a.cmp_default_heuristics(b))
.ok_or_else(|| {
Error::with_message(
ErrorKind::UnsupportedConfig,
"No supported output configuration",
)
})?;
Ok(range
.try_with_standard_sample_rate()
.unwrap_or_else(|| range.with_max_sample_rate()))
}
}
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,
{
crate::validate_stream_config(&config)?;
let mut audio_unit = setup_stream_audio_unit(config, sample_format, true)?;
let device_buffer_frames = Some(get_device_buffer_frames());
let error_callback: ErrorCallbackArc = Arc::new(Mutex::new(error_callback));
let session_manager = SessionEventManager::new(error_callback.clone(), Latch::new());
setup_input_callback(
&mut audio_unit,
sample_format,
config.sample_rate,
device_buffer_frames,
data_callback,
move |e| {
let _ = try_emit_error(&error_callback, e);
},
)?;
let stream = Stream::new(
StreamInner {
playing: false,
audio_unit,
},
session_manager,
);
stream.signal_ready();
Ok(stream)
}
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,
{
crate::validate_stream_config(&config)?;
let mut audio_unit = setup_stream_audio_unit(config, sample_format, false)?;
let device_buffer_frames = Some(get_device_buffer_frames());
let error_callback: ErrorCallbackArc = Arc::new(Mutex::new(error_callback));
let session_manager = SessionEventManager::new(error_callback.clone(), Latch::new());
setup_output_callback(
&mut audio_unit,
sample_format,
config.sample_rate,
device_buffer_frames,
data_callback,
move |e| {
let _ = try_emit_error(&error_callback, e);
},
)?;
let stream = Stream::new(
StreamInner {
playing: false,
audio_unit,
},
session_manager,
);
stream.signal_ready();
Ok(stream)
}
}
pub struct Stream {
inner: Mutex<StreamInner>,
session_manager: SessionEventManager,
}
impl Stream {
fn new(inner: StreamInner, session_manager: SessionEventManager) -> Self {
Self {
inner: Mutex::new(inner),
session_manager,
}
}
fn signal_ready(&self) {
self.session_manager.signal_ready();
}
}
impl Drop for Stream {
fn drop(&mut self) {
self.session_manager.signal_ready();
}
}
impl StreamTrait for Stream {
fn play(&self) -> Result<(), Error> {
let mut stream = self.inner.lock().map_err(|_| {
Error::with_message(ErrorKind::StreamInvalidated, "Stream lock poisoned")
})?;
if !stream.playing {
stream
.audio_unit
.start()
.context("Failed to start audio unit")?;
stream.playing = true;
}
Ok(())
}
fn pause(&self) -> Result<(), Error> {
let mut stream = self.inner.lock().map_err(|_| {
Error::with_message(ErrorKind::StreamInvalidated, "Stream lock poisoned")
})?;
if stream.playing {
stream
.audio_unit
.stop()
.context("Failed to stop audio unit")?;
stream.playing = false;
}
Ok(())
}
fn now(&self) -> StreamInstant {
let m_host_time = unsafe { mach2::mach_time::mach_absolute_time() };
host_time_to_stream_instant(m_host_time).expect("mach_timebase_info failed")
}
fn buffer_size(&self) -> Result<FrameCount, Error> {
Ok(get_device_buffer_frames() as FrameCount)
}
}
struct StreamInner {
playing: bool,
audio_unit: AudioUnit,
}
fn create_audio_unit() -> Result<AudioUnit, coreaudio::Error> {
AudioUnit::new_uninitialized(coreaudio::audio_unit::IOType::RemoteIO)
}
fn configure_for_recording(audio_unit: &mut AudioUnit) -> Result<(), coreaudio::Error> {
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),
)?;
Ok(())
}
fn set_audio_session_buffer_size(
buffer_size: FrameCount,
sample_rate: SampleRate,
) -> Result<(), Error> {
let audio_session = unsafe { AVAudioSession::sharedInstance() };
let buffer_duration = buffer_size as f64 / sample_rate as f64;
unsafe {
audio_session
.setPreferredIOBufferDuration_error(buffer_duration)
.map_err(|e| {
Error::with_message(
ErrorKind::UnsupportedConfig,
format!("Failed to set preferred I/O buffer duration: {e}"),
)
})?;
}
Ok(())
}
fn get_device_buffer_frames() -> usize {
unsafe {
let audio_session = AVAudioSession::sharedInstance();
let buffer_duration = audio_session.IOBufferDuration();
let sample_rate = audio_session.sampleRate();
(buffer_duration * sample_rate) as usize
}
}
const BUFFER_SIZE_MIN: FrameCount = 256;
const BUFFER_SIZE_MAX: FrameCount = 4096;
fn get_supported_stream_configs(is_input: bool) -> std::vec::IntoIter<SupportedStreamConfigRange> {
let (sample_rate, max_channels) = unsafe {
let audio_session = AVAudioSession::sharedInstance();
let sample_rate = audio_session.sampleRate() as SampleRate;
let max_channels = if is_input {
audio_session.inputNumberOfChannels() as ChannelCount
} else {
audio_session.outputNumberOfChannels() as ChannelCount
};
(sample_rate, max_channels)
};
let buffer_size = SupportedBufferSize::Range {
min: BUFFER_SIZE_MIN,
max: BUFFER_SIZE_MAX,
};
let min_channels = if is_input { max_channels } else { 1 };
let configs: Vec<_> = (min_channels..=max_channels)
.map(|channels| SupportedStreamConfigRange {
channels,
min_sample_rate: sample_rate,
max_sample_rate: sample_rate,
buffer_size,
sample_format: SUPPORTED_SAMPLE_FORMAT,
})
.collect();
configs.into_iter()
}
fn setup_stream_audio_unit(
config: StreamConfig,
sample_format: SampleFormat,
is_input: bool,
) -> Result<AudioUnit, Error> {
if let BufferSize::Fixed(buffer_size) = config.buffer_size {
if !(BUFFER_SIZE_MIN..=BUFFER_SIZE_MAX).contains(&buffer_size) {
return Err(Error::with_message(
ErrorKind::UnsupportedConfig,
format!(
"Buffer size {buffer_size} is not in the supported range \
{BUFFER_SIZE_MIN}..={BUFFER_SIZE_MAX}"
),
));
}
set_audio_session_buffer_size(buffer_size, config.sample_rate)?;
}
let mut audio_unit = create_audio_unit()?;
if is_input {
configure_for_recording(&mut audio_unit)?;
}
let (scope, element) = if is_input {
(Scope::Output, Element::Input)
} else {
(Scope::Input, Element::Output)
};
let asbd = asbd_from_config(config, sample_format);
audio_unit.set_property(kAudioUnitProperty_StreamFormat, scope, element, Some(&asbd))?;
audio_unit.initialize()?;
Ok(audio_unit)
}
#[inline]
unsafe fn extract_audio_buffer(
args: &render_callback::Args<data::Raw>,
bytes_per_channel: usize,
sample_format: SampleFormat,
is_input: bool,
) -> (AudioBuffer, Data) {
let buffer = if is_input {
let first_buf_ptr = core::ptr::addr_of!((*args.data.data).mBuffers) as *const AudioBuffer;
core::ptr::read_unaligned(first_buf_ptr)
} else {
let buf_ptr = core::ptr::addr_of!((*args.data.data).mBuffers[0]);
core::ptr::read_unaligned(buf_ptr)
};
let mut data_ptr = buffer.mData as *mut ();
let mut len = buffer.mDataByteSize as usize / bytes_per_channel;
if data_ptr.is_null() {
data_ptr = NonNull::dangling().as_ptr();
len = 0;
}
let data = Data::from_parts(data_ptr, len, sample_format);
(buffer, data)
}
fn setup_input_callback<D, E>(
audio_unit: &mut AudioUnit,
sample_format: SampleFormat,
sample_rate: SampleRate,
device_buffer_frames: Option<usize>,
mut data_callback: D,
mut error_callback: E,
) -> Result<(), Error>
where
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
E: FnMut(Error) + Send + 'static,
{
let bytes_per_channel = sample_format.sample_size();
type Args = render_callback::Args<data::Raw>;
audio_unit.set_input_callback(move |args: Args| {
let (buffer, data) =
unsafe { extract_audio_buffer(&args, bytes_per_channel, sample_format, true) };
let callback = match host_time_to_stream_instant(args.time_stamp.mHostTime) {
Err(err) => {
error_callback(err);
return Err(());
}
Ok(cb) => cb,
};
let latency_frames = device_buffer_frames.unwrap_or_else(|| {
let channels = buffer.mNumberChannels as usize;
data.len().checked_div(channels).unwrap_or(0)
});
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(())
})?;
Ok(())
}
fn setup_output_callback<D, E>(
audio_unit: &mut AudioUnit,
sample_format: SampleFormat,
sample_rate: SampleRate,
device_buffer_frames: Option<usize>,
mut data_callback: D,
mut error_callback: E,
) -> Result<(), Error>
where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
E: FnMut(Error) + Send + 'static,
{
let bytes_per_channel = sample_format.sample_size();
type Args = render_callback::Args<data::Raw>;
audio_unit.set_render_callback(move |args: Args| {
let (buffer, mut data) =
unsafe { extract_audio_buffer(&args, bytes_per_channel, sample_format, false) };
let callback = match host_time_to_stream_instant(args.time_stamp.mHostTime) {
Err(err) => {
error_callback(err);
return Err(());
}
Ok(cb) => cb,
};
let latency_frames = device_buffer_frames.unwrap_or_else(|| {
let channels = buffer.mNumberChannels as usize;
data.len().checked_div(channels).unwrap_or(0)
});
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(())
})?;
Ok(())
}
#[cfg(test)]
mod tests {
use crate::{BufferSize, SampleRate, StreamConfig};
#[test]
fn test_ios_fixed_buffer_size() {
let host = crate::default_host();
let device = host.default_output_device().unwrap();
let config = StreamConfig {
channels: 2,
sample_rate: SampleRate(48000),
buffer_size: BufferSize::Fixed(512),
};
let result = device.build_output_stream(
&config,
|_data: &mut [f32], _info: &OutputCallbackInfo| {},
|_err| {},
None,
);
assert!(
result.is_ok(),
"BufferSize::Fixed should be supported on iOS via AVAudioSession"
);
}
}