From 04f4222467315d6b8868f3dcdf190bca8a40aef6 Mon Sep 17 00:00:00 2001 From: Chun-Min Chang Date: Tue, 19 Feb 2019 15:54:22 -0800 Subject: [PATCH] Implement audiounit_set_channel_layout --- README.md | 8 ++- src/backend/auto_release.rs | 4 ++ src/backend/mod.rs | 70 +++++++++++++++++++++++ src/backend/test.rs | 108 +++++++++++++++++++++++++++++++++++- 4 files changed, 185 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index cc3ebc5..187baed 100644 --- a/README.md +++ b/README.md @@ -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] diff --git a/src/backend/auto_release.rs b/src/backend/auto_release.rs index 6de3443..4de3dbd 100644 --- a/src/backend/auto_release.rs +++ b/src/backend/auto_release.rs @@ -25,6 +25,10 @@ impl AutoRelease { unsafe { &*self.ptr } } + pub fn as_mut(&self) -> &mut T { + unsafe { &mut *self.ptr } + } + pub fn as_mut_ptr(&self) -> *mut T { self.ptr } diff --git a/src/backend/mod.rs b/src/backend/mod.rs index a8ac62e..316481b 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -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::() + (nb_channels as usize - 1) * mem::size_of::(); + 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 { // FIXIT: Add a check ? We will fail to get data size if `device_id` diff --git a/src/backend/test.rs b/src/backend/test.rs index 631fc60..1a31d82 100644 --- a/src/backend/test.rs +++ b/src/backend/test.rs @@ -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> = [ + ("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