Skip to content

Commit

Permalink
Fix iOS compilation error
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelHills committed Sep 30, 2020
1 parent 0cbf885 commit 1d8a55d
Show file tree
Hide file tree
Showing 4 changed files with 280 additions and 5 deletions.
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ script:
- cargo build --verbose
- cargo test --verbose
- cargo doc --verbose
- cargo build --verbose --target aarch64-apple-ios
- cargo test --verbose --target aarch64-apple-ios
- cargo build --verbose --target x86_64-apple-ios
- cargo test --verbose --target x86_64-apple-ios
after_success: |
[ $TRAVIS_BRANCH = master ] &&
[ $TRAVIS_PULL_REQUEST = false ] &&
Expand Down
208 changes: 208 additions & 0 deletions examples/feedback.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
//! A basic input + output stream example, copying the mic input stream to the default output stream
extern crate coreaudio;

use std::collections::VecDeque;
use std::mem;
use std::ptr::null;
use std::sync::{Arc, Mutex};

use coreaudio::audio_unit::audio_format::LinearPcmFlags;
use coreaudio::audio_unit::render_callback::{self, data};
use coreaudio::audio_unit::{AudioUnit, Element, SampleFormat, Scope, StreamFormat};
use coreaudio::sys::*;

type S = f32;
const SAMPLE_FORMAT: SampleFormat = SampleFormat::F32;
const BASE_FLAGS: LinearPcmFlags = LinearPcmFlags::IS_FLOAT;

// type S = i16;
// const SAMPLE_FORMAT: SampleFormat = SampleFormat::I16;
// const BASE_FLAGS: LinearPcmFlags = LinearPcmFlags::IS_SIGNED_INTEGER;

fn main() -> Result<(), coreaudio::Error> {
let mut input_audio_unit = audio_unit_from_device(default_input_device().unwrap(), true)?;
let mut output_audio_unit = audio_unit_from_device(default_output_device().unwrap(), false)?;

// TODO
// - input 1/2 channels float/signed-integer, output 1/2 channels float / signed integer

let in_stream_format = StreamFormat {
sample_rate: 44100.0,
sample_format: SAMPLE_FORMAT,
flags: BASE_FLAGS | LinearPcmFlags::IS_PACKED | LinearPcmFlags::IS_NON_INTERLEAVED,
channels_per_frame: 1,
};

let out_stream_format = StreamFormat {
sample_rate: 44100.0,
sample_format: SAMPLE_FORMAT,
flags: BASE_FLAGS | LinearPcmFlags::IS_PACKED | LinearPcmFlags::IS_NON_INTERLEAVED,
channels_per_frame: 2,
};

println!("input={:#?}", &in_stream_format);
println!("output={:#?}", &out_stream_format);
println!("input_asbd={:#?}", &in_stream_format.to_asbd());
println!("output_asbd={:#?}", &out_stream_format.to_asbd());

let id = kAudioUnitProperty_StreamFormat;
let asbd = in_stream_format.to_asbd();
input_audio_unit.set_property(id, Scope::Output, Element::Input, Some(&asbd))?;

let asbd = out_stream_format.to_asbd();
output_audio_unit.set_property(id, Scope::Input, Element::Output, Some(&asbd))?;

let buffer_left = Arc::new(Mutex::new(VecDeque::<S>::new()));
let producer_left = buffer_left.clone();
let consumer_left = buffer_left.clone();
let buffer_right = Arc::new(Mutex::new(VecDeque::<S>::new()));
let producer_right = buffer_right.clone();
let consumer_right = buffer_right.clone();

// seed roughly 1 second of data to create a delay in the feedback loop for easier testing
for buffer in vec![buffer_left, buffer_right] {
let mut buffer = buffer.lock().unwrap();
for _ in 0..(out_stream_format.sample_rate as i32) {
buffer.push_back(0 as S);
}
}

type Args = render_callback::Args<data::NonInterleaved<S>>;
input_audio_unit.set_input_callback(move |args| {
let Args {
num_frames,
mut data,
..
} = args;
let buffer_left = producer_left.lock().unwrap();
let buffer_right = producer_right.lock().unwrap();
let mut buffers = vec![buffer_left, buffer_right];
for i in 0..num_frames {
for (ch, channel) in data.channels_mut().enumerate() {
let value: S = channel[i];
buffers[ch].push_back(value);
}
}
Ok(())
})?;
input_audio_unit.start()?;

output_audio_unit.set_render_callback(move |args: Args| {
let Args {
num_frames,
mut data,
..
} = args;

let buffer_left = consumer_left.lock().unwrap();
let buffer_right = consumer_right.lock().unwrap();
let mut buffers = vec![buffer_left, buffer_right];
for i in 0..num_frames {
// Default other channels to copy value from first channel as a fallback
let zero: S = 0 as S;
let f: S = *buffers[0].front().unwrap_or(&zero);
for (ch, channel) in data.channels_mut().enumerate() {
let sample: S = buffers[ch].pop_front().unwrap_or(f);
channel[i] = sample;
}
}
Ok(())
})?;
output_audio_unit.start()?;

std::thread::sleep(std::time::Duration::from_millis(100000));

Ok(())
}

/// Copied from cpal
pub fn default_input_device() -> Option<AudioDeviceID> {
let property_address = AudioObjectPropertyAddress {
mSelector: kAudioHardwarePropertyDefaultInputDevice,
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;
}

Some(audio_device_id)
}

/// Copied from cpal
pub fn default_output_device() -> Option<AudioDeviceID> {
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;
}

Some(audio_device_id)
}

/// Copied from cpal
fn audio_unit_from_device(
device_id: AudioDeviceID,
input: bool,
) -> Result<AudioUnit, coreaudio::Error> {
let mut audio_unit = AudioUnit::new(coreaudio::audio_unit::IOType::HalOutput)?;

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_id),
)?;

Ok(audio_unit)
}
48 changes: 48 additions & 0 deletions src/audio_unit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,29 @@ impl AudioUnit {
}
}

/// On successful initialization, the audio formats for input and output are valid
/// and the audio unit is ready to render. During initialization, an audio unit
/// allocates memory according to the maximum number of audio frames it can produce
/// in response to a single render call.
///
/// Usually, the state of an audio unit (such as its I/O formats and memory allocations)
/// cannot be changed while an audio unit is initialized.
pub fn initialize(&mut self) -> Result<(), Error> {
unsafe { try_os_status!(sys::AudioUnitInitialize(self.instance)); }
Ok(())
}

/// Before you change an initialize audio unit’s processing characteristics,
/// such as its input or output audio data format or its sample rate, you must
/// first uninitialize it. Calling this function deallocates the audio unit’s resources.
///
/// After calling this function, you can reconfigure the audio unit and then call
/// AudioUnitInitialize to reinitialize it.
pub fn uninitialize(&mut self) -> Result<(), Error> {
unsafe { try_os_status!(sys::AudioUnitUninitialize(self.instance)); }
Ok(())
}

/// Sets the value for some property of the **AudioUnit**.
///
/// To clear an audio unit property value, set the data paramater with `None::<()>`.
Expand Down Expand Up @@ -375,3 +398,28 @@ pub fn get_property<T>(
Ok(data)
}
}

/// Gets the value of a specified audio session property.
///
/// **Available** in iOS 2.0 and later.
///
/// Parameters
/// ----------
///
/// - **id**: The identifier of the property.
#[cfg(target_os = "ios")]
pub fn audio_session_get_property<T>(
id: u32,
) -> Result<T, Error>
{
let mut size = ::std::mem::size_of::<T>() as u32;
unsafe {
let mut data: T = ::std::mem::uninitialized();
let data_ptr = &mut data as *mut _ as *mut c_void;
let size_ptr = &mut size as *mut _;
try_os_status!(
sys::AudioSessionGetProperty(id, size_ptr, data_ptr)
);
Ok(data)
}
}
25 changes: 20 additions & 5 deletions src/audio_unit/render_callback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ use sys;
pub use self::action_flags::ActionFlags;
pub use self::data::Data;

#[cfg(target_os = "ios")]
use audio_unit::audio_session_get_property;


/// When `set_render_callback` is called, a closure of this type will be used to wrap the given
/// render callback function.
Expand Down Expand Up @@ -398,7 +401,7 @@ impl AudioUnit {
// First, we'll retrieve the stream format so that we can ensure that the given callback
// format matches the audio unit's format.
let id = sys::kAudioUnitProperty_StreamFormat;
let asbd = try!(self.get_property(id, Scope::Output, Element::Output));
let asbd = try!(self.get_property(id, Scope::Input, Element::Output));
let stream_format = super::StreamFormat::from_asbd(asbd)?;

// If the stream format does not match, return an error indicating this.
Expand Down Expand Up @@ -471,7 +474,7 @@ impl AudioUnit {
// First, we'll retrieve the stream format so that we can ensure that the given callback
// format matches the audio unit's format.
let id = sys::kAudioUnitProperty_StreamFormat;
let asbd = self.get_property(id, Scope::Input, Element::Input)?;
let asbd = self.get_property(id, Scope::Output, Element::Input)?;
let stream_format = super::StreamFormat::from_asbd(asbd)?;

// If the stream format does not match, return an error indicating this.
Expand All @@ -482,8 +485,20 @@ impl AudioUnit {
// Pre-allocate a buffer list for input stream.
//
// First, get the current buffer size for pre-allocating the `AudioBuffer`s.
let id = sys::kAudioDevicePropertyBufferFrameSize;
let mut buffer_frame_size: u32 = self.get_property(id, Scope::Global, Element::Output)?;
#[cfg(target_os = "macos")]
let mut buffer_frame_size: u32 = {
let id = sys::kAudioDevicePropertyBufferFrameSize;
let buffer_frame_size: u32 = self.get_property(id, Scope::Global, Element::Output)?;
buffer_frame_size
};
#[cfg(target_os = "ios")]
let mut buffer_frame_size: u32 = {
let id = sys::kAudioSessionProperty_CurrentHardwareIOBufferDuration;
let seconds: f32 = super::audio_session_get_property(id)?;
let id = sys::kAudioSessionProperty_CurrentHardwareSampleRate;
let sample_rate: f64 = super::audio_session_get_property(id)?;
(sample_rate * seconds as f64).round() as u32
};
let mut data: Vec<u8> = vec![];
let sample_bytes = stream_format.sample_format.size_in_bytes();
let n_channels = stream_format.channels_per_frame;
Expand Down Expand Up @@ -525,7 +540,7 @@ impl AudioUnit {
unsafe {
// Retrieve the up-to-date stream format.
let id = sys::kAudioUnitProperty_StreamFormat;
let asbd = match super::get_property(audio_unit, id, Scope::Input, Element::Output) {
let asbd = match super::get_property(audio_unit, id, Scope::Output, Element::Input) {
Err(err) => return err.to_os_status(),
Ok(asbd) => asbd,
};
Expand Down

0 comments on commit 1d8a55d

Please sign in to comment.