Merge pull request #27 from mitchmindtree/master

OSX support via the Apple Core Audio, Audio Unit C API. Only supports f32 samples so far. closes #15
This commit is contained in:
tomaka 2015-02-27 23:58:13 +01:00
commit 502b5f647d
10 changed files with 11043 additions and 4 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
/target /target
/Cargo.lock /Cargo.lock
.cargo/ .cargo/
.DS_Store

View File

@ -32,5 +32,9 @@ path = "alsa-sys"
version = "0" version = "0"
path = "alsa-sys" path = "alsa-sys"
[target.x86_64-apple-darwin.dependencies.core_audio-sys]
version = "0"
path = "core_audio-sys"
[dev-dependencies] [dev-dependencies]
vorbis = "0" vorbis = "0"

3
core_audio-sys/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target
/Cargo.lock
.cargo/

10
core_audio-sys/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "core_audio-sys"
version = "0.0.1"
authors = ["Mitchell Nordine <mitchell.nordine@gmail.com>"]
description = "Bindings for Apple's CoreAudio, AudioUnit and AudioToolbox APIs generated via rust-bindgen"
license = "MIT"
[dependencies]
libc = "*"

File diff suppressed because it is too large Load Diff

10
core_audio-sys/src/lib.rs Normal file
View File

@ -0,0 +1,10 @@
#![allow(missing_copy_implementations)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(raw_pointer_derive)]
extern crate libc;
pub mod audio_unit;

View File

@ -1,3 +1,5 @@
#![feature(core)]
extern crate cpal; extern crate cpal;
fn main() { fn main() {

242
src/core_audio/mod.rs Normal file
View File

@ -0,0 +1,242 @@
extern crate "core_audio-sys" as core_audio;
extern crate libc;
use self::core_audio::audio_unit as au;
use std::mem;
use std::ptr::null_mut;
use std::sync::mpsc::{channel, Sender, Receiver};
type NumChannels = usize;
#[allow(dead_code)]
pub struct Voice {
audio_unit: *mut au::AudioUnit,
ready_receiver: Receiver<()>,
samples_sender: Sender<(Vec<f32>, NumChannels)>,
}
pub struct Buffer<'a, T: 'a> {
samples_sender: Sender<(Vec<f32>, NumChannels)>,
samples: Vec<T>,
num_channels: NumChannels,
marker: ::std::marker::PhantomData<&'a T>,
}
struct CallbackConnection {
ready_sender: Sender<()>,
samples_receiver: Receiver<(Vec<f32>, NumChannels)>,
}
const INPUT_SCOPE : au::AudioUnitScope = au::kAudioUnitScope_Input;
const OUTPUT_ELEMENT : au::AudioUnitElement = 0;
impl Voice {
pub fn new() -> Voice {
new_voice().unwrap()
}
pub fn get_channels(&self) -> ::ChannelsCount {
// TODO: use AudioUnitGetProperty...
2
}
pub fn get_samples_rate(&self) -> ::SamplesRate {
// TODO: use AudioUnitGetProperty...
::SamplesRate(44100)
}
pub fn get_samples_format(&self) -> ::SampleFormat {
// TODO: use AudioUnitGetProperty...
::SampleFormat::F32
}
pub fn append_data<'a, T>(&'a mut self, buffer_size: usize) -> Buffer<'a, T> where T: Clone {
while let None = self.ready_receiver.try_recv().ok() {}
Buffer {
samples_sender: self.samples_sender.clone(),
samples: vec![unsafe{ mem::uninitialized() }; buffer_size],
num_channels: 2,
marker: ::std::marker::PhantomData,
}
}
pub fn play(&mut self) {
// TODO
}
pub fn pause(&mut self) {
// TODO
}
}
impl Drop for Voice {
fn drop(&mut self) {
unsafe {
check_errors(au::AudioOutputUnitStop(*self.audio_unit)).unwrap();
check_errors(au::AudioUnitUninitialize(*self.audio_unit)).unwrap();
}
}
}
impl<'a, T> Buffer<'a, T> {
pub fn get_buffer<'b>(&'b mut self) -> &'b mut [T] {
&mut self.samples[..]
}
pub fn finish(self) {
let Buffer { samples_sender, samples, num_channels, .. } = self;
// TODO: At the moment this assumes the Vec<T> is a Vec<f32>.
// Need to add T: Sample and use Sample::to_vec_f32.
let samples = unsafe { mem::transmute(samples) };
match samples_sender.send((samples, num_channels)) {
Err(_) => panic!("Failed to send samples to audio unit callback."),
Ok(()) => (),
}
}
}
/// Construct a new Voice.
fn new_voice() -> Result<Voice, String> {
let mut audio_unit = try!(default_audio_unit());
// A channel for signalling that the audio unit is ready for data.
let (ready_sender, ready_receiver) = channel();
// A channel for sending the audio callback a pointer to the sample data.
let (samples_sender, samples_receiver) = channel();
let callback_connection = box CallbackConnection {
ready_sender: ready_sender,
samples_receiver: samples_receiver,
};
let size_of_render_callback_struct = mem::size_of::<au::AURenderCallbackStruct>() as u32;
unsafe {
// Setup render callback.
let render_callback = au::AURenderCallbackStruct {
inputProc: Some(input_proc), // TODO
inputProcRefCon: mem::transmute(callback_connection),
};
try!(check_errors(au::AudioUnitSetProperty(audio_unit,
au::kAudioUnitProperty_SetRenderCallback,
INPUT_SCOPE,
OUTPUT_ELEMENT,
&render_callback as *const _ as *const libc::c_void,
size_of_render_callback_struct)));
// Initialise the audio unit!
try!(check_errors(au::AudioUnitInitialize(audio_unit)));
try!(check_errors(au::AudioOutputUnitStart(audio_unit)));
Ok(Voice {
audio_unit: &mut audio_unit as *mut au::AudioUnit,
ready_receiver: ready_receiver,
samples_sender: samples_sender,
})
}
}
/// Construct and initiate a new, default AudioUnit.
fn default_audio_unit() -> Result<au::AudioUnit, String> {
// A description of the audio unit we desire.
let desc = au::AudioComponentDescription {
componentType : au::kAudioUnitType_Output,
componentSubType : au::kAudioUnitSubType_HALOutput,
componentManufacturer : au::kAudioUnitManufacturer_Apple,
componentFlags : 0,
componentFlagsMask : 0,
};
unsafe {
// Find the default audio unit for the description.
let component = match au::AudioComponentFindNext(null_mut(), &desc as *const _) {
component if component == null_mut() => panic!("Could not find a default audio device."),
component => component,
};
// Get an instance of the default audio unit using the component.
let mut audio_unit: au::AudioUnit = mem::uninitialized();
au::AudioComponentInstanceNew(component, &mut audio_unit as *mut au::AudioUnit);
Ok(audio_unit as au::AudioUnit)
}
}
/// Callback procedure that will be called each time our audio_unit requests audio.
extern "C" fn input_proc(in_ref_con: *mut libc::c_void,
_io_action_flags: *mut au::AudioUnitRenderActionFlags,
_in_time_stamp: *const au::AudioTimeStamp,
_in_bus_number: au::UInt32,
in_number_frames: au::UInt32,
io_data: *mut au::AudioBufferList) -> au::OSStatus {
let callback_connection = in_ref_con as *mut CallbackConnection;
let (samples, num_channels) = match unsafe { (*callback_connection).samples_receiver.try_recv() } {
Ok((samples, num_channels)) => (samples, num_channels),
_ => (vec![0.0; 1024], 2),
};
if let Err(_) = unsafe { (*callback_connection).ready_sender.send(()) } {
return -1500
}
assert!(in_number_frames == (samples.len() / num_channels) as u32,
"The number of input frames given differs from the number requested by the AudioUnit");
let mut channels: Vec<&mut [f32]> = unsafe {
(0..num_channels)
.map(|i| {
let slice_ptr = (*io_data).mBuffers[i].mData as *mut libc::c_float;
::std::slice::from_raw_parts_mut(slice_ptr, in_number_frames as usize)
})
.collect()
};
for (i, frame) in samples.chunks(num_channels).enumerate() {
for (channel, sample) in channels.iter_mut().zip(frame.iter()) {
channel[i] = *sample;
}
}
0
}
/// Convert the AudioUnit OSStatus result into a Rust Result.
fn check_errors(result: au::OSStatus) -> Result<(), String> {
if result == 0 { return Ok(()); }
Err(format!("OSStatus {:?}: {:?}", result, match result {
-1500 => "An unspecified error has occurred",
-1501 => "System sound client message timed out",
-10847 => "Unauthorized",
-10848 => "Invalid offline render",
-10849 => "Initialized",
-10850 => "Property not in use",
-10851 => "Invalid property type",
-10863 => "Cannot do in current context",
-10865 => "Property not writeable",
-10866 => "Invalid scope",
-10867 => "Uninitialized",
-10868 => "Format not supported",
-10869 => "File not specified",
-10870 => "Unknown file type",
-10871 => "Invalid file",
-10872 => "Instrument type not found",
-10873 => "Illegal instrument",
-10874 => "Too many frames to process",
-10875 => "Failed initialization",
-10876 => "No connection",
-10877 => "Invalid element",
-10878 => "Invalid parameter",
-10879 => "Invalid property",
_ => "Unknown error",
}))
}

View File

@ -47,8 +47,7 @@ a conversion on your data.
If you have the possibility, you should try to match the format of the voice. If you have the possibility, you should try to match the format of the voice.
*/ */
#![allow(unstable)] #![feature(box_syntax, core, unsafe_destructor)]
#![feature(unsafe_destructor)]
#![unstable] #![unstable]
pub use samples_formats::{SampleFormat, Sample}; pub use samples_formats::{SampleFormat, Sample};
@ -58,13 +57,18 @@ use std::ops::{Deref, DerefMut};
mod conversions; mod conversions;
mod samples_formats; mod samples_formats;
#[cfg(unix)] #[cfg(target_os = "linux")]
#[path="alsa/mod.rs"] #[path="alsa/mod.rs"]
mod cpal_impl; mod cpal_impl;
#[cfg(windows)] #[cfg(windows)]
#[path="wasapi/mod.rs"] #[path="wasapi/mod.rs"]
mod cpal_impl; mod cpal_impl;
#[cfg(target_os = "macos")]
#[path="core_audio/mod.rs"]
mod cpal_impl;
#[cfg(all(not(windows), not(unix)))] #[cfg(all(not(windows), not(unix)))]
#[path="null/mod.rs"] #[path="null/mod.rs"]
mod cpal_impl; mod cpal_impl;

View File

@ -18,7 +18,7 @@ impl Voice {
::SampleFormat::U16 ::SampleFormat::U16
} }
pub fn append_data<'a, T>(&'a mut self, _: uint) -> Buffer<'a, T> { pub fn append_data<'a, T>(&'a mut self, _: usize) -> Buffer<'a, T> {
Buffer Buffer
} }