Skip to content

Commit

Permalink
With vpio, query a dedicated audio unit for channel layout
Browse files Browse the repository at this point in the history
The VoiceProcessingIO audio unit cannot return the channel layout
for its output device. With this patch, in case we are using vpio,
we try to use a dedicated AudioUnit for the output device and query
that, like we would have queried the regular output unit.
  • Loading branch information
Pehrsons committed Oct 24, 2023
1 parent 04a5bdb commit 5e50138
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 27 deletions.
82 changes: 58 additions & 24 deletions src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -829,14 +829,14 @@ fn get_default_device_id(devtype: DeviceType) -> std::result::Result<AudioObject
}
}

fn audiounit_convert_channel_layout(layout: &AudioChannelLayout) -> Vec<mixer::Channel> {
fn audiounit_convert_channel_layout(layout: &AudioChannelLayout) -> Result<Vec<mixer::Channel>> {
if layout.mChannelLayoutTag != kAudioChannelLayoutTag_UseChannelDescriptions {
// kAudioChannelLayoutTag_UseChannelBitmap
// kAudioChannelLayoutTag_Mono
// kAudioChannelLayoutTag_Stereo
// ....
cubeb_log!("Only handling UseChannelDescriptions for now.\n");
return Vec::new();
return Err(Error::error());
}

let channel_descriptions = unsafe {
Expand All @@ -852,10 +852,10 @@ fn audiounit_convert_channel_layout(layout: &AudioChannelLayout) -> Vec<mixer::C
channels.push(label.into());
}

channels
Ok(channels)
}

fn audiounit_get_preferred_channel_layout(output_unit: AudioUnit) -> Vec<mixer::Channel> {
fn audiounit_get_preferred_channel_layout(output_unit: AudioUnit) -> Result<Vec<mixer::Channel>> {
let mut rv = NO_ERR;
let mut size: usize = 0;
rv = audio_unit_get_property_info(
Expand All @@ -871,7 +871,7 @@ fn audiounit_get_preferred_channel_layout(output_unit: AudioUnit) -> Vec<mixer::
"AudioUnitGetPropertyInfo/kAudioDevicePropertyPreferredChannelLayout rv={}",
rv
);
return Vec::new();
return Err(Error::error());
}
debug_assert!(size > 0);

Expand All @@ -889,15 +889,15 @@ fn audiounit_get_preferred_channel_layout(output_unit: AudioUnit) -> Vec<mixer::
"AudioUnitGetProperty/kAudioDevicePropertyPreferredChannelLayout rv={}",
rv
);
return Vec::new();
return Err(Error::error());
}

audiounit_convert_channel_layout(layout.as_ref())
}

// This is for output AudioUnit only. Calling this by input-only AudioUnit is prone
// to crash intermittently.
fn audiounit_get_current_channel_layout(output_unit: AudioUnit) -> Vec<mixer::Channel> {
fn audiounit_get_current_channel_layout(output_unit: AudioUnit) -> Result<Vec<mixer::Channel>> {
let mut rv = NO_ERR;
let mut size: usize = 0;
rv = audio_unit_get_property_info(
Expand All @@ -913,8 +913,7 @@ fn audiounit_get_current_channel_layout(output_unit: AudioUnit) -> Vec<mixer::Ch
"AudioUnitGetPropertyInfo/kAudioUnitProperty_AudioChannelLayout rv={}",
rv
);
// This property isn't known before macOS 10.12, attempt another method.
return audiounit_get_preferred_channel_layout(output_unit);
return Err(Error::error());
}
debug_assert!(size > 0);

Expand All @@ -932,12 +931,25 @@ fn audiounit_get_current_channel_layout(output_unit: AudioUnit) -> Vec<mixer::Ch
"AudioUnitGetProperty/kAudioUnitProperty_AudioChannelLayout rv={}",
rv
);
return Vec::new();
return Err(Error::error());
}

audiounit_convert_channel_layout(layout.as_ref())
}

fn get_channel_layout(output_unit: AudioUnit) -> Result<Vec<mixer::Channel>> {
audiounit_get_current_channel_layout(output_unit)
.or_else(|_| {
// The kAudioUnitProperty_AudioChannelLayout property isn't known before
// macOS 10.12, attempt another method.
cubeb_log!(
"Cannot get current channel layout for audiounit @ {:p}. Trying preferred channel layout.",
output_unit
);
audiounit_get_preferred_channel_layout(output_unit)
})
}

fn start_audiounit(unit: AudioUnit) -> Result<()> {
let status = audio_output_unit_start(unit);
if status == NO_ERR {
Expand Down Expand Up @@ -2956,20 +2968,22 @@ impl<'ctx> CoreStreamData<'ctx> {
e
})?;

// XXX
let device_layout = if using_voice_processing_unit {
let mut channels = Vec::with_capacity(output_hw_desc.mChannelsPerFrame as usize);
match output_hw_desc.mChannelsPerFrame {
1 => channels.push(mixer::Channel::FrontCenter),
_ => {
channels.push(mixer::Channel::FrontLeft);
channels.push(mixer::Channel::FrontRight);
}
}
channels
} else {
audiounit_get_current_channel_layout(self.output_unit)
};
let device_layout = self
.get_output_channel_layout()
.map_err(|e| {
cubeb_log!(
"({:p}) Could not get any channel layout. Defaulting to no channels.",
self.stm_ptr
);
e
})
.unwrap_or_default();

cubeb_log!(
"({:p} Using output device channel layout {:?}",
self.stm_ptr,
device_layout
);

// The mixer will be set up when
// 1. using aggregate device whose input device has output channels
Expand Down Expand Up @@ -3445,6 +3459,26 @@ impl<'ctx> CoreStreamData<'ctx> {

Ok(())
}

fn get_output_channel_layout(&self) -> Result<Vec<mixer::Channel>> {
if self.input_unit != self.output_unit {
return get_channel_layout(self.output_unit);
}

// The VoiceProcessingIO unit (as tried on MacOS 14) is known to not support
// kAudioUnitProperty_AudioChannelLayout queries, and to lie about
// kAudioDevicePropertyPreferredChannelLayout. If we're using
// VoiceProcessingIO, try standing up a regular AudioUnit and query that.
cubeb_log!(
"({:p}) get_output_channel_layout with a VoiceProcessingIO output unit. Trying a dedicated unit.",
self.stm_ptr
);
let mut dedicated_unit = create_audiounit(&self.output_device)?;
let res = get_channel_layout(dedicated_unit);
dispose_audio_unit(dedicated_unit);
dedicated_unit = ptr::null_mut();
res
}
}

impl<'ctx> Drop for CoreStreamData<'ctx> {
Expand Down
10 changes: 7 additions & 3 deletions src/backend/tests/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -700,7 +700,7 @@ fn test_convert_channel_layout() {
}
let layout_ref = unsafe { &(*(&layout as *const TestLayout as *const AudioChannelLayout)) };
assert_eq!(
&audiounit_convert_channel_layout(layout_ref),
&audiounit_convert_channel_layout(layout_ref).unwrap(),
expected_layout
);
}
Expand All @@ -711,7 +711,9 @@ fn test_convert_channel_layout() {
#[test]
fn test_get_preferred_channel_layout_output() {
match test_get_default_audiounit(Scope::Output) {
Some(unit) => assert!(!audiounit_get_preferred_channel_layout(unit.get_inner()).is_empty()),
Some(unit) => assert!(!audiounit_get_preferred_channel_layout(unit.get_inner())
.unwrap()
.is_empty()),
None => println!("No output audiounit for test."),
}
}
Expand All @@ -721,7 +723,9 @@ fn test_get_preferred_channel_layout_output() {
#[test]
fn test_get_current_channel_layout_output() {
match test_get_default_audiounit(Scope::Output) {
Some(unit) => assert!(!audiounit_get_current_channel_layout(unit.get_inner()).is_empty()),
Some(unit) => assert!(!audiounit_get_current_channel_layout(unit.get_inner())
.unwrap()
.is_empty()),
None => println!("No output audiounit for test."),
}
}
Expand Down

0 comments on commit 5e50138

Please sign in to comment.