Don't account for channels on Tap input streams

With VPIO, output devices may get a Tap stream, basically adding an
input AudioStream to the output device. To avoid enumerating
output-only devices as input devices, we ignore these channels.
This commit is contained in:
Andreas Pehrson 2023-11-13 22:31:40 +01:00 коммит произвёл Paul Adenot
Родитель 8d83a4385f
Коммит dc4dac9ce6
4 изменённых файлов: 112 добавлений и 3 удалений

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

@ -10,5 +10,5 @@ core-foundation-sys = { version = "0.8" }
[dependencies.coreaudio-sys]
default-features = false
features = ["audio_unit", "core_audio"]
version = "0.2"
features = ["audio_unit", "core_audio", "io_kit_audio"]
version = "0.2.14"

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

@ -240,6 +240,23 @@ pub fn get_stream_latency(id: AudioStreamID) -> std::result::Result<u32, OSStatu
}
}
pub fn get_stream_terminal_type(id: AudioStreamID) -> std::result::Result<u32, OSStatus> {
assert_ne!(id, kAudioObjectUnknown);
let address = get_property_address(
Property::StreamTerminalType,
DeviceType::INPUT | DeviceType::OUTPUT,
);
let mut size = mem::size_of::<u32>();
let mut terminal_type: u32 = 0;
let err = audio_object_get_property_data(id, &address, &mut size, &mut terminal_type);
if err == NO_ERR {
Ok(terminal_type)
} else {
Err(err)
}
}
pub fn get_stream_virtual_format(
id: AudioStreamID,
) -> std::result::Result<AudioStreamBasicDescription, OSStatus> {
@ -293,6 +310,7 @@ pub enum Property {
HardwareDevices,
ModelUID,
StreamLatency,
StreamTerminalType,
StreamVirtualFormat,
TransportType,
ClockDomain,
@ -317,6 +335,7 @@ impl From<Property> for AudioObjectPropertySelector {
Property::HardwareDevices => kAudioHardwarePropertyDevices,
Property::ModelUID => kAudioDevicePropertyModelUID,
Property::StreamLatency => kAudioStreamPropertyLatency,
Property::StreamTerminalType => kAudioStreamPropertyTerminalType,
Property::StreamVirtualFormat => kAudioStreamPropertyVirtualFormat,
Property::TransportType => kAudioDevicePropertyTransportType,
Property::ClockDomain => kAudioDevicePropertyClockDomain,

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

@ -1353,7 +1353,43 @@ fn get_channel_count(
) -> std::result::Result<u32, OSStatus> {
assert_ne!(devid, kAudioObjectUnknown);
let streams = get_device_streams(devid, devtype)?;
let mut streams = get_device_streams(devid, devtype)?;
if devtype == DeviceType::INPUT {
// With VPIO, output devices will/may get a Tap that appears as input channels on the
// output device id. One could check for whether the output device has a tap enabled,
// but it is impossible to distinguish an output-only device from an input+output
// device. There have also been corner cases observed, where the device does NOT have
// a Tap enabled, but it still has the extra input channels from the Tap.
// We can check the terminal type of the input stream instead -- if it is
// INPUT_UNDEFINED (reported to be the VPIO type according to Chromium) or any
// non-input-only terminal type, we ignore it when counting channels (and thereby
// determining whether we are dealing with an input device).
streams.retain(|stream| {
let terminal_type = get_stream_terminal_type(*stream);
if terminal_type.is_err() {
return true;
}
#[allow(non_upper_case_globals)]
match terminal_type.unwrap() {
kAudioStreamTerminalTypeMicrophone
| kAudioStreamTerminalTypeHeadsetMicrophone
| kAudioStreamTerminalTypeReceiverMicrophone => true,
t if t > INPUT_UNDEFINED && t < OUTPUT_UNDEFINED => true,
t if t > BIDIRECTIONAL_UNDEFINED && t < TELEPHONY_UNDEFINED => true,
t if t > TELEPHONY_UNDEFINED && t < EXTERNAL_UNDEFINED => true,
t => {
cubeb_log!(
"Unexpected TerminalType {:06X} for input stream. Ignoring its channels.",
t
);
false
}
}
});
}
let mut count = 0;
for stream in streams {
if let Ok(format) = get_stream_virtual_format(stream) {

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

@ -417,3 +417,57 @@ fn test_get_stream_virtual_format() {
fn test_get_stream_virtual_format_by_unknown_stream() {
assert!(get_stream_virtual_format(kAudioObjectUnknown).is_err());
}
// get_stream_terminal_type
// ------------------------------------
#[test]
fn test_get_stream_terminal_type() {
fn terminal_type_to_device_type(terminal_type: u32) -> Option<DeviceType> {
#[allow(non_upper_case_globals)]
match terminal_type {
kAudioStreamTerminalTypeMicrophone
| kAudioStreamTerminalTypeHeadsetMicrophone
| kAudioStreamTerminalTypeReceiverMicrophone => Some(DeviceType::INPUT),
kAudioStreamTerminalTypeSpeaker
| kAudioStreamTerminalTypeHeadphones
| kAudioStreamTerminalTypeLFESpeaker
| kAudioStreamTerminalTypeReceiverSpeaker => Some(DeviceType::OUTPUT),
t if t > INPUT_UNDEFINED && t < OUTPUT_UNDEFINED => Some(DeviceType::INPUT),
t if t > OUTPUT_UNDEFINED && t < BIDIRECTIONAL_UNDEFINED => Some(DeviceType::OUTPUT),
t => {
println!("UNKNOWN TerminalType {:#06x}", t);
None
}
}
}
if let Some(device) = test_get_default_device(Scope::Input) {
let streams = get_device_streams(device, DeviceType::INPUT).unwrap();
for stream in streams {
assert_eq!(
terminal_type_to_device_type(get_stream_terminal_type(stream).unwrap()),
Some(DeviceType::INPUT)
);
}
} else {
println!("No input device.");
}
if let Some(device) = test_get_default_device(Scope::Output) {
let streams = get_device_streams(device, DeviceType::OUTPUT).unwrap();
for stream in streams {
assert_eq!(
terminal_type_to_device_type(get_stream_terminal_type(stream).unwrap()),
Some(DeviceType::OUTPUT)
);
}
} else {
println!("No output device.");
}
}
#[test]
#[should_panic]
fn test_get_stream_terminal_type_by_unknown_stream() {
assert!(get_stream_terminal_type(kAudioObjectUnknown).is_err());
}