Implement audiounit_set_channel_layout
This commit is contained in:
Родитель
bfa60d7e66
Коммит
04f4222467
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче