2018-01-26 12:49:47 +00:00
|
|
|
extern crate core_foundation_sys;
|
2020-01-20 19:35:23 +00:00
|
|
|
extern crate coreaudio;
|
2015-02-28 18:50:29 +00:00
|
|
|
|
2020-05-01 07:38:25 +00:00
|
|
|
use self::core_foundation_sys::string::{CFStringGetCString, CFStringGetCStringPtr, CFStringRef};
|
2016-08-12 07:49:13 +00:00
|
|
|
use self::coreaudio::audio_unit::render_callback::{self, data};
|
2020-01-20 19:35:23 +00:00
|
|
|
use self::coreaudio::audio_unit::{AudioUnit, Element, Scope};
|
2018-01-26 12:49:47 +00:00
|
|
|
use self::coreaudio::sys::{
|
2020-01-20 19:35:23 +00:00
|
|
|
kAudioDevicePropertyAvailableNominalSampleRates, kAudioDevicePropertyDeviceNameCFString,
|
|
|
|
kAudioDevicePropertyNominalSampleRate, kAudioDevicePropertyScopeOutput,
|
|
|
|
kAudioDevicePropertyStreamConfiguration, kAudioDevicePropertyStreamFormat,
|
|
|
|
kAudioFormatFlagIsFloat, kAudioFormatFlagIsPacked, kAudioFormatLinearPCM,
|
|
|
|
kAudioObjectPropertyElementMaster, kAudioObjectPropertyScopeGlobal,
|
|
|
|
kAudioObjectPropertyScopeInput, kAudioObjectPropertyScopeOutput,
|
|
|
|
kAudioOutputUnitProperty_CurrentDevice, kAudioOutputUnitProperty_EnableIO,
|
|
|
|
kAudioUnitProperty_StreamFormat, kCFStringEncodingUTF8, AudioBuffer, AudioBufferList,
|
|
|
|
AudioDeviceID, AudioObjectAddPropertyListener, AudioObjectGetPropertyData,
|
|
|
|
AudioObjectGetPropertyDataSize, AudioObjectID, AudioObjectPropertyAddress,
|
|
|
|
AudioObjectPropertyScope, AudioObjectRemovePropertyListener, AudioObjectSetPropertyData,
|
|
|
|
AudioStreamBasicDescription, AudioValueRange, OSStatus,
|
2018-01-26 12:49:47 +00:00
|
|
|
};
|
2020-01-20 19:35:23 +00:00
|
|
|
use crate::traits::{DeviceTrait, HostTrait, StreamTrait};
|
|
|
|
use crate::{
|
2020-01-28 14:03:27 +00:00
|
|
|
BackendSpecificError, BuildStreamError, ChannelCount, Data, DefaultStreamConfigError,
|
2020-04-16 12:50:36 +00:00
|
|
|
DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError,
|
|
|
|
PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedStreamConfig,
|
|
|
|
SupportedStreamConfigRange, SupportedStreamConfigsError,
|
2017-11-03 09:51:02 +00:00
|
|
|
};
|
2020-01-20 19:35:23 +00:00
|
|
|
use std::cell::RefCell;
|
2020-01-15 16:21:11 +00:00
|
|
|
use std::ffi::CStr;
|
|
|
|
use std::fmt;
|
|
|
|
use std::mem;
|
|
|
|
use std::os::raw::c_char;
|
|
|
|
use std::ptr::null;
|
|
|
|
use std::slice;
|
|
|
|
use std::thread;
|
|
|
|
use std::time::Duration;
|
2015-02-28 18:50:29 +00:00
|
|
|
|
2015-09-24 03:02:28 +00:00
|
|
|
mod enumerate;
|
|
|
|
|
2020-01-20 19:35:23 +00:00
|
|
|
pub use self::enumerate::{
|
2020-01-28 14:03:27 +00:00
|
|
|
default_input_device, default_output_device, Devices, SupportedInputConfigs,
|
|
|
|
SupportedOutputConfigs,
|
2020-01-20 19:35:23 +00:00
|
|
|
};
|
2015-09-24 03:02:28 +00:00
|
|
|
|
2019-06-24 20:44:57 +00:00
|
|
|
/// Coreaudio host, the default host on macOS and iOS.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Host;
|
|
|
|
|
|
|
|
impl Host {
|
|
|
|
pub fn new() -> Result<Self, crate::HostUnavailable> {
|
|
|
|
Ok(Host)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl HostTrait for Host {
|
|
|
|
type Devices = Devices;
|
|
|
|
type Device = Device;
|
|
|
|
|
|
|
|
fn is_available() -> bool {
|
2019-06-25 14:26:27 +00:00
|
|
|
// Assume coreaudio is always available on macOS and iOS.
|
2019-06-24 20:44:57 +00:00
|
|
|
true
|
|
|
|
}
|
|
|
|
|
|
|
|
fn devices(&self) -> Result<Self::Devices, DevicesError> {
|
|
|
|
Devices::new()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn default_input_device(&self) -> Option<Self::Device> {
|
|
|
|
default_input_device()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn default_output_device(&self) -> Option<Self::Device> {
|
|
|
|
default_output_device()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl DeviceTrait for Device {
|
2020-01-28 14:03:27 +00:00
|
|
|
type SupportedInputConfigs = SupportedInputConfigs;
|
|
|
|
type SupportedOutputConfigs = SupportedOutputConfigs;
|
2020-01-20 19:35:23 +00:00
|
|
|
type Stream = Stream;
|
2019-06-24 20:44:57 +00:00
|
|
|
|
|
|
|
fn name(&self) -> Result<String, DeviceNameError> {
|
|
|
|
Device::name(self)
|
|
|
|
}
|
|
|
|
|
2020-01-28 14:03:27 +00:00
|
|
|
fn supported_input_configs(
|
2020-01-20 19:35:23 +00:00
|
|
|
&self,
|
2020-01-28 14:03:27 +00:00
|
|
|
) -> Result<Self::SupportedInputConfigs, SupportedStreamConfigsError> {
|
|
|
|
Device::supported_input_configs(self)
|
2019-06-24 20:44:57 +00:00
|
|
|
}
|
|
|
|
|
2020-01-28 14:03:27 +00:00
|
|
|
fn supported_output_configs(
|
2020-01-20 19:35:23 +00:00
|
|
|
&self,
|
2020-01-28 14:03:27 +00:00
|
|
|
) -> Result<Self::SupportedOutputConfigs, SupportedStreamConfigsError> {
|
|
|
|
Device::supported_output_configs(self)
|
2019-06-24 20:44:57 +00:00
|
|
|
}
|
|
|
|
|
2020-01-28 14:03:27 +00:00
|
|
|
fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
|
|
|
|
Device::default_input_config(self)
|
2019-06-24 20:44:57 +00:00
|
|
|
}
|
|
|
|
|
2020-01-28 14:03:27 +00:00
|
|
|
fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
|
|
|
|
Device::default_output_config(self)
|
2019-06-24 20:44:57 +00:00
|
|
|
}
|
|
|
|
|
2020-01-22 05:52:18 +00:00
|
|
|
fn build_input_stream_raw<D, E>(
|
2020-01-15 16:21:11 +00:00
|
|
|
&self,
|
2020-02-02 18:22:27 +00:00
|
|
|
config: &StreamConfig,
|
|
|
|
sample_format: SampleFormat,
|
2020-01-15 16:21:11 +00:00
|
|
|
data_callback: D,
|
|
|
|
error_callback: E,
|
|
|
|
) -> Result<Self::Stream, BuildStreamError>
|
|
|
|
where
|
2020-04-16 12:50:36 +00:00
|
|
|
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
|
2020-01-15 16:21:11 +00:00
|
|
|
E: FnMut(StreamError) + Send + 'static,
|
|
|
|
{
|
2020-02-02 18:22:27 +00:00
|
|
|
Device::build_input_stream_raw(self, config, sample_format, data_callback, error_callback)
|
2019-06-24 20:44:57 +00:00
|
|
|
}
|
|
|
|
|
2020-01-22 05:52:18 +00:00
|
|
|
fn build_output_stream_raw<D, E>(
|
2020-01-15 16:21:11 +00:00
|
|
|
&self,
|
2020-02-02 18:22:27 +00:00
|
|
|
config: &StreamConfig,
|
|
|
|
sample_format: SampleFormat,
|
2020-01-15 16:21:11 +00:00
|
|
|
data_callback: D,
|
|
|
|
error_callback: E,
|
|
|
|
) -> Result<Self::Stream, BuildStreamError>
|
|
|
|
where
|
2020-04-16 12:50:36 +00:00
|
|
|
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
|
2020-01-15 16:21:11 +00:00
|
|
|
E: FnMut(StreamError) + Send + 'static,
|
|
|
|
{
|
2020-02-02 18:22:27 +00:00
|
|
|
Device::build_output_stream_raw(self, config, sample_format, data_callback, error_callback)
|
2019-06-24 20:44:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-24 03:02:28 +00:00
|
|
|
#[derive(Clone, PartialEq, Eq)]
|
2018-02-12 13:10:24 +00:00
|
|
|
pub struct Device {
|
2018-01-26 12:49:47 +00:00
|
|
|
audio_device_id: AudioDeviceID,
|
|
|
|
}
|
2015-09-24 03:02:28 +00:00
|
|
|
|
2018-02-12 13:10:24 +00:00
|
|
|
impl Device {
|
2019-06-24 20:44:57 +00:00
|
|
|
fn name(&self) -> Result<String, DeviceNameError> {
|
2018-02-12 13:10:24 +00:00
|
|
|
let property_address = AudioObjectPropertyAddress {
|
|
|
|
mSelector: kAudioDevicePropertyDeviceNameCFString,
|
|
|
|
mScope: kAudioDevicePropertyScopeOutput,
|
|
|
|
mElement: kAudioObjectPropertyElementMaster,
|
|
|
|
};
|
|
|
|
let device_name: CFStringRef = null();
|
|
|
|
let data_size = mem::size_of::<CFStringRef>();
|
|
|
|
let c_str = unsafe {
|
|
|
|
let status = AudioObjectGetPropertyData(
|
|
|
|
self.audio_device_id,
|
|
|
|
&property_address as *const _,
|
|
|
|
0,
|
|
|
|
null(),
|
|
|
|
&data_size as *const _ as *mut _,
|
|
|
|
&device_name as *const _ as *mut _,
|
|
|
|
);
|
2019-06-20 22:53:11 +00:00
|
|
|
check_os_status(status)?;
|
|
|
|
|
2018-02-12 13:10:24 +00:00
|
|
|
let c_string: *const c_char = CFStringGetCStringPtr(device_name, kCFStringEncodingUTF8);
|
|
|
|
if c_string == null() {
|
2020-05-01 07:38:25 +00:00
|
|
|
let status = AudioObjectGetPropertyData(
|
|
|
|
self.audio_device_id,
|
|
|
|
&property_address as *const _,
|
|
|
|
0,
|
|
|
|
null(),
|
|
|
|
&data_size as *const _ as *mut _,
|
|
|
|
&device_name as *const _ as *mut _,
|
|
|
|
);
|
|
|
|
check_os_status(status)?;
|
|
|
|
let mut buf: [i8; 255] = [0; 255];
|
|
|
|
let result = CFStringGetCString(
|
|
|
|
device_name,
|
|
|
|
buf.as_mut_ptr(),
|
|
|
|
buf.len() as _,
|
|
|
|
kCFStringEncodingUTF8,
|
|
|
|
);
|
|
|
|
if result == 0 {
|
|
|
|
let description =
|
|
|
|
"core foundation failed to return device name string".to_string();
|
|
|
|
let err = BackendSpecificError { description };
|
|
|
|
return Err(err.into());
|
|
|
|
}
|
|
|
|
let name: &CStr = unsafe { CStr::from_ptr(buf.as_ptr()) };
|
|
|
|
return Ok(name.to_str().unwrap().to_owned());
|
2018-02-12 13:10:24 +00:00
|
|
|
}
|
|
|
|
CStr::from_ptr(c_string as *mut _)
|
|
|
|
};
|
2019-06-20 22:53:11 +00:00
|
|
|
Ok(c_str.to_string_lossy().into_owned())
|
2018-02-12 13:10:24 +00:00
|
|
|
}
|
|
|
|
|
2020-01-28 14:03:27 +00:00
|
|
|
// Logic re-used between `supported_input_configs` and `supported_output_configs`.
|
|
|
|
fn supported_configs(
|
2018-02-12 13:10:24 +00:00
|
|
|
&self,
|
|
|
|
scope: AudioObjectPropertyScope,
|
2020-01-28 14:03:27 +00:00
|
|
|
) -> Result<SupportedOutputConfigs, SupportedStreamConfigsError> {
|
2018-01-26 12:49:47 +00:00
|
|
|
let mut property_address = AudioObjectPropertyAddress {
|
|
|
|
mSelector: kAudioDevicePropertyStreamConfiguration,
|
2018-02-12 13:10:24 +00:00
|
|
|
mScope: scope,
|
2018-01-26 12:49:47 +00:00
|
|
|
mElement: kAudioObjectPropertyElementMaster,
|
|
|
|
};
|
|
|
|
|
|
|
|
unsafe {
|
|
|
|
// Retrieve the devices audio buffer list.
|
|
|
|
let data_size = 0u32;
|
|
|
|
let status = AudioObjectGetPropertyDataSize(
|
|
|
|
self.audio_device_id,
|
|
|
|
&property_address as *const _,
|
|
|
|
0,
|
|
|
|
null(),
|
|
|
|
&data_size as *const _ as *mut _,
|
|
|
|
);
|
2019-06-20 22:22:30 +00:00
|
|
|
check_os_status(status)?;
|
|
|
|
|
2018-01-26 12:49:47 +00:00
|
|
|
let mut audio_buffer_list: Vec<u8> = vec![];
|
|
|
|
audio_buffer_list.reserve_exact(data_size as usize);
|
|
|
|
let status = AudioObjectGetPropertyData(
|
|
|
|
self.audio_device_id,
|
|
|
|
&property_address as *const _,
|
|
|
|
0,
|
|
|
|
null(),
|
|
|
|
&data_size as *const _ as *mut _,
|
|
|
|
audio_buffer_list.as_mut_ptr() as *mut _,
|
|
|
|
);
|
2019-06-20 22:22:30 +00:00
|
|
|
check_os_status(status)?;
|
|
|
|
|
2018-01-26 12:49:47 +00:00
|
|
|
let audio_buffer_list = audio_buffer_list.as_mut_ptr() as *mut AudioBufferList;
|
|
|
|
|
|
|
|
// If there's no buffers, skip.
|
|
|
|
if (*audio_buffer_list).mNumberBuffers == 0 {
|
|
|
|
return Ok(vec![].into_iter());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Count the number of channels as the sum of all channels in all output buffers.
|
|
|
|
let n_buffers = (*audio_buffer_list).mNumberBuffers as usize;
|
|
|
|
let first: *const AudioBuffer = (*audio_buffer_list).mBuffers.as_ptr();
|
|
|
|
let buffers: &'static [AudioBuffer] = slice::from_raw_parts(first, n_buffers);
|
|
|
|
let mut n_channels = 0;
|
|
|
|
for buffer in buffers {
|
|
|
|
n_channels += buffer.mNumberChannels as usize;
|
|
|
|
}
|
|
|
|
|
|
|
|
// AFAIK the sample format should always be f32 on macos and i16 on iOS? Feel free to
|
|
|
|
// fix this if more pcm formats are supported.
|
|
|
|
let sample_format = if cfg!(target_os = "ios") {
|
|
|
|
SampleFormat::I16
|
|
|
|
} else {
|
|
|
|
SampleFormat::F32
|
|
|
|
};
|
|
|
|
|
|
|
|
// Get available sample rate ranges.
|
|
|
|
property_address.mSelector = kAudioDevicePropertyAvailableNominalSampleRates;
|
|
|
|
let data_size = 0u32;
|
|
|
|
let status = AudioObjectGetPropertyDataSize(
|
|
|
|
self.audio_device_id,
|
|
|
|
&property_address as *const _,
|
|
|
|
0,
|
|
|
|
null(),
|
|
|
|
&data_size as *const _ as *mut _,
|
|
|
|
);
|
2019-06-20 22:22:30 +00:00
|
|
|
check_os_status(status)?;
|
|
|
|
|
2018-01-26 12:49:47 +00:00
|
|
|
let n_ranges = data_size as usize / mem::size_of::<AudioValueRange>();
|
|
|
|
let mut ranges: Vec<u8> = vec![];
|
|
|
|
ranges.reserve_exact(data_size as usize);
|
|
|
|
let status = AudioObjectGetPropertyData(
|
|
|
|
self.audio_device_id,
|
|
|
|
&property_address as *const _,
|
|
|
|
0,
|
|
|
|
null(),
|
|
|
|
&data_size as *const _ as *mut _,
|
|
|
|
ranges.as_mut_ptr() as *mut _,
|
|
|
|
);
|
2019-06-20 22:22:30 +00:00
|
|
|
check_os_status(status)?;
|
|
|
|
|
2018-01-26 12:49:47 +00:00
|
|
|
let ranges: *mut AudioValueRange = ranges.as_mut_ptr() as *mut _;
|
|
|
|
let ranges: &'static [AudioValueRange] = slice::from_raw_parts(ranges, n_ranges);
|
|
|
|
|
|
|
|
// Collect the supported formats for the device.
|
|
|
|
let mut fmts = vec![];
|
|
|
|
for range in ranges {
|
2020-01-28 14:03:27 +00:00
|
|
|
let fmt = SupportedStreamConfigRange {
|
2018-02-04 12:02:16 +00:00
|
|
|
channels: n_channels as ChannelCount,
|
|
|
|
min_sample_rate: SampleRate(range.mMinimum as _),
|
|
|
|
max_sample_rate: SampleRate(range.mMaximum as _),
|
2020-01-28 14:03:27 +00:00
|
|
|
sample_format: sample_format,
|
2018-01-26 12:49:47 +00:00
|
|
|
};
|
|
|
|
fmts.push(fmt);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(fmts.into_iter())
|
|
|
|
}
|
2015-09-24 03:02:28 +00:00
|
|
|
}
|
|
|
|
|
2020-01-28 14:03:27 +00:00
|
|
|
fn supported_input_configs(
|
|
|
|
&self,
|
|
|
|
) -> Result<SupportedOutputConfigs, SupportedStreamConfigsError> {
|
|
|
|
self.supported_configs(kAudioObjectPropertyScopeInput)
|
2018-02-12 13:10:24 +00:00
|
|
|
}
|
|
|
|
|
2020-01-28 14:03:27 +00:00
|
|
|
fn supported_output_configs(
|
|
|
|
&self,
|
|
|
|
) -> Result<SupportedOutputConfigs, SupportedStreamConfigsError> {
|
|
|
|
self.supported_configs(kAudioObjectPropertyScopeOutput)
|
2018-02-12 13:10:24 +00:00
|
|
|
}
|
|
|
|
|
2020-01-28 14:03:27 +00:00
|
|
|
fn default_config(
|
2018-02-12 13:10:24 +00:00
|
|
|
&self,
|
|
|
|
scope: AudioObjectPropertyScope,
|
2020-01-28 14:03:27 +00:00
|
|
|
) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
|
|
|
|
fn default_config_error_from_os_status(
|
|
|
|
status: OSStatus,
|
|
|
|
) -> Result<(), DefaultStreamConfigError> {
|
2018-02-12 13:10:24 +00:00
|
|
|
let err = match coreaudio::Error::from_os_status(status) {
|
|
|
|
Err(err) => err,
|
2019-06-20 23:34:07 +00:00
|
|
|
Ok(_) => return Ok(()),
|
2018-02-12 13:10:24 +00:00
|
|
|
};
|
|
|
|
match err {
|
2020-01-20 19:35:23 +00:00
|
|
|
coreaudio::Error::AudioUnit(
|
|
|
|
coreaudio::error::AudioUnitError::FormatNotSupported,
|
|
|
|
)
|
|
|
|
| coreaudio::Error::AudioCodec(_)
|
|
|
|
| coreaudio::Error::AudioFormat(_) => {
|
2020-01-28 14:03:27 +00:00
|
|
|
Err(DefaultStreamConfigError::StreamTypeNotSupported)
|
2019-06-20 23:34:07 +00:00
|
|
|
}
|
|
|
|
coreaudio::Error::AudioUnit(coreaudio::error::AudioUnitError::NoConnection) => {
|
2020-01-28 14:03:27 +00:00
|
|
|
Err(DefaultStreamConfigError::DeviceNotAvailable)
|
2019-06-20 23:34:07 +00:00
|
|
|
}
|
|
|
|
err => {
|
|
|
|
let description = format!("{}", std::error::Error::description(&err));
|
|
|
|
let err = BackendSpecificError { description };
|
|
|
|
Err(err.into())
|
|
|
|
}
|
2018-02-12 13:10:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-26 12:49:47 +00:00
|
|
|
let property_address = AudioObjectPropertyAddress {
|
2018-02-12 13:10:24 +00:00
|
|
|
mSelector: kAudioDevicePropertyStreamFormat,
|
|
|
|
mScope: scope,
|
2018-01-26 12:49:47 +00:00
|
|
|
mElement: kAudioObjectPropertyElementMaster,
|
|
|
|
};
|
2018-02-12 13:10:24 +00:00
|
|
|
|
|
|
|
unsafe {
|
2019-08-10 13:10:43 +00:00
|
|
|
let asbd: AudioStreamBasicDescription = mem::zeroed();
|
2018-02-12 13:10:24 +00:00
|
|
|
let data_size = mem::size_of::<AudioStreamBasicDescription>() as u32;
|
2018-01-26 12:49:47 +00:00
|
|
|
let status = AudioObjectGetPropertyData(
|
|
|
|
self.audio_device_id,
|
|
|
|
&property_address as *const _,
|
|
|
|
0,
|
|
|
|
null(),
|
|
|
|
&data_size as *const _ as *mut _,
|
2018-02-12 13:10:24 +00:00
|
|
|
&asbd as *const _ as *mut _,
|
2018-01-26 12:49:47 +00:00
|
|
|
);
|
2020-01-28 14:03:27 +00:00
|
|
|
default_config_error_from_os_status(status)?;
|
2018-02-12 13:10:24 +00:00
|
|
|
|
|
|
|
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,
|
2020-01-28 14:03:27 +00:00
|
|
|
_ => return Err(DefaultStreamConfigError::StreamTypeNotSupported),
|
2018-02-12 13:10:24 +00:00
|
|
|
};
|
|
|
|
let maybe_sample_format =
|
|
|
|
coreaudio::audio_unit::SampleFormat::from_flags_and_bytes_per_frame(
|
|
|
|
flags,
|
|
|
|
asbd.mBytesPerFrame,
|
|
|
|
);
|
|
|
|
match maybe_sample_format {
|
|
|
|
Some(coreaudio::audio_unit::SampleFormat::F32) => SampleFormat::F32,
|
|
|
|
Some(coreaudio::audio_unit::SampleFormat::I16) => SampleFormat::I16,
|
2020-01-28 14:03:27 +00:00
|
|
|
_ => return Err(DefaultStreamConfigError::StreamTypeNotSupported),
|
2018-02-12 13:10:24 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-01-28 14:03:27 +00:00
|
|
|
let config = SupportedStreamConfig {
|
2018-02-12 13:10:24 +00:00
|
|
|
sample_rate: SampleRate(asbd.mSampleRate as _),
|
|
|
|
channels: asbd.mChannelsPerFrame as _,
|
2020-01-28 14:03:27 +00:00
|
|
|
sample_format: sample_format,
|
2018-02-12 13:10:24 +00:00
|
|
|
};
|
2020-01-28 14:03:27 +00:00
|
|
|
Ok(config)
|
2018-02-12 13:10:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-28 14:03:27 +00:00
|
|
|
fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
|
|
|
|
self.default_config(kAudioObjectPropertyScopeInput)
|
2018-02-12 13:10:24 +00:00
|
|
|
}
|
|
|
|
|
2020-01-28 14:03:27 +00:00
|
|
|
fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
|
|
|
|
self.default_config(kAudioObjectPropertyScopeOutput)
|
2015-09-24 03:02:28 +00:00
|
|
|
}
|
2015-02-28 18:50:29 +00:00
|
|
|
}
|
|
|
|
|
2019-03-22 20:27:17 +00:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-12 13:10:24 +00:00
|
|
|
struct StreamInner {
|
2017-10-18 18:24:05 +00:00
|
|
|
playing: bool,
|
|
|
|
audio_unit: AudioUnit,
|
2018-04-01 11:25:46 +00:00
|
|
|
// Track the device with which the audio unit was spawned.
|
|
|
|
//
|
|
|
|
// We must do this so that we can avoid changing the device sample rate if there is already
|
|
|
|
// a stream associated with the device.
|
|
|
|
device_id: AudioDeviceID,
|
2017-10-18 18:24:05 +00:00
|
|
|
}
|
2015-02-28 18:50:29 +00:00
|
|
|
|
2017-11-03 09:50:02 +00:00
|
|
|
// TODO need stronger error identification
|
2019-06-20 19:31:15 +00:00
|
|
|
impl From<coreaudio::Error> for BuildStreamError {
|
|
|
|
fn from(err: coreaudio::Error) -> BuildStreamError {
|
2017-11-03 09:50:02 +00:00
|
|
|
match err {
|
2020-01-20 19:35:23 +00:00
|
|
|
coreaudio::Error::RenderCallbackBufferFormatDoesNotMatchAudioUnitStreamFormat
|
|
|
|
| coreaudio::Error::NoKnownSubtype
|
|
|
|
| coreaudio::Error::AudioUnit(coreaudio::error::AudioUnitError::FormatNotSupported)
|
|
|
|
| coreaudio::Error::AudioCodec(_)
|
2020-01-28 15:38:02 +00:00
|
|
|
| coreaudio::Error::AudioFormat(_) => BuildStreamError::StreamConfigNotSupported,
|
2019-06-20 19:31:15 +00:00
|
|
|
_ => BuildStreamError::DeviceNotAvailable,
|
2017-11-03 09:50:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-12 13:10:24 +00:00
|
|
|
// Create a coreaudio AudioStreamBasicDescription from a CPAL Format.
|
2020-02-02 18:22:27 +00:00
|
|
|
fn asbd_from_config(
|
|
|
|
config: &StreamConfig,
|
|
|
|
sample_format: SampleFormat,
|
|
|
|
) -> AudioStreamBasicDescription {
|
2020-01-28 14:03:27 +00:00
|
|
|
let n_channels = config.channels as usize;
|
|
|
|
let sample_rate = config.sample_rate.0;
|
2020-02-02 18:22:27 +00:00
|
|
|
let bytes_per_channel = sample_format.sample_size();
|
2018-02-12 13:10:24 +00:00
|
|
|
let bits_per_channel = bytes_per_channel * 8;
|
|
|
|
let bytes_per_frame = n_channels * bytes_per_channel;
|
|
|
|
let frames_per_packet = 1;
|
|
|
|
let bytes_per_packet = frames_per_packet * bytes_per_frame;
|
|
|
|
let format_flags = match sample_format {
|
|
|
|
SampleFormat::F32 => (kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked) as u32,
|
|
|
|
_ => kAudioFormatFlagIsPacked as u32,
|
|
|
|
};
|
|
|
|
let asbd = AudioStreamBasicDescription {
|
|
|
|
mBitsPerChannel: bits_per_channel as _,
|
|
|
|
mBytesPerFrame: bytes_per_frame as _,
|
|
|
|
mChannelsPerFrame: n_channels as _,
|
|
|
|
mBytesPerPacket: bytes_per_packet as _,
|
|
|
|
mFramesPerPacket: frames_per_packet as _,
|
|
|
|
mFormatFlags: format_flags,
|
|
|
|
mFormatID: kAudioFormatLinearPCM,
|
|
|
|
mSampleRate: sample_rate as _,
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
asbd
|
|
|
|
}
|
|
|
|
|
|
|
|
fn audio_unit_from_device(device: &Device, input: bool) -> Result<AudioUnit, coreaudio::Error> {
|
|
|
|
let mut audio_unit = {
|
|
|
|
let au_type = if cfg!(target_os = "ios") {
|
|
|
|
// The HalOutput unit isn't available in iOS unfortunately.
|
|
|
|
// RemoteIO is a sensible replacement.
|
|
|
|
// See https://goo.gl/CWwRTx
|
|
|
|
coreaudio::audio_unit::IOType::RemoteIO
|
|
|
|
} else {
|
|
|
|
coreaudio::audio_unit::IOType::HalOutput
|
|
|
|
};
|
|
|
|
AudioUnit::new(au_type)?
|
|
|
|
};
|
|
|
|
|
|
|
|
if input {
|
|
|
|
// Enable input processing.
|
|
|
|
let enable_input = 1u32;
|
|
|
|
audio_unit.set_property(
|
|
|
|
kAudioOutputUnitProperty_EnableIO,
|
|
|
|
Scope::Input,
|
|
|
|
Element::Input,
|
|
|
|
Some(&enable_input),
|
|
|
|
)?;
|
|
|
|
|
|
|
|
// Disable output processing.
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2019-09-05 08:34:07 +00:00
|
|
|
impl Device {
|
2020-01-22 05:52:18 +00:00
|
|
|
fn build_input_stream_raw<D, E>(
|
2020-01-15 16:21:11 +00:00
|
|
|
&self,
|
2020-02-02 18:22:27 +00:00
|
|
|
config: &StreamConfig,
|
|
|
|
sample_format: SampleFormat,
|
2020-01-15 16:21:11 +00:00
|
|
|
mut data_callback: D,
|
2020-05-01 13:19:28 +00:00
|
|
|
mut error_callback: E,
|
2020-01-15 16:21:11 +00:00
|
|
|
) -> Result<Stream, BuildStreamError>
|
|
|
|
where
|
2020-04-16 12:50:36 +00:00
|
|
|
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
|
2020-01-15 16:21:11 +00:00
|
|
|
E: FnMut(StreamError) + Send + 'static,
|
|
|
|
{
|
2018-04-01 11:25:46 +00:00
|
|
|
// The scope and element for working with a device's input stream.
|
2018-02-12 13:10:24 +00:00
|
|
|
let scope = Scope::Output;
|
|
|
|
let element = Element::Input;
|
2017-11-03 09:51:02 +00:00
|
|
|
|
2018-04-01 11:25:46 +00:00
|
|
|
// Check whether or not we need to change the device sample rate to suit the one specified for the stream.
|
|
|
|
unsafe {
|
|
|
|
// Get the current sample rate.
|
|
|
|
let mut property_address = AudioObjectPropertyAddress {
|
|
|
|
mSelector: kAudioDevicePropertyNominalSampleRate,
|
2020-01-20 19:35:23 +00:00
|
|
|
mScope: kAudioObjectPropertyScopeGlobal,
|
|
|
|
mElement: kAudioObjectPropertyElementMaster,
|
2018-04-01 11:25:46 +00:00
|
|
|
};
|
|
|
|
let sample_rate: f64 = 0.0;
|
|
|
|
let data_size = mem::size_of::<f64>() as u32;
|
|
|
|
let status = AudioObjectGetPropertyData(
|
2019-09-05 08:34:07 +00:00
|
|
|
self.audio_device_id,
|
2018-04-01 11:25:46 +00:00
|
|
|
&property_address as *const _,
|
|
|
|
0,
|
|
|
|
null(),
|
|
|
|
&data_size as *const _ as *mut _,
|
|
|
|
&sample_rate as *const _ as *mut _,
|
|
|
|
);
|
|
|
|
coreaudio::Error::from_os_status(status)?;
|
|
|
|
|
|
|
|
// If the requested sample rate is different to the device sample rate, update the device.
|
2020-01-28 14:03:27 +00:00
|
|
|
if sample_rate as u32 != config.sample_rate.0 {
|
2018-04-01 11:25:46 +00:00
|
|
|
// Get available sample rate ranges.
|
|
|
|
property_address.mSelector = kAudioDevicePropertyAvailableNominalSampleRates;
|
|
|
|
let data_size = 0u32;
|
|
|
|
let status = AudioObjectGetPropertyDataSize(
|
2019-09-05 08:34:07 +00:00
|
|
|
self.audio_device_id,
|
2018-04-01 11:25:46 +00:00
|
|
|
&property_address as *const _,
|
|
|
|
0,
|
|
|
|
null(),
|
|
|
|
&data_size as *const _ as *mut _,
|
|
|
|
);
|
|
|
|
coreaudio::Error::from_os_status(status)?;
|
|
|
|
let n_ranges = data_size as usize / mem::size_of::<AudioValueRange>();
|
|
|
|
let mut ranges: Vec<u8> = vec![];
|
|
|
|
ranges.reserve_exact(data_size as usize);
|
|
|
|
let status = AudioObjectGetPropertyData(
|
2019-09-05 08:34:07 +00:00
|
|
|
self.audio_device_id,
|
2018-04-01 11:25:46 +00:00
|
|
|
&property_address as *const _,
|
|
|
|
0,
|
|
|
|
null(),
|
|
|
|
&data_size as *const _ as *mut _,
|
|
|
|
ranges.as_mut_ptr() as *mut _,
|
|
|
|
);
|
|
|
|
coreaudio::Error::from_os_status(status)?;
|
|
|
|
let ranges: *mut AudioValueRange = ranges.as_mut_ptr() as *mut _;
|
|
|
|
let ranges: &'static [AudioValueRange] = slice::from_raw_parts(ranges, n_ranges);
|
|
|
|
|
|
|
|
// Now that we have the available ranges, pick the one matching the desired rate.
|
2020-01-28 14:03:27 +00:00
|
|
|
let sample_rate = config.sample_rate.0;
|
2020-01-20 19:35:23 +00:00
|
|
|
let maybe_index = ranges.iter().position(|r| {
|
|
|
|
r.mMinimum as u32 == sample_rate && r.mMaximum as u32 == sample_rate
|
|
|
|
});
|
2018-04-01 11:25:46 +00:00
|
|
|
let range_index = match maybe_index {
|
2020-01-28 15:38:02 +00:00
|
|
|
None => return Err(BuildStreamError::StreamConfigNotSupported),
|
2018-04-01 11:25:46 +00:00
|
|
|
Some(i) => i,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Update the property selector to specify the nominal sample rate.
|
|
|
|
property_address.mSelector = kAudioDevicePropertyNominalSampleRate;
|
|
|
|
|
|
|
|
// Setting the sample rate of a device is an asynchronous process in coreaudio.
|
|
|
|
//
|
|
|
|
// Thus we are required to set a `listener` so that we may be notified when the
|
|
|
|
// change occurs.
|
|
|
|
unsafe extern "C" fn rate_listener(
|
|
|
|
device_id: AudioObjectID,
|
|
|
|
_n_addresses: u32,
|
|
|
|
_properties: *const AudioObjectPropertyAddress,
|
|
|
|
rate_ptr: *mut ::std::os::raw::c_void,
|
|
|
|
) -> OSStatus {
|
|
|
|
let rate_ptr: *const f64 = rate_ptr as *const _;
|
|
|
|
let data_size = mem::size_of::<f64>();
|
|
|
|
let property_address = AudioObjectPropertyAddress {
|
|
|
|
mSelector: kAudioDevicePropertyNominalSampleRate,
|
2020-01-20 19:35:23 +00:00
|
|
|
mScope: kAudioObjectPropertyScopeGlobal,
|
|
|
|
mElement: kAudioObjectPropertyElementMaster,
|
2018-04-01 11:25:46 +00:00
|
|
|
};
|
|
|
|
AudioObjectGetPropertyData(
|
|
|
|
device_id,
|
|
|
|
&property_address as *const _,
|
|
|
|
0,
|
|
|
|
null(),
|
|
|
|
&data_size as *const _ as *mut _,
|
|
|
|
rate_ptr as *const _ as *mut _,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add our sample rate change listener callback.
|
|
|
|
let reported_rate: f64 = 0.0;
|
|
|
|
let status = AudioObjectAddPropertyListener(
|
2019-09-05 08:34:07 +00:00
|
|
|
self.audio_device_id,
|
2018-04-01 11:25:46 +00:00
|
|
|
&property_address as *const _,
|
|
|
|
Some(rate_listener),
|
|
|
|
&reported_rate as *const _ as *mut _,
|
|
|
|
);
|
|
|
|
coreaudio::Error::from_os_status(status)?;
|
|
|
|
|
|
|
|
// Finally, set the sample rate.
|
|
|
|
let sample_rate = sample_rate as f64;
|
|
|
|
let status = AudioObjectSetPropertyData(
|
2019-09-05 08:34:07 +00:00
|
|
|
self.audio_device_id,
|
2018-04-01 11:25:46 +00:00
|
|
|
&property_address as *const _,
|
|
|
|
0,
|
|
|
|
null(),
|
|
|
|
data_size,
|
|
|
|
&ranges[range_index] as *const _ as *const _,
|
|
|
|
);
|
|
|
|
coreaudio::Error::from_os_status(status)?;
|
|
|
|
|
|
|
|
// Wait for the reported_rate to change.
|
|
|
|
//
|
|
|
|
// This should not take longer than a few ms, but we timeout after 1 sec just in case.
|
|
|
|
let timer = ::std::time::Instant::now();
|
|
|
|
while sample_rate != reported_rate {
|
2019-12-29 23:02:34 +00:00
|
|
|
if timer.elapsed() > Duration::from_secs(1) {
|
2020-01-20 19:35:23 +00:00
|
|
|
let description =
|
|
|
|
"timeout waiting for sample rate update for device".into();
|
2019-06-21 00:38:59 +00:00
|
|
|
let err = BackendSpecificError { description };
|
|
|
|
return Err(err.into());
|
2018-04-01 11:25:46 +00:00
|
|
|
}
|
2019-12-29 23:02:34 +00:00
|
|
|
thread::sleep(Duration::from_millis(5));
|
2018-04-01 11:25:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Remove the `rate_listener` callback.
|
|
|
|
let status = AudioObjectRemovePropertyListener(
|
2019-09-05 08:34:07 +00:00
|
|
|
self.audio_device_id,
|
2018-04-01 11:25:46 +00:00
|
|
|
&property_address as *const _,
|
|
|
|
Some(rate_listener),
|
|
|
|
&reported_rate as *const _ as *mut _,
|
|
|
|
);
|
|
|
|
coreaudio::Error::from_os_status(status)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-05 08:34:07 +00:00
|
|
|
let mut audio_unit = audio_unit_from_device(self, true)?;
|
2018-04-01 11:25:46 +00:00
|
|
|
|
2017-11-03 09:51:02 +00:00
|
|
|
// Set the stream in interleaved mode.
|
2020-02-02 18:22:27 +00:00
|
|
|
let asbd = asbd_from_config(config, sample_format);
|
2018-02-12 13:10:24 +00:00
|
|
|
audio_unit.set_property(kAudioUnitProperty_StreamFormat, scope, element, Some(&asbd))?;
|
|
|
|
|
|
|
|
// Register the callback that is being called by coreaudio whenever it needs data to be
|
|
|
|
// fed to the audio buffer.
|
2020-01-19 18:42:43 +00:00
|
|
|
let bytes_per_channel = sample_format.sample_size();
|
2020-04-27 18:29:51 +00:00
|
|
|
let sample_rate = config.sample_rate;
|
2018-02-12 13:10:24 +00:00
|
|
|
type Args = render_callback::Args<data::Raw>;
|
|
|
|
audio_unit.set_input_callback(move |args: Args| unsafe {
|
|
|
|
let ptr = (*args.data.data).mBuffers.as_ptr() as *const AudioBuffer;
|
|
|
|
let len = (*args.data.data).mNumberBuffers as usize;
|
|
|
|
let buffers: &[AudioBuffer] = slice::from_raw_parts(ptr, len);
|
2017-11-03 09:51:02 +00:00
|
|
|
|
2018-02-12 13:10:24 +00:00
|
|
|
// TODO: Perhaps loop over all buffers instead?
|
|
|
|
let AudioBuffer {
|
2020-05-01 13:19:28 +00:00
|
|
|
mNumberChannels: channels,
|
2018-02-12 13:10:24 +00:00
|
|
|
mDataByteSize: data_byte_size,
|
2020-01-20 19:35:23 +00:00
|
|
|
mData: data,
|
2018-02-12 13:10:24 +00:00
|
|
|
} = buffers[0];
|
|
|
|
|
2020-01-19 18:42:43 +00:00
|
|
|
let data = data as *mut ();
|
|
|
|
let len = (data_byte_size as usize / bytes_per_channel) as usize;
|
|
|
|
let data = Data::from_parts(data, len, sample_format);
|
2020-04-27 18:29:51 +00:00
|
|
|
|
|
|
|
// TODO: Need a better way to get delay, for now we assume a double-buffer offset.
|
2020-05-01 13:19:28 +00:00
|
|
|
let callback = match host_time_to_stream_instant(args.time_stamp.mHostTime) {
|
|
|
|
Err(err) => {
|
|
|
|
error_callback(err.into());
|
|
|
|
return Err(());
|
|
|
|
}
|
|
|
|
Ok(cb) => cb,
|
|
|
|
};
|
|
|
|
let buffer_frames = len / channels as usize;
|
2020-04-27 18:29:51 +00:00
|
|
|
let delay = frames_to_duration(buffer_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 };
|
2020-04-16 12:50:36 +00:00
|
|
|
data_callback(&data, &info);
|
2018-02-12 13:10:24 +00:00
|
|
|
Ok(())
|
|
|
|
})?;
|
|
|
|
|
|
|
|
audio_unit.start()?;
|
|
|
|
|
2019-09-05 08:34:07 +00:00
|
|
|
Ok(Stream::new(StreamInner {
|
|
|
|
playing: true,
|
|
|
|
audio_unit,
|
|
|
|
device_id: self.audio_device_id,
|
|
|
|
}))
|
2018-02-12 13:10:24 +00:00
|
|
|
}
|
|
|
|
|
2020-01-22 05:52:18 +00:00
|
|
|
fn build_output_stream_raw<D, E>(
|
2020-01-15 16:21:11 +00:00
|
|
|
&self,
|
2020-02-02 18:22:27 +00:00
|
|
|
config: &StreamConfig,
|
|
|
|
sample_format: SampleFormat,
|
2020-01-15 16:21:11 +00:00
|
|
|
mut data_callback: D,
|
2020-05-01 13:19:28 +00:00
|
|
|
mut error_callback: E,
|
2020-01-15 16:21:11 +00:00
|
|
|
) -> Result<Stream, BuildStreamError>
|
|
|
|
where
|
2020-04-16 12:50:36 +00:00
|
|
|
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
|
2020-01-15 16:21:11 +00:00
|
|
|
E: FnMut(StreamError) + Send + 'static,
|
|
|
|
{
|
2019-09-05 08:34:07 +00:00
|
|
|
let mut audio_unit = audio_unit_from_device(self, false)?;
|
2018-02-12 13:10:24 +00:00
|
|
|
|
|
|
|
// The scope and element for working with a device's output stream.
|
|
|
|
let scope = Scope::Input;
|
|
|
|
let element = Element::Output;
|
|
|
|
|
|
|
|
// Set the stream in interleaved mode.
|
2020-02-02 18:22:27 +00:00
|
|
|
let asbd = asbd_from_config(config, sample_format);
|
2018-02-12 13:10:24 +00:00
|
|
|
audio_unit.set_property(kAudioUnitProperty_StreamFormat, scope, element, Some(&asbd))?;
|
|
|
|
|
2017-10-18 18:24:05 +00:00
|
|
|
// Register the callback that is being called by coreaudio whenever it needs data to be
|
|
|
|
// fed to the audio buffer.
|
2020-01-19 18:42:43 +00:00
|
|
|
let bytes_per_channel = sample_format.sample_size();
|
2020-04-27 18:29:51 +00:00
|
|
|
let sample_rate = config.sample_rate;
|
2018-02-12 13:10:24 +00:00
|
|
|
type Args = render_callback::Args<data::Raw>;
|
|
|
|
audio_unit.set_render_callback(move |args: Args| unsafe {
|
2017-10-18 18:24:05 +00:00
|
|
|
// If `run()` is currently running, then a callback will be available from this list.
|
|
|
|
// Otherwise, we just fill the buffer with zeroes and return.
|
2017-11-03 09:51:02 +00:00
|
|
|
|
|
|
|
let AudioBuffer {
|
2020-04-27 18:29:51 +00:00
|
|
|
mNumberChannels: channels,
|
2017-11-03 09:51:02 +00:00
|
|
|
mDataByteSize: data_byte_size,
|
2020-01-20 19:35:23 +00:00
|
|
|
mData: data,
|
2017-11-03 09:51:02 +00:00
|
|
|
} = (*args.data.data).mBuffers[0];
|
2018-01-23 05:58:37 +00:00
|
|
|
|
2020-01-19 18:42:43 +00:00
|
|
|
let data = data as *mut ();
|
|
|
|
let len = (data_byte_size as usize / bytes_per_channel) as usize;
|
|
|
|
let mut data = Data::from_parts(data, len, sample_format);
|
2020-04-27 18:29:51 +00:00
|
|
|
|
2020-05-01 13:19:28 +00:00
|
|
|
let callback = match host_time_to_stream_instant(args.time_stamp.mHostTime) {
|
|
|
|
Err(err) => {
|
|
|
|
error_callback(err.into());
|
|
|
|
return Err(());
|
|
|
|
}
|
|
|
|
Ok(cb) => cb,
|
|
|
|
};
|
2020-04-27 18:29:51 +00:00
|
|
|
// TODO: Need a better way to get delay, for now we assume a double-buffer offset.
|
2020-05-01 13:19:28 +00:00
|
|
|
let buffer_frames = len / channels as usize;
|
2020-04-27 18:29:51 +00:00
|
|
|
let delay = frames_to_duration(buffer_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 };
|
2020-04-16 12:50:36 +00:00
|
|
|
data_callback(&mut data, &info);
|
2017-10-18 18:24:05 +00:00
|
|
|
Ok(())
|
2017-11-03 09:50:02 +00:00
|
|
|
})?;
|
2015-09-24 03:02:28 +00:00
|
|
|
|
2017-11-03 09:50:02 +00:00
|
|
|
audio_unit.start()?;
|
2017-10-18 18:24:05 +00:00
|
|
|
|
2019-09-05 08:34:07 +00:00
|
|
|
Ok(Stream::new(StreamInner {
|
|
|
|
playing: true,
|
|
|
|
audio_unit,
|
|
|
|
device_id: self.audio_device_id,
|
|
|
|
}))
|
2017-10-18 18:24:05 +00:00
|
|
|
}
|
2019-09-05 08:34:07 +00:00
|
|
|
}
|
|
|
|
|
2020-04-27 18:29:51 +00:00
|
|
|
fn host_time_to_stream_instant(
|
|
|
|
m_host_time: u64,
|
|
|
|
) -> Result<crate::StreamInstant, BackendSpecificError> {
|
2020-05-01 13:19:28 +00:00
|
|
|
let mut info: mach::mach_time::mach_timebase_info = Default::default();
|
|
|
|
let res = unsafe { mach::mach_time::mach_timebase_info(&mut info) };
|
|
|
|
check_os_status(res)?;
|
|
|
|
let nanos = m_host_time * info.numer as u64 / info.denom as u64;
|
2020-04-27 18:29:51 +00:00
|
|
|
let secs = nanos / 1_000_000_000;
|
|
|
|
let subsec_nanos = nanos - secs * 1_000_000_000;
|
2020-05-01 13:19:28 +00:00
|
|
|
Ok(crate::StreamInstant::new(secs as i64, subsec_nanos as u32))
|
2020-04-27 18:29:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Convert the given duration in frames at the given sample rate to a `std::time::Duration`.
|
|
|
|
fn frames_to_duration(frames: usize, rate: crate::SampleRate) -> std::time::Duration {
|
|
|
|
let secsf = frames as f64 / rate.0 as f64;
|
|
|
|
let secs = secsf as u64;
|
|
|
|
let nanos = ((secsf - secs as f64) * 1_000_000_000.0) as u32;
|
|
|
|
std::time::Duration::new(secs, nanos)
|
|
|
|
}
|
|
|
|
|
2019-09-05 08:34:07 +00:00
|
|
|
pub struct Stream {
|
|
|
|
inner: RefCell<StreamInner>,
|
|
|
|
}
|
2015-02-28 18:50:29 +00:00
|
|
|
|
2019-09-05 08:34:07 +00:00
|
|
|
impl Stream {
|
|
|
|
fn new(inner: StreamInner) -> Self {
|
|
|
|
Self {
|
|
|
|
inner: RefCell::new(inner),
|
2019-06-22 13:23:46 +00:00
|
|
|
}
|
2017-10-18 18:24:05 +00:00
|
|
|
}
|
2019-09-05 08:34:07 +00:00
|
|
|
}
|
2017-04-19 10:44:42 +00:00
|
|
|
|
2019-09-05 08:34:07 +00:00
|
|
|
impl StreamTrait for Stream {
|
|
|
|
fn play(&self) -> Result<(), PlayStreamError> {
|
|
|
|
let mut stream = self.inner.borrow_mut();
|
2016-08-12 07:49:13 +00:00
|
|
|
|
2018-02-12 13:10:24 +00:00
|
|
|
if !stream.playing {
|
2019-06-21 01:03:03 +00:00
|
|
|
if let Err(e) = stream.audio_unit.start() {
|
2019-06-21 01:11:20 +00:00
|
|
|
let description = format!("{}", std::error::Error::description(&e));
|
2019-06-21 01:03:03 +00:00
|
|
|
let err = BackendSpecificError { description };
|
|
|
|
return Err(err.into());
|
|
|
|
}
|
2018-02-12 13:10:24 +00:00
|
|
|
stream.playing = true;
|
2017-10-18 18:24:05 +00:00
|
|
|
}
|
2019-06-21 01:03:03 +00:00
|
|
|
Ok(())
|
2015-02-28 18:50:29 +00:00
|
|
|
}
|
|
|
|
|
2019-09-05 08:34:07 +00:00
|
|
|
fn pause(&self) -> Result<(), PauseStreamError> {
|
|
|
|
let mut stream = self.inner.borrow_mut();
|
2017-10-18 18:24:05 +00:00
|
|
|
|
2018-02-12 13:10:24 +00:00
|
|
|
if stream.playing {
|
2019-06-21 01:03:03 +00:00
|
|
|
if let Err(e) = stream.audio_unit.stop() {
|
2019-06-21 01:11:20 +00:00
|
|
|
let description = format!("{}", std::error::Error::description(&e));
|
2019-06-21 01:03:03 +00:00
|
|
|
let err = BackendSpecificError { description };
|
|
|
|
return Err(err.into());
|
|
|
|
}
|
2019-06-22 13:23:46 +00:00
|
|
|
|
2018-02-12 13:10:24 +00:00
|
|
|
stream.playing = false;
|
2016-10-18 06:20:40 +00:00
|
|
|
}
|
2019-06-21 01:03:03 +00:00
|
|
|
Ok(())
|
2015-02-28 18:50:29 +00:00
|
|
|
}
|
2017-10-18 18:24:05 +00:00
|
|
|
}
|
2019-06-20 22:22:30 +00:00
|
|
|
|
|
|
|
fn check_os_status(os_status: OSStatus) -> Result<(), BackendSpecificError> {
|
|
|
|
match coreaudio::Error::from_os_status(os_status) {
|
|
|
|
Ok(()) => Ok(()),
|
|
|
|
Err(err) => {
|
|
|
|
let description = std::error::Error::description(&err).to_string();
|
|
|
|
Err(BackendSpecificError { description })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|