Skip to content

Commit

Permalink
Filter input streams by ID
Browse files Browse the repository at this point in the history
Empirical testing has shown that stream IDs lie between their owning
device's device ID and the next higher device ID. This commit uses this
knowledge to filter out VPIO input tap streams (really they're loopbacks
of some output stream) from devices because the VPIO tap streams have
IDs between the VPIO device's ID and the next higher device ID.

Regular aggregate devices are an exception as the tap streams appear to
take the place of the real input streams (by judging at their channel
count) and the real input streams get IDs that seem to belong to the
VPIO device. To solve this we enumerate the aggregate's sub devices and
return all their input streams, filtered per above. This should be
enough for every stream property except StartingChannel (it needs to be
in the context of the right device for obvious reasons) which we don't
use.
  • Loading branch information
Pehrsons committed May 30, 2024
1 parent 04c3c0b commit 652fc24
Showing 1 changed file with 58 additions and 4 deletions.
62 changes: 58 additions & 4 deletions src/backend/device_property.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,11 +212,65 @@ pub fn get_device_streams(

let mut streams: Vec<AudioObjectID> = allocate_array_by_size(size);
let err = audio_object_get_property_data(id, &address, &mut size, streams.as_mut_ptr());
if err == NO_ERR {
Ok(streams)
} else {
Err(err)
if err != NO_ERR {
return Err(err);
}

if devtype.contains(DeviceType::INPUT) {
// With VPIO, output devices will/may get a Tap that appears as an input stream on the
// output device id. It is unclear what kind of Tap this is as it cannot be enumerated
// as a Tap through the public APIs. There is no property on the stream itself that
// can consistently identify it as originating from another device's output either.
// TerminalType gets close but is often kAudioStreamTerminalTypeUnknown, and there are
// cases reported where real input streams have that TerminalType, too.
// See Firefox bug 1890186.
// We rely on AudioObjectID order instead. AudioDeviceID and AudioStreamID (and more)
// are all AudioObjectIDs underneath, and they're all distinct. The Tap streams
// mentioned above are created when VPIO is created, and their AudioObjectIDs are higher
// than the VPIO device's AudioObjectID, but lower than the next *real* device's
// AudioObjectID.
// Simplified, a device's native streams have AudioObjectIDs higher than their device's
// AudioObjectID but lower than the next device's AudioObjectID.
// We use this to filter streams, and hope that it holds across macOS versions.
// Note that for aggregate devices this does not hold, as their stream IDs seem to be
// repurposed by VPIO. We sum up the result of the above algorithm for each of their sub
// devices instead, as that seems to hold.
let mut devices = get_devices();
let sub_devices = AggregateDevice::get_sub_devices(id);
if let Ok(sub_device_ids) = sub_devices {
cubeb_log!(
"Getting input device streams for aggregate device {}. Summing over sub devices {:?}.",
id,
sub_device_ids
);
return Ok(sub_device_ids
.into_iter()
.filter_map(|sub_id| get_device_streams(sub_id, devtype).ok())
.flatten()
.collect());
}
debug_assert!(devices.contains(&id));
devices.sort();
let next_id = devices.into_iter().skip_while(|&i| i != id).skip(1).next();
cubeb_log!(
"Filtering input streams {:?} for device {}. Next device is {:?}.",
streams,
id,
next_id
);
if let Some(next_id) = next_id {
streams.retain(|&s| s > id && s < next_id);
} else {
streams.retain(|&s| s > id);
}
cubeb_log!(
"Input stream filtering for device {} retained {:?}.",
id,
streams
);
}

Ok(streams)
}

pub fn get_device_sample_rate(
Expand Down

0 comments on commit 652fc24

Please sign in to comment.