Skip to content

Instantly share code, notes, and snippets.

@ChunMinChang
Last active October 29, 2018 01:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ChunMinChang/f0f4a71f78d1e1c6390493ab1c9d10d3 to your computer and use it in GitHub Desktop.
Save ChunMinChang/f0f4a71f78d1e1c6390493ab1c9d10d3 to your computer and use it in GitHub Desktop.
Rust wrappers for AudioObjectAddPropertyListener on OSX
all:
rustc property_listener_native_apis.rs
rustc property_listener_generic_type.rs
rustc property_listener_within_context.rs
clean:
rm property_listener_native_apis
rm property_listener_generic_type
rm property_listener_within_context
// TODO:
// 1. Merge this with https://gist.github.com/ChunMinChang/07b806cb6a9ea1136cb3cbd8cda6c806
// 2. If we have a way to set property data, then we don't need to plug or
// unplug audio devices manually. We can have a process like:
// a) add property listener
// b) set(change) the property
// c) check if property listener is fired
mod sys;
use std::os::raw::c_void;
use std::slice;
use std::thread;
const DEVICES_PROPERTY_ADDRESS: sys::AudioObjectPropertyAddress =
sys::AudioObjectPropertyAddress {
mSelector: sys::kAudioHardwarePropertyDevices,
mScope: sys::kAudioObjectPropertyScopeGlobal,
mElement: sys::kAudioObjectPropertyElementMaster,
};
fn test_property_listener() {
let mut context: f64 = 123.456;
fn listener(
id: sys::AudioObjectID,
number_of_addresses: u32,
addresses: *const sys::AudioObjectPropertyAddress,
data: *mut f64
) -> sys::OSStatus {
let addrs = unsafe {
slice::from_raw_parts(addresses, number_of_addresses as usize)
};
// TODO: Find a way to test the case for number_of_addresses > 1.
for (i, addr) in addrs.iter().enumerate() {
println!("device {} > address {}: selector {}, scope {}, element {}",
id, i, addr.mSelector, addr.mScope, addr.mElement);
}
let ctx = unsafe {
&mut (*data)
};
println!("context is {}", ctx);
assert_eq!(ctx, &123.456_f64);
0 // noErr.
}
let _ = add_property_listener(
sys::kAudioObjectSystemObject,
&DEVICES_PROPERTY_ADDRESS,
listener,
&mut context,
);
println!("Plug or unplug audio devices to see if the listener is fired...");
loop {};
// Since this function never ends, we can make sure `context` exists
// when listener is called!
}
type PropertyListener<T> = fn(
sys::AudioObjectID,
u32,
*const sys::AudioObjectPropertyAddress,
*mut T
) -> sys::OSStatus;
struct DataWithListener<T> {
listener: PropertyListener<T>,
data: *mut T,
}
fn add_property_listener<T>(
id: sys::AudioObjectID,
address: &sys::AudioObjectPropertyAddress,
listener: PropertyListener<T>,
data: *mut T,
) -> sys::OSStatus {
let data_with_listener = Box::new(DataWithListener {
listener,
data
});
audio_object_add_property_listener(
id,
address,
Some(property_changed_callback::<T>),
Box::into_raw(data_with_listener) as *mut c_void
)
}
extern "C" fn property_changed_callback<T>(
id: sys::AudioObjectID,
number_of_addresses: u32,
addresses: *const sys::AudioObjectPropertyAddress,
data: *mut c_void
) -> sys::OSStatus {
let data_with_listener = unsafe {
Box::from_raw(data as *mut DataWithListener<T>)
};
(data_with_listener.listener)(
id,
number_of_addresses,
addresses,
data_with_listener.data,
)
}
fn audio_object_add_property_listener(
id: sys::AudioObjectID,
address: &sys::AudioObjectPropertyAddress,
listener: sys::AudioObjectPropertyListenerProc,
data: *mut c_void,
) -> sys::OSStatus {
unsafe {
sys::AudioObjectAddPropertyListener(
id,
address,
listener,
data
)
}
}
fn main() {
// Make a vector to hold the children which are spawned.
let mut children = vec![];
children.push(thread::spawn(|| {
test_property_listener();
}));
// You can add another listeners or do other things here ...
for child in children {
// Wait for the threads to finish.
let _ = child.join();
}
}
// TODO:
// 1. Merge this with https://gist.github.com/ChunMinChang/07b806cb6a9ea1136cb3cbd8cda6c806
// 2. If we have a way to set property data, then we don't need to plug or
// unplug audio devices manually. We can have a process like:
// a) add property listener
// b) set(change) the property
// c) check if property listener is fired
mod sys;
use std::os::raw::c_void;
use std::slice;
use std::thread;
const DEVICES_PROPERTY_ADDRESS: sys::AudioObjectPropertyAddress =
sys::AudioObjectPropertyAddress {
mSelector: sys::kAudioHardwarePropertyDevices,
mScope: sys::kAudioObjectPropertyScopeGlobal,
mElement: sys::kAudioObjectPropertyElementMaster,
};
// Call AudioObjectAddPropertyListener by using native APIs
// ============================================================================
fn test_property_listener() {
let mut context: i32 = 100;
// TODO: is `listener` a static function?
extern fn listener(
id: sys::AudioObjectID,
number_of_addresses: u32,
addresses: *const sys::AudioObjectPropertyAddress,
data: *mut c_void
) -> sys::OSStatus {
let addrs = unsafe {
slice::from_raw_parts(addresses, number_of_addresses as usize)
};
// TODO: Find a way to test the case for number_of_addresses > 1.
for (i, addr) in addrs.iter().enumerate() {
println!("device {} > address {}: selector {}, scope {}, element {}",
id, i, addr.mSelector, addr.mScope, addr.mElement);
}
let ctx = unsafe {
&mut (*(data as *mut i32))
};
println!("context is {}", ctx);
assert_eq!(ctx, &100);
0 // noErr.
}
let _ = audio_object_add_property_listener(
sys::kAudioObjectSystemObject,
&DEVICES_PROPERTY_ADDRESS,
Some(listener),
&mut context as *mut i32 as *mut c_void,
);
println!("Plug or unplug audio devices to see if the listener is fired...");
loop {};
// Since this function never ends, we can make sure `context` exists
// when listener is called!
}
fn audio_object_add_property_listener(
id: sys::AudioObjectID,
address: &sys::AudioObjectPropertyAddress,
listener: sys::AudioObjectPropertyListenerProc,
data: *mut c_void,
) -> sys::OSStatus {
unsafe {
sys::AudioObjectAddPropertyListener(
id,
address,
listener,
data
)
}
}
fn main() {
// Make a vector to hold the children which are spawned.
let mut children = vec![];
children.push(thread::spawn(|| {
test_property_listener();
}));
// You can add another listeners or do other things here ...
for child in children {
// Wait for the threads to finish.
let _ = child.join();
}
}
// TODO:
// 1. Merge this with https://gist.github.com/ChunMinChang/07b806cb6a9ea1136cb3cbd8cda6c806
// 2. If we have a way to set property data, then we don't need to plug or
// unplug audio devices manually. We can have a process like:
// a) add property listener
// b) set(change) the property
// c) check if property listener is fired
mod sys;
use std::os::raw::c_void;
use std::slice;
use std::thread;
const DEVICES_PROPERTY_ADDRESS: sys::AudioObjectPropertyAddress =
sys::AudioObjectPropertyAddress {
mSelector: sys::kAudioHardwarePropertyDevices,
mScope: sys::kAudioObjectPropertyScopeGlobal,
mElement: sys::kAudioObjectPropertyElementMaster,
};
fn test_property_listener() {
let mut listener = PropertyListener::new();
let _ = listener.listen_property_changed();
println!("Plug or unplug audio devices to see if the listener is fired...");
loop {};
// Since this function never ends, we can make sure `context` exists
// when on_property_changed is called!
}
struct PropertyListener {}
impl PropertyListener {
fn new() -> Self {
PropertyListener {}
}
fn listen_property_changed(&mut self) -> sys::OSStatus {
audio_object_add_property_listener(
sys::kAudioObjectSystemObject,
&DEVICES_PROPERTY_ADDRESS,
Some(Self::property_changed_callback),
self as *mut Self as *mut c_void
)
}
fn on_property_changed<'a>(
&self,
id: sys::AudioObjectID,
addresses: &'a [sys::AudioObjectPropertyAddress],
) -> sys::OSStatus {
// TODO: Find a way to test the case for number_of_addresses > 1.
for (i, addr) in addresses.iter().enumerate() {
println!("device {} > address {}: selector {}, scope {}, element {}",
id, i, addr.mSelector, addr.mScope, addr.mElement);
}
0 // noErr.
}
// The *static* callback function that will be registered into
// the `AudioObjectAddPropertyListener`.
extern "C" fn property_changed_callback(
id: sys::AudioObjectID,
number_of_addresses: u32,
addresses: *const sys::AudioObjectPropertyAddress,
data: *mut c_void
) -> sys::OSStatus {
let addrs = unsafe {
slice::from_raw_parts(addresses, number_of_addresses as usize)
};
let property_listener = data as *mut Self;
unsafe {
(*property_listener).on_property_changed(
id,
addrs,
)
}
}
}
fn audio_object_add_property_listener(
id: sys::AudioObjectID,
address: &sys::AudioObjectPropertyAddress,
listener: sys::AudioObjectPropertyListenerProc,
data: *mut c_void,
) -> sys::OSStatus {
unsafe {
sys::AudioObjectAddPropertyListener(
id,
address,
listener,
data
)
}
}
fn main() {
// Make a vector to hold the children which are spawned.
let mut children = vec![];
children.push(thread::spawn(|| {
test_property_listener();
}));
// You can add another listeners or do other things here ...
for child in children {
// Wait for the threads to finish.
let _ = child.join();
}
}
#![allow(non_snake_case, non_upper_case_globals)]
use std::os::raw::c_void;
pub type OSStatus = i32;
pub type AudioObjectID = u32;
// pub const kAudioObjectUnknown: AudioObjectID = 0;
pub const kAudioObjectSystemObject: AudioObjectID = 1;
pub type AudioObjectPropertySelector = u32;
// https://developer.apple.com/documentation/coreaudio/1545886-anonymous/kaudiohardwarepropertydevices?language=objc
// https://github.com/phracker/MacOSX-SDKs/blob/9fc3ed0ad0345950ac25c28695b0427846eea966/MacOSX10.13.sdk/System/Library/Frameworks/CoreAudio.framework/Versions/A/Headers/AudioHardware.h#L585
// 0x'dev#' = 0x64657623 = 1684370979
pub const kAudioHardwarePropertyDevices: AudioObjectPropertySelector = 1684370979;
pub type AudioObjectPropertyScope = u32;
// https://developer.apple.com/documentation/coreaudio/1494464-anonymous/kaudioobjectpropertyscopeglobal
// https://github.com/phracker/MacOSX-SDKs/blob/9fc3ed0ad0345950ac25c28695b0427846eea966/MacOSX10.13.sdk/System/Library/Frameworks/CoreAudio.framework/Versions/A/Headers/AudioHardwareBase.h#L198
// 0x'glob' = 0x676C6F62 = 1735159650
pub const kAudioObjectPropertyScopeGlobal: AudioObjectPropertyScope = 1735159650;
pub type AudioObjectPropertyElement = u32;
// https://developer.apple.com/documentation/coreaudio/1494464-anonymous/kaudioobjectpropertyelementmaster
// https://github.com/phracker/MacOSX-SDKs/blob/9fc3ed0ad0345950ac25c28695b0427846eea966/MacOSX10.13.sdk/System/Library/Frameworks/CoreAudio.framework/Versions/A/Headers/AudioHardwareBase.h#L202
pub const kAudioObjectPropertyElementMaster: AudioObjectPropertyElement = 0;
#[repr(C)]
pub struct AudioObjectPropertyAddress {
pub mSelector: AudioObjectPropertySelector,
pub mScope: AudioObjectPropertyScope,
pub mElement: AudioObjectPropertyElement,
}
// https://developer.apple.com/documentation/coreaudio/audioobjectpropertylistenerproc?language=objc
// https://github.com/phracker/MacOSX-SDKs/blob/9fc3ed0ad0345950ac25c28695b0427846eea966/MacOSX10.13.sdk/System/Library/Frameworks/CoreAudio.framework/Versions/A/Headers/AudioHardware.h#L117-L143
pub type AudioObjectPropertyListenerProc = Option<unsafe extern "C" fn(
AudioObjectID,
u32,
*const AudioObjectPropertyAddress,
*mut c_void
) -> OSStatus>;
#[link(name = "CoreAudio", kind = "framework")] // Link to a dynamic library in CoreAudio framework.
extern "C" {
// https://developer.apple.com/documentation/coreaudio/1422472-audioobjectaddpropertylistener?language=objc
// https://github.com/phracker/MacOSX-SDKs/blob/9fc3ed0ad0345950ac25c28695b0427846eea966/MacOSX10.13.sdk/System/Library/Frameworks/CoreAudio.framework/Versions/A/Headers/AudioHardware.h#L331-L350
pub fn AudioObjectAddPropertyListener(
id: AudioObjectID,
address: *const AudioObjectPropertyAddress,
listener: AudioObjectPropertyListenerProc,
data: *mut c_void,
) -> OSStatus;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment