Skip to content

Commit a843cfd

Browse files
authored
Merge pull request #72 from MichaelHills/mike-ios-support
Fix iOS compilation error and add macOS + iOS feedback example applications
2 parents 0cbf885 + 741099d commit a843cfd

File tree

19 files changed

+1137
-5
lines changed

19 files changed

+1137
-5
lines changed

.travis.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,17 @@ env:
1313
- LD_LIBRARY_PATH: /usr/local/lib
1414
global:
1515
- secure: URNNlOqxnfwWDC/w4PDTCswqe9ccnkIqDwfQecppfEui4KhpYXfrG9gx3xupWzWMfX+NPEcXtiyWA4CuUYaHbWWINLeLQUk650AFEqaRSiTpeh45Y9nh3dHT4fFDz4OeNfayNBBKL0kx5YwrugoeQggIgnF2KEcIHMF0+BbTtAM=
16+
install:
17+
- rustup target add aarch64-apple-ios x86_64-apple-ios
1618
before_script:
1719
- rustc --version
1820
- cargo --version
1921
script:
2022
- cargo build --verbose
2123
- cargo test --verbose
2224
- cargo doc --verbose
25+
- cargo build --verbose --target aarch64-apple-ios
26+
- cargo build --verbose --target x86_64-apple-ios
2327
after_success: |
2428
[ $TRAVIS_BRANCH = master ] &&
2529
[ $TRAVIS_PULL_REQUEST = false ] &&

examples/feedback.rs

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
//! A basic input + output stream example, copying the mic input stream to the default output stream
2+
3+
extern crate coreaudio;
4+
5+
use std::collections::VecDeque;
6+
use std::mem;
7+
use std::ptr::null;
8+
use std::sync::{Arc, Mutex};
9+
10+
use coreaudio::audio_unit::audio_format::LinearPcmFlags;
11+
use coreaudio::audio_unit::render_callback::{self, data};
12+
use coreaudio::audio_unit::{AudioUnit, Element, SampleFormat, Scope, StreamFormat};
13+
use coreaudio::sys::*;
14+
15+
const SAMPLE_RATE: f64 = 44100.0;
16+
17+
type S = f32; const SAMPLE_FORMAT: SampleFormat = SampleFormat::F32;
18+
// type S = i32; const SAMPLE_FORMAT: SampleFormat = SampleFormat::I32;
19+
// type S = i16; const SAMPLE_FORMAT: SampleFormat = SampleFormat::I16;
20+
// type S = i8; const SAMPLE_FORMAT: SampleFormat = SampleFormat::I8;
21+
22+
fn main() -> Result<(), coreaudio::Error> {
23+
let mut input_audio_unit = audio_unit_from_device(default_input_device().unwrap(), true)?;
24+
let mut output_audio_unit = audio_unit_from_device(default_output_device().unwrap(), false)?;
25+
26+
let format_flag = match SAMPLE_FORMAT {
27+
SampleFormat::F32 => LinearPcmFlags::IS_FLOAT,
28+
SampleFormat::I32 | SampleFormat::I16 | SampleFormat::I8 => LinearPcmFlags::IS_SIGNED_INTEGER,
29+
};
30+
31+
// Using IS_NON_INTERLEAVED everywhere because data::Interleaved is commented out / not implemented
32+
let in_stream_format = StreamFormat {
33+
sample_rate: SAMPLE_RATE,
34+
sample_format: SAMPLE_FORMAT,
35+
flags: format_flag | LinearPcmFlags::IS_PACKED | LinearPcmFlags::IS_NON_INTERLEAVED,
36+
// audio_unit.set_input_callback is hardcoded to 1 buffer, and when using non_interleaved
37+
// we are forced to 1 channel
38+
channels_per_frame: 1,
39+
};
40+
41+
let out_stream_format = StreamFormat {
42+
sample_rate: SAMPLE_RATE,
43+
sample_format: SAMPLE_FORMAT,
44+
flags: format_flag | LinearPcmFlags::IS_PACKED | LinearPcmFlags::IS_NON_INTERLEAVED,
45+
// you can change this to 1
46+
channels_per_frame: 2,
47+
};
48+
49+
println!("input={:#?}", &in_stream_format);
50+
println!("output={:#?}", &out_stream_format);
51+
println!("input_asbd={:#?}", &in_stream_format.to_asbd());
52+
println!("output_asbd={:#?}", &out_stream_format.to_asbd());
53+
54+
let id = kAudioUnitProperty_StreamFormat;
55+
let asbd = in_stream_format.to_asbd();
56+
input_audio_unit.set_property(id, Scope::Output, Element::Input, Some(&asbd))?;
57+
58+
let asbd = out_stream_format.to_asbd();
59+
output_audio_unit.set_property(id, Scope::Input, Element::Output, Some(&asbd))?;
60+
61+
let buffer_left = Arc::new(Mutex::new(VecDeque::<S>::new()));
62+
let producer_left = buffer_left.clone();
63+
let consumer_left = buffer_left.clone();
64+
let buffer_right = Arc::new(Mutex::new(VecDeque::<S>::new()));
65+
let producer_right = buffer_right.clone();
66+
let consumer_right = buffer_right.clone();
67+
68+
// seed roughly 1 second of data to create a delay in the feedback loop for easier testing
69+
for buffer in vec![buffer_left, buffer_right] {
70+
let mut buffer = buffer.lock().unwrap();
71+
for _ in 0..(out_stream_format.sample_rate as i32) {
72+
buffer.push_back(0 as S);
73+
}
74+
}
75+
76+
type Args = render_callback::Args<data::NonInterleaved<S>>;
77+
78+
input_audio_unit.set_input_callback(move |args| {
79+
let Args {
80+
num_frames,
81+
mut data,
82+
..
83+
} = args;
84+
let buffer_left = producer_left.lock().unwrap();
85+
let buffer_right = producer_right.lock().unwrap();
86+
let mut buffers = vec![buffer_left, buffer_right];
87+
for i in 0..num_frames {
88+
for (ch, channel) in data.channels_mut().enumerate() {
89+
let value: S = channel[i];
90+
buffers[ch].push_back(value);
91+
}
92+
}
93+
Ok(())
94+
})?;
95+
input_audio_unit.start()?;
96+
97+
output_audio_unit.set_render_callback(move |args: Args| {
98+
let Args {
99+
num_frames,
100+
mut data,
101+
..
102+
} = args;
103+
104+
let buffer_left = consumer_left.lock().unwrap();
105+
let buffer_right = consumer_right.lock().unwrap();
106+
let mut buffers = vec![buffer_left, buffer_right];
107+
for i in 0..num_frames {
108+
// Default other channels to copy value from first channel as a fallback
109+
let zero: S = 0 as S;
110+
let f: S = *buffers[0].front().unwrap_or(&zero);
111+
for (ch, channel) in data.channels_mut().enumerate() {
112+
let sample: S = buffers[ch].pop_front().unwrap_or(f);
113+
channel[i] = sample;
114+
}
115+
}
116+
Ok(())
117+
})?;
118+
output_audio_unit.start()?;
119+
120+
std::thread::sleep(std::time::Duration::from_millis(100000));
121+
122+
Ok(())
123+
}
124+
125+
/// Copied from cpal
126+
pub fn default_input_device() -> Option<AudioDeviceID> {
127+
let property_address = AudioObjectPropertyAddress {
128+
mSelector: kAudioHardwarePropertyDefaultInputDevice,
129+
mScope: kAudioObjectPropertyScopeGlobal,
130+
mElement: kAudioObjectPropertyElementMaster,
131+
};
132+
133+
let audio_device_id: AudioDeviceID = 0;
134+
let data_size = mem::size_of::<AudioDeviceID>();
135+
let status = unsafe {
136+
AudioObjectGetPropertyData(
137+
kAudioObjectSystemObject,
138+
&property_address as *const _,
139+
0,
140+
null(),
141+
&data_size as *const _ as *mut _,
142+
&audio_device_id as *const _ as *mut _,
143+
)
144+
};
145+
if status != kAudioHardwareNoError as i32 {
146+
return None;
147+
}
148+
149+
Some(audio_device_id)
150+
}
151+
152+
/// Copied from cpal
153+
pub fn default_output_device() -> Option<AudioDeviceID> {
154+
let property_address = AudioObjectPropertyAddress {
155+
mSelector: kAudioHardwarePropertyDefaultOutputDevice,
156+
mScope: kAudioObjectPropertyScopeGlobal,
157+
mElement: kAudioObjectPropertyElementMaster,
158+
};
159+
160+
let audio_device_id: AudioDeviceID = 0;
161+
let data_size = mem::size_of::<AudioDeviceID>();
162+
let status = unsafe {
163+
AudioObjectGetPropertyData(
164+
kAudioObjectSystemObject,
165+
&property_address as *const _,
166+
0,
167+
null(),
168+
&data_size as *const _ as *mut _,
169+
&audio_device_id as *const _ as *mut _,
170+
)
171+
};
172+
if status != kAudioHardwareNoError as i32 {
173+
return None;
174+
}
175+
176+
Some(audio_device_id)
177+
}
178+
179+
/// Copied from cpal
180+
fn audio_unit_from_device(
181+
device_id: AudioDeviceID,
182+
input: bool,
183+
) -> Result<AudioUnit, coreaudio::Error> {
184+
let mut audio_unit = AudioUnit::new(coreaudio::audio_unit::IOType::HalOutput)?;
185+
186+
if input {
187+
// Enable input processing.
188+
let enable_input = 1u32;
189+
audio_unit.set_property(
190+
kAudioOutputUnitProperty_EnableIO,
191+
Scope::Input,
192+
Element::Input,
193+
Some(&enable_input),
194+
)?;
195+
196+
// Disable output processing.
197+
let disable_output = 0u32;
198+
audio_unit.set_property(
199+
kAudioOutputUnitProperty_EnableIO,
200+
Scope::Output,
201+
Element::Output,
202+
Some(&disable_output),
203+
)?;
204+
}
205+
206+
audio_unit.set_property(
207+
kAudioOutputUnitProperty_CurrentDevice,
208+
Scope::Global,
209+
Element::Output,
210+
Some(&device_id),
211+
)?;
212+
213+
Ok(audio_unit)
214+
}

examples/ios/Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "coreaudio-ios-example"
3+
version = "0.1.0"
4+
authors = ["Michael Hills <mhills@gmail.com>"]
5+
edition = "2018"
6+
7+
[lib]
8+
name = "coreaudio_ios_example"
9+
crate-type = ["staticlib"]
10+
11+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
12+
13+
[dependencies]
14+
coreaudio-rs = { path = "../.." }
15+

examples/ios/build_rust_deps.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/bin/sh
2+
3+
set -e
4+
5+
PATH=$PATH:$HOME/.cargo/bin
6+
7+
# If you want your build to run faster, add a "--targets x86_64-apple-ios" for just using the ios simulator.
8+
if [ -n ${IOS_TARGETS} ]; then
9+
cargo lipo --targets ${IOS_TARGETS}
10+
else
11+
cargo lipo
12+
fi

0 commit comments

Comments
 (0)