Skip to content

Commit

Permalink
Fix iOS compilation error and add macOS + iOS feedback example applic…
Browse files Browse the repository at this point in the history
…ations
  • Loading branch information
MichaelHills committed Oct 2, 2020
1 parent d9c1a6d commit 4812b0c
Show file tree
Hide file tree
Showing 19 changed files with 1,134 additions and 5 deletions.
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@ env:
- LD_LIBRARY_PATH: /usr/local/lib
global:
- secure: URNNlOqxnfwWDC/w4PDTCswqe9ccnkIqDwfQecppfEui4KhpYXfrG9gx3xupWzWMfX+NPEcXtiyWA4CuUYaHbWWINLeLQUk650AFEqaRSiTpeh45Y9nh3dHT4fFDz4OeNfayNBBKL0kx5YwrugoeQggIgnF2KEcIHMF0+BbTtAM=
install:
- rustup target add aarch64-apple-ios x86_64-apple-ios
before_script:
- rustc --version
- cargo --version
script:
- cargo build --verbose
- cargo test --verbose
- cargo doc --verbose
- cargo build --verbose --target aarch64-apple-ios
- cargo build --verbose --target x86_64-apple-ios
after_success: |
[ $TRAVIS_BRANCH = master ] &&
[ $TRAVIS_PULL_REQUEST = false ] &&
Expand Down
214 changes: 214 additions & 0 deletions examples/feedback.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
//! 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::*;

const SAMPLE_RATE: f64 = 44100.0;

type S = f32; const SAMPLE_FORMAT: SampleFormat = SampleFormat::F32;
// type S = i32; const SAMPLE_FORMAT: SampleFormat = SampleFormat::I32;
// type S = i16; const SAMPLE_FORMAT: SampleFormat = SampleFormat::I16;
// type S = i8; const SAMPLE_FORMAT: SampleFormat = SampleFormat::I8;

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

let format_flag = match SAMPLE_FORMAT {
SampleFormat::F32 => LinearPcmFlags::IS_FLOAT,
SampleFormat::I32 | SampleFormat::I16 | SampleFormat::I8 => LinearPcmFlags::IS_SIGNED_INTEGER,
};

// Using IS_NON_INTERLEAVED everywhere because data::Interleaved is commented out / not implemented
let in_stream_format = StreamFormat {
sample_rate: SAMPLE_RATE,
sample_format: SAMPLE_FORMAT,
flags: format_flag | LinearPcmFlags::IS_PACKED | LinearPcmFlags::IS_NON_INTERLEAVED,
// audio_unit.set_input_callback is hardcoded to 1 buffer, and when using non_interleaved
// we are forced to 1 channel
channels_per_frame: 1,
};

let out_stream_format = StreamFormat {
sample_rate: SAMPLE_RATE,
sample_format: SAMPLE_FORMAT,
flags: format_flag | LinearPcmFlags::IS_PACKED | LinearPcmFlags::IS_NON_INTERLEAVED,
// you can change this to 1
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)
}
12 changes: 12 additions & 0 deletions examples/ios/build_rust_deps.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/sh

set -e

PATH=$PATH:$HOME/.cargo/bin

# If you want your build to run faster, add a "--targets x86_64-apple-ios" for just using the ios simulator.
if [ -n ${IOS_TARGETS} ]; then
cargo lipo --targets ${IOS_TARGETS}
else
cargo lipo
fi
Loading

0 comments on commit 4812b0c

Please sign in to comment.