[macos] Implement Endpoint and Format Enumeration

Based on #195.

Also implements proper handling of the given `Endpoint` in the
macos implementation of the `build_voice` method.

Updates to the latest coreaudio-sys and coreaudio-rs which include the
additional necessary frameworks.

Also adds a line that prints the name of the default device in the
`enumeration.rs` example.

Updates the CHANGELOG for this PR.

Closes #194.
Related to #180.

Related external issues:

- RustAudio/coreaudio-sys#4
- RustAudio/coreaudio-rs#57
This commit is contained in:
mitchmindtree 2018-01-26 23:49:47 +11:00
parent 091798ac5a
commit 3952c44c63
5 changed files with 294 additions and 31 deletions

View File

@ -1,5 +1,6 @@
# Unreleased # Unreleased
- Implement Endpoint and Format Enumeration for macos.
- Implement format handling for macos `build_voice` method. - Implement format handling for macos `build_voice` method.
# Version 0.6.0 (2017-12-11) # Version 0.6.0 (2017-12-11)

View File

@ -21,7 +21,8 @@ alsa-sys = { version = "0.1", path = "alsa-sys" }
libc = "0.2" libc = "0.2"
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
coreaudio-rs = "0.7.0" coreaudio-rs = "0.8.0"
core-foundation-sys = "0.5.1" # For linking to CoreFoundation.framework and handling device name `CFString`s.
[target.'cfg(target_os = "emscripten")'.dependencies] [target.'cfg(target_os = "emscripten")'.dependencies]
stdweb = { version = "0.1.3", default-features = false } stdweb = { version = "0.1.3", default-features = false }

View File

@ -1,8 +1,9 @@
extern crate cpal; extern crate cpal;
fn main() { fn main() {
let endpoints = cpal::endpoints(); println!("Default Endpoint:\n {:?}", cpal::default_endpoint().map(|e| e.name()));
let endpoints = cpal::endpoints();
println!("Endpoints: "); println!("Endpoints: ");
for (endpoint_index, endpoint) in endpoints.enumerate() { for (endpoint_index, endpoint) in endpoints.enumerate() {
println!("{}. Endpoint \"{}\" Audio formats: ", println!("{}. Endpoint \"{}\" Audio formats: ",

View File

@ -1,10 +1,76 @@
use SupportedFormat;
use std::mem;
use std::ptr::null;
use std::vec::IntoIter as VecIntoIter;
use super::coreaudio::sys::{
AudioDeviceID,
AudioObjectPropertyAddress,
AudioObjectGetPropertyData,
AudioObjectGetPropertyDataSize,
kAudioHardwareNoError,
kAudioHardwarePropertyDefaultOutputDevice,
kAudioHardwarePropertyDevices,
kAudioObjectPropertyElementMaster,
kAudioObjectPropertyScopeGlobal,
kAudioObjectSystemObject,
OSStatus,
};
use super::Endpoint; use super::Endpoint;
use SupportedFormat; unsafe fn audio_output_devices() -> Result<Vec<AudioDeviceID>, OSStatus> {
let property_address = AudioObjectPropertyAddress {
mSelector: kAudioHardwarePropertyDevices,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
use std::vec::IntoIter as VecIntoIter; macro_rules! try_status_or_return {
($status:expr) => {
if $status != kAudioHardwareNoError as i32 {
return Err($status);
}
};
}
pub struct EndpointsIterator(bool); let data_size = 0u32;
let status = AudioObjectGetPropertyDataSize(
kAudioObjectSystemObject,
&property_address as *const _,
0,
null(),
&data_size as *const _ as *mut _,
);
try_status_or_return!(status);
let device_count = data_size / mem::size_of::<AudioDeviceID>() as u32;
let mut audio_devices = vec![];
audio_devices.reserve_exact(device_count as usize);
let status = AudioObjectGetPropertyData(
kAudioObjectSystemObject,
&property_address as *const _,
0,
null(),
&data_size as *const _ as *mut _,
audio_devices.as_mut_ptr() as *mut _,
);
try_status_or_return!(status);
audio_devices.set_len(device_count as usize);
// Only keep the devices that have some supported output format.
audio_devices.retain(|&id| {
let e = Endpoint { audio_device_id: id };
match e.supported_formats() {
Err(_) => false,
Ok(mut fmts) => fmts.next().is_some(),
}
});
Ok(audio_devices)
}
pub struct EndpointsIterator(VecIntoIter<AudioDeviceID>);
unsafe impl Send for EndpointsIterator { unsafe impl Send for EndpointsIterator {
} }
@ -13,24 +79,47 @@ unsafe impl Sync for EndpointsIterator {
impl Default for EndpointsIterator { impl Default for EndpointsIterator {
fn default() -> Self { fn default() -> Self {
EndpointsIterator(false) let devices = unsafe {
audio_output_devices().expect("failed to get audio output devices")
};
EndpointsIterator(devices.into_iter())
} }
} }
impl Iterator for EndpointsIterator { impl Iterator for EndpointsIterator {
type Item = Endpoint; type Item = Endpoint;
fn next(&mut self) -> Option<Endpoint> { fn next(&mut self) -> Option<Endpoint> {
if self.0 { self.0.next().map(|id| Endpoint { audio_device_id: id })
None
} else {
self.0 = true;
Some(Endpoint)
}
} }
} }
pub fn default_endpoint() -> Option<Endpoint> { pub fn default_endpoint() -> Option<Endpoint> {
Some(Endpoint) let property_address = AudioObjectPropertyAddress {
mSelector: kAudioHardwarePropertyDefaultOutputDevice,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
let audio_device_id: AudioDeviceID = 0;
let data_size = mem::size_of::<AudioDeviceID>();;
let status = unsafe {
AudioObjectGetPropertyData(
kAudioObjectSystemObject,
&property_address as *const _,
0,
null(),
&data_size as *const _ as *mut _,
&audio_device_id as *const _ as *mut _,
)
};
if status != kAudioHardwareNoError as i32 {
return None;
}
let endpoint = Endpoint {
audio_device_id: audio_device_id,
};
Some(endpoint)
} }
pub type SupportedFormatsIterator = VecIntoIter<SupportedFormat>; pub type SupportedFormatsIterator = VecIntoIter<SupportedFormat>;

View File

@ -1,4 +1,5 @@
extern crate coreaudio; extern crate coreaudio;
extern crate core_foundation_sys;
use ChannelPosition; use ChannelPosition;
use CreationError; use CreationError;
@ -10,7 +11,10 @@ use SamplesRate;
use SupportedFormat; use SupportedFormat;
use UnknownTypeBuffer; use UnknownTypeBuffer;
use std::ffi::CStr;
use std::mem; use std::mem;
use std::os::raw::c_char;
use std::ptr::null;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::thread; use std::thread;
use std::time::Duration; use std::time::Duration;
@ -18,13 +22,32 @@ use std::slice;
use self::coreaudio::audio_unit::{AudioUnit, Scope, Element}; use self::coreaudio::audio_unit::{AudioUnit, Scope, Element};
use self::coreaudio::audio_unit::render_callback::{self, data}; use self::coreaudio::audio_unit::render_callback::{self, data};
use self::coreaudio::bindings::audio_unit::{ use self::coreaudio::sys::{
AudioStreamBasicDescription,
AudioBuffer, AudioBuffer,
kAudioFormatLinearPCM, AudioBufferList,
AudioDeviceID,
AudioObjectGetPropertyData,
AudioObjectGetPropertyDataSize,
AudioObjectPropertyAddress,
AudioStreamBasicDescription,
AudioValueRange,
kAudioDevicePropertyAvailableNominalSampleRates,
kAudioDevicePropertyDeviceNameCFString,
kAudioDevicePropertyScopeOutput,
kAudioDevicePropertyStreamConfiguration,
kAudioFormatFlagIsFloat, kAudioFormatFlagIsFloat,
kAudioFormatFlagIsPacked, kAudioFormatFlagIsPacked,
kAudioUnitProperty_StreamFormat kAudioFormatLinearPCM,
kAudioHardwareNoError,
kAudioObjectPropertyElementMaster,
kAudioObjectPropertyScopeOutput,
kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitProperty_StreamFormat,
kCFStringEncodingUTF8,
};
use self::core_foundation_sys::string::{
CFStringRef,
CFStringGetCStringPtr,
}; };
mod enumerate; mod enumerate;
@ -32,24 +55,166 @@ mod enumerate;
pub use self::enumerate::{EndpointsIterator, SupportedFormatsIterator, default_endpoint}; pub use self::enumerate::{EndpointsIterator, SupportedFormatsIterator, default_endpoint};
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq)]
pub struct Endpoint; pub struct Endpoint {
audio_device_id: AudioDeviceID,
}
impl Endpoint { impl Endpoint {
pub fn supported_formats(&self) -> Result<SupportedFormatsIterator, FormatsEnumerationError> { pub fn supported_formats(&self) -> Result<SupportedFormatsIterator, FormatsEnumerationError> {
Ok( let mut property_address = AudioObjectPropertyAddress {
vec![ mSelector: kAudioDevicePropertyStreamConfiguration,
SupportedFormat { mScope: kAudioObjectPropertyScopeOutput,
channels: vec![ChannelPosition::FrontLeft, ChannelPosition::FrontRight], mElement: kAudioObjectPropertyElementMaster,
min_samples_rate: SamplesRate(44100), };
max_samples_rate: SamplesRate(44100),
data_type: SampleFormat::F32, unsafe {
}, // Retrieve the devices audio buffer list.
].into_iter(), let data_size = 0u32;
) let status = AudioObjectGetPropertyDataSize(
self.audio_device_id,
&property_address as *const _,
0,
null(),
&data_size as *const _ as *mut _,
);
if status != kAudioHardwareNoError as i32 {
unimplemented!();
}
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 _,
);
if status != kAudioHardwareNoError as i32 {
unimplemented!();
}
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;
}
const CHANNEL_POSITIONS: &'static [ChannelPosition] = &[
ChannelPosition::FrontLeft,
ChannelPosition::FrontRight,
ChannelPosition::FrontCenter,
ChannelPosition::LowFrequency,
ChannelPosition::BackLeft,
ChannelPosition::BackRight,
ChannelPosition::FrontLeftOfCenter,
ChannelPosition::FrontRightOfCenter,
ChannelPosition::BackCenter,
ChannelPosition::SideLeft,
ChannelPosition::SideRight,
ChannelPosition::TopCenter,
ChannelPosition::TopFrontLeft,
ChannelPosition::TopFrontCenter,
ChannelPosition::TopFrontRight,
ChannelPosition::TopBackLeft,
ChannelPosition::TopBackCenter,
ChannelPosition::TopBackRight,
];
// 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 _,
);
if status != kAudioHardwareNoError as i32 {
unimplemented!();
}
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 _,
);
if status != kAudioHardwareNoError as i32 {
unimplemented!();
}
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 {
let channels = CHANNEL_POSITIONS.iter()
.cloned()
.cycle()
.take(n_channels)
.collect::<Vec<_>>();
let fmt = SupportedFormat {
channels: channels.clone(),
min_samples_rate: SamplesRate(range.mMinimum as _),
max_samples_rate: SamplesRate(range.mMaximum as _),
data_type: sample_format,
};
fmts.push(fmt);
}
Ok(fmts.into_iter())
}
} }
pub fn name(&self) -> String { pub fn name(&self) -> String {
"Default AudioUnit Endpoint".to_string() 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 _,
);
if status != kAudioHardwareNoError as i32 {
return format!("<OSStatus: {:?}>", status);
}
let c_string: *const c_char = CFStringGetCStringPtr(device_name, kCFStringEncodingUTF8);
if c_string == null() {
return "<null>".into();
}
CStr::from_ptr(c_string as *mut _)
};
c_str.to_string_lossy().into_owned()
} }
} }
@ -117,7 +282,7 @@ impl EventLoop {
} }
#[inline] #[inline]
pub fn build_voice(&self, _endpoint: &Endpoint, format: &Format) pub fn build_voice(&self, endpoint: &Endpoint, format: &Format)
-> Result<VoiceId, CreationError> { -> Result<VoiceId, CreationError> {
let mut audio_unit = { let mut audio_unit = {
let au_type = if cfg!(target_os = "ios") { let au_type = if cfg!(target_os = "ios") {
@ -132,7 +297,13 @@ impl EventLoop {
AudioUnit::new(au_type)? AudioUnit::new(au_type)?
}; };
// TODO: iOS uses integer and fixed-point data // TODO: Set the audio output unit device as the given endpoint device.
audio_unit.set_property(
kAudioOutputUnitProperty_CurrentDevice,
Scope::Global,
Element::Output,
Some(&endpoint.audio_device_id),
)?;
// Set the stream in interleaved mode. // Set the stream in interleaved mode.
let n_channels = format.channels.len(); let n_channels = format.channels.len();