From 489d6c3acf556149a39b60be83ee1845e380e3d3 Mon Sep 17 00:00:00 2001 From: Michael Hills Date: Mon, 28 Sep 2020 07:47:19 +1000 Subject: [PATCH] Fix iOS compilation error --- .travis.yml | 2 + examples/feedback.rs | 208 ++++++++++++++++++++++++++++++ src/audio_unit/mod.rs | 48 +++++++ src/audio_unit/render_callback.rs | 25 +++- 4 files changed, 278 insertions(+), 5 deletions(-) create mode 100644 examples/feedback.rs diff --git a/.travis.yml b/.travis.yml index 27d5f0e88..36abbef92 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,8 @@ script: - cargo build --verbose - cargo test --verbose - cargo doc --verbose +- cargo build --verbose --target aarch64-apple-ios +- cargo test --verbose --target aarch64-apple-ios after_success: | [ $TRAVIS_BRANCH = master ] && [ $TRAVIS_PULL_REQUEST = false ] && diff --git a/examples/feedback.rs b/examples/feedback.rs new file mode 100644 index 000000000..c4cb81802 --- /dev/null +++ b/examples/feedback.rs @@ -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::::new())); + let producer_left = buffer_left.clone(); + let consumer_left = buffer_left.clone(); + let buffer_right = Arc::new(Mutex::new(VecDeque::::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>; + 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 { + let property_address = AudioObjectPropertyAddress { + mSelector: kAudioHardwarePropertyDefaultInputDevice, + mScope: kAudioObjectPropertyScopeGlobal, + mElement: kAudioObjectPropertyElementMaster, + }; + + let audio_device_id: AudioDeviceID = 0; + let data_size = mem::size_of::(); + 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 { + let property_address = AudioObjectPropertyAddress { + mSelector: kAudioHardwarePropertyDefaultOutputDevice, + mScope: kAudioObjectPropertyScopeGlobal, + mElement: kAudioObjectPropertyElementMaster, + }; + + let audio_device_id: AudioDeviceID = 0; + let data_size = mem::size_of::(); + 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 { + 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) +} diff --git a/src/audio_unit/mod.rs b/src/audio_unit/mod.rs index 9e4459bbe..9fe3fae7d 100644 --- a/src/audio_unit/mod.rs +++ b/src/audio_unit/mod.rs @@ -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::<()>`. @@ -375,3 +398,28 @@ pub fn get_property( 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( + id: u32, +) -> Result +{ + let mut size = ::std::mem::size_of::() 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) + } +} diff --git a/src/audio_unit/render_callback.rs b/src/audio_unit/render_callback.rs index e0c5668a7..141f86aa9 100644 --- a/src/audio_unit/render_callback.rs +++ b/src/audio_unit/render_callback.rs @@ -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. @@ -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. @@ -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. @@ -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 = vec![]; let sample_bytes = stream_format.sample_format.size_in_bytes(); let n_channels = stream_format.channels_per_frame; @@ -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, };