Implement audiounit_set_channel_layout

This commit is contained in:
Chun-Min Chang 2019-02-19 15:54:22 -08:00
Родитель bfa60d7e66
Коммит 04f4222467
4 изменённых файлов: 185 добавлений и 5 удалений

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

@ -73,9 +73,9 @@ By applying the [patch][integrate-with-cubeb] to integrate within [Cubeb][cubeb]
### Interanl APIs
- 🥚 : 6/75 (8%)
- 🥚 : 5/75 (6.6%)
- 🐣 : 7/75 (9.3%)
- 🐥 : 62/75 (82.6%)
- 🐥 : 63/75 (84%)
| Interanl AudioUnit APIs | status |
| ------------------------------------------- | ------ |
@ -114,7 +114,7 @@ By applying the [patch][integrate-with-cubeb] to integrate within [Cubeb][cubeb]
| audiounit_destroy | 🥚 |
| audio_stream_desc_init | 🐥 |
| audiounit_init_mixer | 🥚 |
| audiounit_set_channel_layout | 🥚 |
| audiounit_set_channel_layout | 🐥 |
| audiounit_layout_init | 🥚 |
| audiounit_get_sub_devices | 🐥 |
| audiounit_create_blank_aggregate_device | 🐥 |
@ -186,6 +186,8 @@ By applying the [patch][integrate-with-cubeb] to integrate within [Cubeb][cubeb]
- Check the input `StreamParams` parameters properly, or we will set a invalid format into `AudioUnit`.
- In fact, we should check **all** the parameters properly so we can make sure we don't mess up the streams/devices settings!
- Find a reliable way to verify `enumerate_devices`
- Make a list pairing (device-uid/device-name, available channel layouts) so we can check the layout-related APIs properly!
- A prototype is in [`test_set_channel_layout_output`](src/backend/test.rs).
- [cubeb-rs][cubeb-rs]
- Implement `to_owned` in [`StreamParamsRef`][cubeb-rs-stmparamsref]

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

@ -25,6 +25,10 @@ impl<T> AutoRelease<T> {
unsafe { &*self.ptr }
}
pub fn as_mut(&self) -> &mut T {
unsafe { &mut *self.ptr }
}
pub fn as_mut_ptr(&self) -> *mut T {
self.ptr
}

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

@ -1159,6 +1159,76 @@ fn audio_stream_desc_init(ss: &mut AudioStreamBasicDescription,
Ok(())
}
fn audiounit_set_channel_layout(unit: AudioUnit,
side: io_side,
layout: ChannelLayout) -> Result<()>
{
assert!(!unit.is_null());
if side != io_side::OUTPUT {
return Err(Error::error());
}
if layout == ChannelLayout::UNDEFINED {
// We leave everything as-is...
return Ok(());
}
let mut r = NO_ERR;
let nb_channels = unsafe { ffi::cubeb_channel_layout_nb_channels(layout.into()) };
// We do not use CoreAudio standard layout for lack of documentation on what
// the actual channel orders are. So we set a custom layout.
assert!(nb_channels >= 1);
let size = mem::size_of::<AudioChannelLayout>() + (nb_channels as usize - 1) * mem::size_of::<AudioChannelDescription>();
let mut au_layout = make_sized_audio_channel_layout(size);
au_layout.as_mut().mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
au_layout.as_mut().mNumberChannelDescriptions = nb_channels;
let channel_descriptions = unsafe {
slice::from_raw_parts_mut(
au_layout.as_mut().mChannelDescriptions.as_mut_ptr(),
nb_channels as usize
)
};
let mut channels: usize = 0;
let mut channelMap: ffi::cubeb_channel_layout = layout.into();
let i = 0;
while channelMap != 0 {
assert!(channels < nb_channels as usize);
let channel = (channelMap & 1) << i;
if channel != 0 {
channel_descriptions[channels].mChannelLabel =
cubeb_channel_to_channel_label(ChannelLayout::from(channel));
channel_descriptions[channels].mChannelFlags = kAudioChannelFlags_AllOff;
channels += 1;
}
channelMap = channelMap >> 1;
}
// TODO: This call doesn't work all the times, and r is NO_ERR doesn't
// guarantee the layout is set to the one we want. The layouts on some
// devices don't be changed even no errors are returned,
// e.g., r returns NO_ERR when we set stereo layout to a 4-channels aggregate
// device with QUAD layout (created by Audio MIDI Setup). However, the layout
// of this 4-channels aggregate device is still QUAD. Another weird thing is
// that we will get a kAudioUnitErr_InvalidPropertyValue error if we set the
// layout to QUAD. It's the same layout as its original one but it cannot be
// set!
r = audio_unit_set_property(unit,
kAudioUnitProperty_AudioChannelLayout,
kAudioUnitScope_Input,
AU_OUT_BUS,
au_layout.as_ref(),
size);
if r != NO_ERR {
cubeb_log!("AudioUnitSetProperty/{}/kAudioUnitProperty_AudioChannelLayout rv={}", to_string(&side), r);
return Err(Error::error());
}
Ok(())
}
fn audiounit_get_sub_devices(device_id: AudioDeviceID) -> Vec<AudioObjectID>
{
// FIXIT: Add a check ? We will fail to get data size if `device_id`

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

@ -2256,10 +2256,9 @@ fn test_get_preferred_channel_layout_output() {
// id: default_input_id,
// flags: device_flags::DEV_INPUT | device_flags::DEV_SYSTEM_DEFAULT
// };
// assert!(audiounit_create_unit(&mut unit, &device).is_ok());
// assert!(!unit.is_null());
//
// assert_eq!(audiounit_get_current_channel_layout(unit), ChannelLayout::UNDEFINED);
// }
@ -2324,6 +2323,111 @@ fn test_audio_stream_desc_init() {
}
}
// set_channel_layout
// ------------------------------------
#[test]
#[should_panic]
fn test_set_channel_layout_with_null_unit() {
assert!(audiounit_set_channel_layout(ptr::null_mut(), io_side::OUTPUT, ChannelLayout::UNDEFINED).is_err());
}
#[test]
fn test_set_channel_layout_undefind_layout() {
// Initialize the unit to default output device.
let default_output_id = audiounit_get_default_device_id(DeviceType::OUTPUT);
if !valid_id(default_output_id) {
return;
}
let mut unit: AudioUnit = ptr::null_mut();
let device = device_info {
id: default_output_id,
flags: device_flags::DEV_OUTPUT | device_flags::DEV_SYSTEM_DEFAULT
};
assert!(audiounit_create_unit(&mut unit, &device).is_ok());
assert!(!unit.is_null());
// Get original layout.
let original_layout = audiounit_get_current_channel_layout(unit);
// Leave layout as it is.
assert!(audiounit_set_channel_layout(unit, io_side::OUTPUT, ChannelLayout::UNDEFINED).is_ok());
// Check the layout is same as the original one.
assert_eq!(
audiounit_get_current_channel_layout(unit),
original_layout
);
}
#[test]
fn test_set_channel_layout_input() {
// Initialize the unit to the default input device.
let default_input_id = audiounit_get_default_device_id(DeviceType::INPUT);
if !valid_id(default_input_id) {
return;
}
let mut unit: AudioUnit = ptr::null_mut();
let device = device_info {
id: default_input_id,
flags: device_flags::DEV_INPUT | device_flags::DEV_SYSTEM_DEFAULT
};
assert!(audiounit_create_unit(&mut unit, &device).is_ok());
assert!(!unit.is_null());
assert_eq!(
audiounit_set_channel_layout(unit, io_side::INPUT, ChannelLayout::UNDEFINED).unwrap_err(),
Error::error()
);
}
#[test]
fn test_set_channel_layout_output() {
// TODO: Add more devices and its available layouts.
use std::collections::HashMap;
let devices_layouts: HashMap<&'static str, Vec<ChannelLayout>> = [
("hdpn", vec![ChannelLayout::STEREO]),
("ispk", vec![ChannelLayout::STEREO]),
].into_iter().cloned().collect();
// Initialize the unit to default output device.
let default_output_id = audiounit_get_default_device_id(DeviceType::OUTPUT);
if !valid_id(default_output_id) {
return;
}
let mut unit: AudioUnit = ptr::null_mut();
let device = device_info {
id: default_output_id,
flags: device_flags::DEV_OUTPUT | device_flags::DEV_SYSTEM_DEFAULT
};
assert!(audiounit_create_unit(&mut unit, &device).is_ok());
assert!(!unit.is_null());
let mut device = ffi::cubeb_device::default();
assert!(
audiounit_get_default_device_name(
unsafe { &*(ptr::null() as *const AudioUnitStream) },
&mut device,
DeviceType::OUTPUT
).is_ok()
);
let device_name = unsafe {
CStr::from_ptr(device.output_name)
.to_string_lossy()
.into_owned()
};
if let Some(layouts) = devices_layouts.get(device_name.as_str()) {
for layout in layouts.iter() {
assert!(audiounit_set_channel_layout(unit, io_side::OUTPUT, *layout).is_ok());
assert_eq!(
audiounit_get_current_channel_layout(unit),
*layout
);
}
}
}
// get_sub_devices
// ------------------------------------
// FIXIT: It doesn't make any sense to return the sub devices for an unknown