dummp implementation for enumerate_devices

This commit is contained in:
Chun-Min Chang 2018-10-05 14:56:19 -07:00
Родитель b567926418
Коммит 253939a7bb
5 изменённых файлов: 535 добавлений и 96 удалений

Просмотреть файл

@ -3,5 +3,13 @@ name = "cubeb-coreaudio"
version = "0.1.0"
authors = ["Chun-Min Chang <chun.m.chang@gmail.com>"]
[features]
default = ["audio_unit", "core_audio"]
core_audio = ["coreaudio-sys/core_audio"]
audio_unit = ["coreaudio-sys/audio_unit"]
[dependencies]
cubeb-backend = "0.5"
coreaudio-sys = { version = "0.2", default-features = false }
core-foundation-sys = { version = "0.6" }
lazy_static = "1.1.0"

Просмотреть файл

@ -1,96 +0,0 @@
// Copyright © 2018 Mozilla Foundation
//
// This program is made available under an ISC-style license. See the
// accompanying file LICENSE for details.
// cubeb_backend::{*} is is referred:
// - ffi : cubeb_sys::* (cubeb-core/lib.rs).
// - Context : pub struct Context (cubeb-core/context.rs).
// - ContextOps : pub trait ContextOps (cubeb-backend/trait.rs).
// - DeviceCollectionRef: pub struct DeviceCollectionRef (cubeb-core/device_collection.rs).
// - DeviceId : pub type DeviceId (cubeb-core/device.rs).
// - DeviceType : pub struct DeviceType (cubeb-core/device.rs).
// - Error : pub struct Error (cubeb-core/error.rs).
// - Result : pub type Result<T> (cubeb-core/error.rs).
// - Stream : pub struct Stream (cubeb-core/stream.rs)
// - StreamParams : pub struct StreamParams (cubeb-core/stream.rs)
// - StreamParamsRef : pub struct StreamParamsRef (cubeb-core/stream.rs)
use cubeb_backend::{ffi, Context, ContextOps, DeviceCollectionRef, DeviceId,
DeviceType, Error, Result, Stream, StreamParams,
StreamParamsRef};
use std::ffi::{CStr, CString};
use std::os::raw::c_void;
pub struct AudioUnitContext {
pub context_name: Option<CString>,
}
impl AudioUnitContext {
fn new(name: Option<&CStr>) -> Result<Box<Self>> {
let name = name.map(|s| s.to_owned());
let ctx = Box::new(AudioUnitContext {
context_name: name,
});
Ok(ctx)
}
}
impl ContextOps for AudioUnitContext {
fn init(context_name: Option<&CStr>) -> Result<Context> {
let ctx = AudioUnitContext::new(context_name)?;
Ok(unsafe { Context::from_ptr(Box::into_raw(ctx) as *mut _) }) // _ is ffi::cubeb.
}
fn backend_id(&mut self) -> &'static CStr {
unsafe { CStr::from_ptr(b"audiounit-rust\0".as_ptr() as *const _) } // _ is c_char.
}
fn max_channel_count(&mut self) -> Result<u32> {
Err(Error::not_supported())
}
fn min_latency(&mut self, params: StreamParams) -> Result<u32> {
Err(Error::not_supported())
}
fn preferred_sample_rate(&mut self) -> Result<u32> {
Err(Error::not_supported())
}
fn enumerate_devices(
&mut self,
devtype: DeviceType,
collection: &DeviceCollectionRef,
) -> Result<()> {
Err(Error::not_supported())
}
fn device_collection_destroy(&mut self, collection: &mut DeviceCollectionRef) -> Result<()> {
Err(Error::not_supported())
}
#[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))]
fn stream_init(
&mut self,
stream_name: Option<&CStr>,
input_device: DeviceId,
input_stream_params: Option<&StreamParamsRef>,
output_device: DeviceId,
output_stream_params: Option<&StreamParamsRef>,
latency_frames: u32,
data_callback: ffi::cubeb_data_callback,
state_callback: ffi::cubeb_state_callback,
user_ptr: *mut c_void,
) -> Result<Stream> {
Err(Error::not_supported())
}
fn register_device_collection_changed(
&mut self,
devtype: DeviceType,
cb: ffi::cubeb_device_collection_changed_callback,
user_ptr: *mut c_void,
) -> Result<()> {
Err(Error::not_supported())
}
}

367
src/backend/mod.rs Normal file
Просмотреть файл

@ -0,0 +1,367 @@
// Copyright © 2018 Mozilla Foundation
//
// This program is made available under an ISC-style license. See the
// accompanying file LICENSE for details.
extern crate coreaudio_sys;
use self::coreaudio_sys::*;
// cubeb_backend::{*} is is referred:
// - ffi : cubeb_sys::* (cubeb-core/lib.rs).
// - Context : pub struct Context (cubeb-core/context.rs).
// - ContextOps : pub trait ContextOps (cubeb-backend/trait.rs).
// - DeviceCollectionRef: pub struct DeviceCollectionRef (cubeb-core/device_collection.rs).
// - DeviceId : pub type DeviceId (cubeb-core/device.rs).
// - DeviceType : pub struct DeviceType (cubeb-core/device.rs).
// - Error : pub struct Error (cubeb-core/error.rs).
// - Result : pub type Result<T> (cubeb-core/error.rs).
// - Stream : pub struct Stream (cubeb-core/stream.rs)
// - StreamParams : pub struct StreamParams (cubeb-core/stream.rs)
// - StreamParamsRef : pub struct StreamParamsRef (cubeb-core/stream.rs)
use cubeb_backend::{ffi, Context, ContextOps, DeviceCollectionRef, DeviceId,
DeviceType, Error, Result, Stream, StreamParams,
StreamParamsRef};
use std::ffi::{CStr, CString};
use std::mem;
use std::os::raw::{c_char, c_void};
use std::ptr;
use std::slice;
mod utils;
use self::utils::*;
// Implementation
// ============================================================================
pub const DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS: AudioObjectPropertyAddress =
AudioObjectPropertyAddress {
mSelector: kAudioHardwarePropertyDefaultInputDevice,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
pub const DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS: AudioObjectPropertyAddress =
AudioObjectPropertyAddress {
mSelector: kAudioHardwarePropertyDefaultOutputDevice,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
const DEVICES_PROPERTY_ADDRESS: AudioObjectPropertyAddress =
AudioObjectPropertyAddress {
mSelector: kAudioHardwarePropertyDevices,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
fn audiounit_get_default_device_id(
dev_type: DeviceType
) -> AudioObjectID {
let mut adr;
if dev_type == DeviceType::OUTPUT {
adr = &DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS;
} else if dev_type == DeviceType::INPUT {
adr = &DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS;
} else {
return kAudioObjectUnknown;
}
let mut dev_id: AudioDeviceID = kAudioObjectUnknown;
let mut size = mem::size_of::<AudioDeviceID>();
if audio_object_get_property_data(
kAudioObjectSystemObject,
adr,
&mut size,
&mut dev_id
) != 0 {
return kAudioObjectUnknown;
}
return dev_id;
}
fn audiounit_get_channel_count(
dev_id: AudioObjectID,
scope: AudioObjectPropertyScope
) -> u32 {
let mut count: u32 = 0;
let mut size: usize = 0;
let adr = AudioObjectPropertyAddress {
mSelector: kAudioDevicePropertyStreamConfiguration,
mScope: scope,
mElement: kAudioObjectPropertyElementMaster
};
if audio_object_get_property_data_size(dev_id, &adr, &mut size) == 0 && size > 0 {
let mut data: Vec<u8> = allocate_array_by_size(size);
let ptr = data.as_mut_ptr() as *mut AudioBufferList;
if audio_object_get_property_data(dev_id, &adr, &mut size, ptr) == 0 {
let list = unsafe { *ptr };
let buffers = unsafe {
let ptr = list.mBuffers.as_ptr() as *mut AudioBuffer;
let len = list.mNumberBuffers as usize;
slice::from_raw_parts_mut(ptr, len)
};
for buffer in buffers {
count += buffer.mNumberChannels;
}
}
}
count
}
fn audiounit_get_devices_of_type(dev_type: DeviceType) -> Vec<AudioObjectID> {
let mut size: usize = 0;
let mut ret = audio_object_get_property_data_size(
kAudioObjectSystemObject,
&DEVICES_PROPERTY_ADDRESS,
&mut size
);
if ret != 0 {
return Vec::new();
}
/* Total number of input and output devices. */
let mut devices: Vec<AudioObjectID> = allocate_array_by_size(size);
ret = audio_object_get_property_data(
kAudioObjectSystemObject,
&DEVICES_PROPERTY_ADDRESS,
&mut size,
devices.as_mut_ptr(),
);
if ret != 0 {
return Vec::new();
}
/* Expected sorted but did not find anything in the docs. */
devices.sort();
if dev_type.contains(DeviceType::INPUT | DeviceType::OUTPUT) {
return devices;
}
// FIXIT: This is wrong. We will return the output devices when dev_type
// is unknown. Change it after C version is updated!
let scope = if dev_type == DeviceType::INPUT {
kAudioDevicePropertyScopeInput
} else {
kAudioDevicePropertyScopeOutput
};
let mut devices_in_scope = Vec::new();
for device in devices {
if audiounit_get_channel_count(device, scope) > 0 {
devices_in_scope.push(device);
}
}
return devices_in_scope;
}
fn audiounit_create_device_from_hwdev(
dev_info: &mut ffi::cubeb_device_info,
dev_id: AudioObjectID,
dev_type: DeviceType,
) -> Result<()> {
let mut adr = AudioObjectPropertyAddress {
mSelector: 0,
mScope: 0,
mElement: kAudioObjectPropertyElementMaster
};
let mut size: usize = 0;
if dev_type == DeviceType::OUTPUT {
adr.mScope = kAudioDevicePropertyScopeOutput;
} else if dev_type == DeviceType::INPUT {
adr.mScope = kAudioDevicePropertyScopeInput;
} else {
return Err(Error::error());
}
let ch = audiounit_get_channel_count(dev_id, adr.mScope);
if ch == 0 {
return Err(Error::error());
}
let device_id_c = CString::new("device id !").unwrap();
let devid_c = CString::new("devid !").unwrap();
let friendly_name_c = CString::new("friendly name !").unwrap();
let group_id_c = CString::new("group id !").unwrap();
let vendor_name_c = CString::new("group id !").unwrap();
dev_info.device_id = device_id_c.into_raw();
dev_info.devid = devid_c.into_raw() as ffi::cubeb_devid;
dev_info.friendly_name = friendly_name_c.into_raw();
dev_info.group_id = group_id_c.into_raw();
dev_info.vendor_name = vendor_name_c.into_raw();
dev_info.device_type = ffi::CUBEB_DEVICE_TYPE_UNKNOWN;
dev_info.state = ffi::CUBEB_DEVICE_STATE_UNPLUGGED;
dev_info.preferred = ffi::CUBEB_DEVICE_PREF_NONE;
dev_info.format = ffi::CUBEB_DEVICE_FMT_ALL;
dev_info.default_format = ffi::CUBEB_DEVICE_FMT_F32LE;
dev_info.max_channels = ch;
dev_info.min_rate = 1;
dev_info.max_rate = 2;
dev_info.default_rate = 0;
dev_info.latency_lo = 0;
dev_info.latency_hi = 0;
Ok(())
}
// Interface
// ============================================================================
pub struct AudioUnitContext {
pub context_name: Option<CString>,
}
impl AudioUnitContext {
fn new(name: Option<&CStr>) -> Result<Box<Self>> {
let name = name.map(|s| s.to_owned());
let ctx = Box::new(AudioUnitContext {
context_name: name,
});
Ok(ctx)
}
}
impl ContextOps for AudioUnitContext {
fn init(context_name: Option<&CStr>) -> Result<Context> {
let ctx = AudioUnitContext::new(context_name)?;
Ok(unsafe { Context::from_ptr(Box::into_raw(ctx) as *mut _) }) // _ is ffi::cubeb.
}
fn backend_id(&mut self) -> &'static CStr {
unsafe { CStr::from_ptr(b"audiounit-rust\0".as_ptr() as *const _) } // _ is c_char.
}
fn max_channel_count(&mut self) -> Result<u32> {
Err(Error::not_supported())
}
fn min_latency(&mut self, params: StreamParams) -> Result<u32> {
Err(Error::not_supported())
}
fn preferred_sample_rate(&mut self) -> Result<u32> {
Err(Error::not_supported())
}
fn enumerate_devices(
&mut self,
devtype: DeviceType,
collection: &DeviceCollectionRef,
) -> Result<()> {
let mut input_devs = Vec::<AudioObjectID>::new();
let mut output_devs = Vec::<AudioObjectID>::new();
// Count number of input and output devices. This is not
// necessarily the same as the count of raw devices supported by the
// system since, for example, with Soundflower installed, some
// devices may report as being both input *and* output and cubeb
// separates those into two different devices.
if devtype.contains(DeviceType::OUTPUT) {
output_devs = audiounit_get_devices_of_type(DeviceType::OUTPUT);
}
if devtype.contains(DeviceType::INPUT) {
input_devs = audiounit_get_devices_of_type(DeviceType::OUTPUT);
}
let mut devices: Vec<ffi::cubeb_device_info> = allocate_array(
output_devs.len() + input_devs.len()
);
let mut count = 0;
if devtype.contains(DeviceType::OUTPUT) {
for dev in output_devs {
audiounit_create_device_from_hwdev(&mut devices[count], dev, DeviceType::OUTPUT)?;
// is_aggregate_device ?
count += 1;
}
}
if devtype.contains(DeviceType::INPUT) {
for dev in input_devs {
audiounit_create_device_from_hwdev(&mut devices[count], dev, DeviceType::INPUT)?;
// is_aggregate_device ?
count += 1;
}
}
let coll = unsafe { &mut *collection.as_ptr() };
if (count > 0) {
let mut tmp = Vec::new();
mem::swap(&mut devices, &mut tmp);
let mut devs = tmp.into_boxed_slice();
coll.device = devs.as_mut_ptr();
coll.count = devs.len();
// Giving away the memory owned by devices. Don't free it!
mem::forget(devs);
} else {
coll.device = ptr::null_mut();
coll.count = 0;
}
Ok(())
}
fn device_collection_destroy(&mut self, collection: &mut DeviceCollectionRef) -> Result<()> {
debug_assert!(!collection.as_ptr().is_null()); // TODO: This fails if there is no device.
unsafe {
let coll = &mut *collection.as_ptr();
let mut devices = Vec::from_raw_parts(
coll.device as *mut ffi::cubeb_device_info,
coll.count,
coll.count,
);
for dev in &mut devices {
// These should be paired with what we create in audiounit_create_device_from_hwdev.
if !dev.device_id.is_null() {
let _ = CString::from_raw(dev.device_id as *mut _);
}
if !dev.devid.is_null() {
let _ = CString::from_raw(dev.devid as *mut _);
}
if !dev.friendly_name.is_null() {
let _ = CString::from_raw(dev.friendly_name as *mut _);
}
if !dev.group_id.is_null() {
let _ = CString::from_raw(dev.group_id as *mut _);
}
if !dev.vendor_name.is_null() {
let _ = CString::from_raw(dev.vendor_name as *mut _);
}
}
coll.device = ptr::null_mut();
coll.count = 0;
}
Ok(())
}
#[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))]
fn stream_init(
&mut self,
stream_name: Option<&CStr>,
input_device: DeviceId,
input_stream_params: Option<&StreamParamsRef>,
output_device: DeviceId,
output_stream_params: Option<&StreamParamsRef>,
latency_frames: u32,
data_callback: ffi::cubeb_data_callback,
state_callback: ffi::cubeb_state_callback,
user_ptr: *mut c_void,
) -> Result<Stream> {
Err(Error::not_supported())
}
fn register_device_collection_changed(
&mut self,
devtype: DeviceType,
cb: ffi::cubeb_device_collection_changed_callback,
user_ptr: *mut c_void,
) -> Result<()> {
Err(Error::not_supported())
}
}
#[cfg(test)]
mod test;

90
src/backend/test.rs Normal file
Просмотреть файл

@ -0,0 +1,90 @@
use super::*;
// Implementation
// ============================================================================
// get_default_device_id
// ------------------------------------
#[test]
fn test_get_default_device_id() {
// Invalid types:
assert_eq!(
audiounit_get_default_device_id(DeviceType::UNKNOWN),
kAudioObjectUnknown,
);
assert_eq!(
audiounit_get_default_device_id(DeviceType::INPUT | DeviceType::OUTPUT),
kAudioObjectUnknown,
);
// The following types work since DeviceType::UNKNOWN is 0.
// TODO: Is that a bug?
// assert_eq!(
// audiounit_get_default_device_id(DeviceType::UNKNOWN | DeviceType::INPUT),
// kAudioObjectUnknown,
// );
// assert_eq!(
// audiounit_get_default_device_id(DeviceType::UNKNOWN | DeviceType::OUTPUT),
// kAudioObjectUnknown,
// );
// Valid types:
// P.S. Works only when there is available default input and output.
assert_ne!(
audiounit_get_default_device_id(DeviceType::INPUT),
kAudioObjectUnknown,
);
assert_ne!(
audiounit_get_default_device_id(DeviceType::OUTPUT),
kAudioObjectUnknown,
)
}
// get_channel_count
// ------------------------------------
#[test]
fn test_get_channel_count() {
let input_id = audiounit_get_default_device_id(DeviceType::INPUT);
if valid_id(input_id) {
assert!(audiounit_get_channel_count(input_id, kAudioDevicePropertyScopeInput) > 0);
}
let output_id = audiounit_get_default_device_id(DeviceType::OUTPUT);
if valid_id(output_id) {
assert!(audiounit_get_channel_count(output_id, kAudioDevicePropertyScopeOutput) > 0);
}
}
// get_devices_of_type
// ------------------------------------
#[test]
fn test_get_devices_of_type() {
// FIXIT: Open this assertion after C version is updated.
// let no_devs = audiounit_get_devices_of_type(DeviceType::UNKNOWN);
// assert!(no_devs.is_empty());
let all_devs = audiounit_get_devices_of_type(DeviceType::INPUT | DeviceType::OUTPUT);
let in_devs = audiounit_get_devices_of_type(DeviceType::INPUT);
let out_devs = audiounit_get_devices_of_type(DeviceType::OUTPUT);
let input_id = audiounit_get_default_device_id(DeviceType::INPUT);
if valid_id(input_id) {
assert!(!all_devs.is_empty());
assert!(!in_devs.is_empty());
assert!(all_devs.contains(&input_id));
assert!(in_devs.contains(&input_id));
}
let output_id = audiounit_get_default_device_id(DeviceType::OUTPUT);
if valid_id(output_id) {
assert!(!all_devs.is_empty());
assert!(!out_devs.is_empty());
assert!(all_devs.contains(&input_id));
assert!(out_devs.contains(&output_id));
}
}
// Utils
// ------------------------------------
fn valid_id(id: AudioObjectID) -> bool {
id != kAudioObjectUnknown
}

70
src/backend/utils.rs Normal file
Просмотреть файл

@ -0,0 +1,70 @@
extern crate coreaudio_sys as sys;
use std::mem;
use std::os::raw::c_void;
use std::ptr;
pub fn allocate_array_by_size<T>(size: usize) -> Vec<T> {
let elements = size / mem::size_of::<T>();
allocate_array::<T>(elements)
}
pub fn allocate_array<T>(elements: usize) -> Vec<T> {
let mut array = Vec::<T>::with_capacity(elements);
unsafe {
array.set_len(elements);
}
array
}
pub fn audio_object_get_property_data<T>(
id: sys::AudioObjectID,
address: &sys::AudioObjectPropertyAddress,
size: *mut usize,
data: *mut T,
) -> sys::OSStatus {
unsafe {
sys::AudioObjectGetPropertyData(
id,
address, // as `*const AudioObjectPropertyAddress` automatically.
0,
ptr::null(),
size as *mut sys::UInt32, // Cast raw usize pointer to raw u32 pointer.
data as *mut c_void, // Cast raw T pointer to void pointer.
)
}
}
pub fn audio_object_get_property_data_size(
id: sys::AudioObjectID,
address: &sys::AudioObjectPropertyAddress,
size: *mut usize,
) -> sys::OSStatus {
unsafe {
sys::AudioObjectGetPropertyDataSize(
id,
address, // as `*const AudioObjectPropertyAddress` automatically.
0,
ptr::null(),
size as *mut sys::UInt32, // Cast raw usize pointer to raw u32 pointer.
)
}
}
pub fn audio_object_set_property_data<T>(
id: sys::AudioObjectID,
address: &sys::AudioObjectPropertyAddress,
size: usize,
data: *const T,
) -> sys::OSStatus {
unsafe {
sys::AudioObjectSetPropertyData(
id,
address, // as `*const AudioObjectPropertyAddress` automatically.
0,
ptr::null(),
size as sys::UInt32, // Cast usize variable to raw u32 variable.
data as *const c_void, // Cast raw T pointer to void pointer.
)
}
}