feat: introduce description() for devices#1046
Conversation
13aaf6b to
6c9c6d3
Compare
|
cc: @xephyris - currently the device selection was quite broken and this PR leverages your stable IDs to fix that. I'd like to fix this breakage and agree on how this should work before v0.17 - your perspectives are appreciated! |
|
This seems to be a great idea, however one suggestion I have is if we are doing a A struct for this with potentially more data about things such as device type (Input/Output), channel information, or some other important details as well as the human readable device name seems more fitting of a Another not too significant change is I think we might want to create a new What are your thoughts? |
|
Great idea. As well as address, type (headphones…) for those that support it. It could become a struct with a bunch of |
|
Working on it. Gonna be a big one - anything that stretches across all hosts is. pub struct DeviceDescription {
/// Human-readable device name (always available).
name: String,
/// Device manufacturer or vendor name (macOS, Windows).
manufacturer: Option<String>,
/// Driver name (ALSA, ASIO).
/// Example: ASIO4ALL v2
driver: Option<String>,
/// Categorization of device type (defaults to `Unknown` if not available).
/// Examples: BuiltIn, Usb, Bluetooth, WiredHeadset, ... , Unknown
device_type: DeviceType,
/// Connection/transport type (defaults to `Unknown` if not available).
/// Examples: BuiltIn, Usb, Bluetooth, Pci, FireWire, ... , Unknown
bus_type: BusType,
/// Direction: input, output, or duplex (defaults to `Unknown` if not known).
direction: DeviceDirection,
/// Physical address or connection identifier (Android).
/// Example: 00:1A:7D:DA:71:13
address: Option<String>,
/// Additional description lines with detailed information (ALSA, WASAPI).
///
/// On ALSA, this contains the multi-line description from hints.
/// On WASAPI, this may contain additional device description info.
extended: Vec<String>,
/// Name of the audio interface/adapter (WASAPI).
/// Example: Focusrite USB Audio
interface_name: Option<String>,
/// Container identifier for grouping devices from same hardware (WASAPI).
container_id: Option<String>,
} |
Deprecate DeviceTrait::name in favor of description() for human-readable labels and id() for unique identifiers. Implement description across host backends, update examples to print device IDs, and forward name() to description() where needed. Before this, the name() implementation across hosts was inconsistent. To ease upgrading users, this change keeps the name() behavior as it was in v0.16. Also removes #[inline] pragmas for code outside of the audio hot path.
* Update Display and FromStr implementations to match renamed variants (Wasapi, Asio, Alsa). This is consistent with the naming conventions used in other parts of the codebase. * Remove the IOS variant and change iOS device id to return CoreAudio. Rationale: iOS uses CoreAudio as its underlying audio API, so having a separate IOS variant is redundant and may cause confusion. iOS and macOS should never occur in the same build. * Improve DeviceId parse error message format.
* Replace device description string returns with a DeviceDescription type across hosts and traits. * Add enums for device type, interface, and direction, builder and helper conversion/parsing functions, and re-export the new module from the crate. * Provide a default name() shim that uses description().name() for compatibility.
5518b76 to
df3555a
Compare
|
@xephyris on Discord, @jwagner wrote:
Which I think is an interesting thought. Do you see any reason why we wouldn't flatten it like that? |
d71e32f to
7994df4
Compare
* Expose COMMON_SAMPLE_RATES as pub(crate) and use it in the ALSA backend instead of a private RATES array. * Remove the Windows-only cfg and add a few additional sample rates (12000, 24000, 352800).
5508d06 to
5404e9f
Compare
|
I think there are benefits to both approaches, and I would be willing to make a pull request to change the id system if the general consensus is to have a The main benefits I see from such a method would be greater simplicity for the The main benefit of the current method, would be human readability and also API specific implementations. While it is a bit clunky, I think it being more specific here can help to prevent errors that could arise from incorrect host matching. Also just a question, how would we be able to implement HostId matching if we were to do this implementation? From my limited testing it seems like if I do a |
|
Hey @xephyris, I totally agree that both approaches have their merits. I'm not quite sure what you mean with 'incorrect host matching' and 'HostId matching'? |
|
By host matching I mean when we are trying to determine what API is being used currently or what host type the id belongs to. For example, right now for the match platform {
"wasapi" => Ok(DeviceId::WASAPI(data.to_string())),
"asio" => Ok(DeviceId::ASIO(data.to_string())),
"coreaudio" => Ok(DeviceId::CoreAudio(data.to_string())),
"alsa" => Ok(DeviceId::ALSA(data.to_string())),
"aaudio" => {
let id = data.parse().map_err(|_| DeviceIdError::BackendSpecific {
err: BackendSpecificError {
description: format!("Failed to parse aaudio device id: {}", data),
},
})?;
Ok(DeviceId::AAudio(id))
}
"jack" => Ok(DeviceId::Jack(data.to_string())),
"webaudio" => Ok(DeviceId::WebAudio(data.to_string())),
"emscripten" => Ok(DeviceId::Emscripten(data.to_string())),
"ios" => Ok(DeviceId::IOS(data.to_string())),
"null" => Ok(DeviceId::Null),
&_ => todo!("implement DeviceId::FromStr for {platform}"),
}I'm just unsure about how would such an implementation work with HostId's, as there doesn't seem to be an effective way to match it, as only the device's supported platform APIs seems to show up. HostId also doesn't seem to currenly support What are your thoughts? If there are any misunderstandings that I have, I would be happy to learn more. |
|
I think from_string could then be (HostId::from_string, data). Assuming HostId has from_string, if not it can be added. But maybe I'm also missing distinction between what a Host(Id) and a platform is. I didn't consider the case where you would want to deserialize a DeviceId from an unsupported Host. If that is something we want to support, and we don't want unsupported Hosts to show up in the HostId enum then another enum is required. On a side note I think we might want to turn the into a This would avoid a panic when trying to read a value that came from a newer version, like when an older version of an application reads a state from a newer version. |
* Remove the Android-specific AudioDeviceDirection and use the core DeviceDirection throughout the AAudio Java interface. * Add android_device_flags to convert DeviceDirection to AudioManager flags, and direction_from_caps to derive DeviceDirection from isSource/isSink. * Refactor direction_from_counts to call direction_from_caps and update device requests and exports accordingly.
* Use Default for device description fields * Remove stale explanatory device option comments from examples/record_wav.rs
* Remove the unnecessary --features asio flag from the Android clippy run. * Re-export all definitions from java_interface. * Remove an unneeded .into() when setting device direction in DeviceDescriptionBuilder.
Change DeviceId from a per-host enum with 10+ variants to a simple tuple struct containing (HostId, String). This eliminates significant duplication and centralizes platform-specific logic. Benefits: - Single source of truth for platform-specific host matching - Adding new hosts only requires updating impl_platform_host! macro - Cross-platform device ID deserialization fails gracefully with Err(UnsupportedPlatform) instead of panicking This enables users to store device preferences that gracefully degrade when loaded on different platforms, while maintaining type safety for hosts available on the current platform.
|
Looks good to me @roderickvd. I think the trade-off that it is no longer possible to parse an id from an unsupported host is good, given the simplification it brings to the code, but I'm biased. :) |
|
This definitely looks cleaner and simpler, however I believe the issue I mentioned above isn't quite addressed. Is there any way to easily add in something that lists all of the supported hosts? This is mainly for extending outside of Right now I'm on macOS and if I try to pub fn from_cpal_id(device_id: DeviceId) -> Self {
match device_id.0 {
HostId::CoreAudio => todo!(),
}
}If I try to add another If there is an easy approach to resolve this, that doesn't require a bulky enum, that'd be great, but as of now, I still lean towards the old approach. |
|
You could now match on the string representation: match device_id.0.to_string().as_str() {
"coreaudio" => todo!(),
"jack" => todo!(),
// ...
} |
|
That could work nicely, although one thing to point out is it does leave more room for error, as the strings could get messed up when coding leading to undesired behavior. Maybe we could add a list of all the possible hosts in the documentation? That way it is easier to tell what APIs are supported, although it is less concrete than an enum. |
|
Can do. Risk is that the documentation will go stale when we add a host and forget to update the documentation, but that's just what it is. |
|
This PR has become quite extensive, so I'm gonna merge it to let it simmer for a while. That's a call out to everyone to test it out, thanks! |
Introduces
DeviceTrait::description()and deprecatesDeviceTrait::name()to clarify the distinction between human-readable descriptions and unique device identifiers.Motivation
The current
name()implementation was inconsistent across platforms - sometimes returning unique IDs, sometimes human-readable names, and sometimes a mix of both. In this state, examples don't work as expected when--device foois given on the command line, and neither will user applications that match on device name.For example: on ALSA,
name()calledself.to_string()which combined the PCM ID and description. For a device with PCM ID "hw:0,0" and description "HD Audio",name()returned "hw:0,0 (HD Audio)". When a user passed--device hw:0,0, that string comparison failed.In conclusion, this mixing of PCM ID and description broke device selection.
Solution
This split makes the API clearer: use
description()for displaying to users, andid()for uniquely identifying devices. With this change:id()returns "alsa:hw:0,0" in our ALSA example - just the identifier, no description mixed in.description()returns "HD Audio" - just the human-readable nameDevice selection does expect
alsa:hw:0,0instead of justhw:0,0. Through the power of #1014 that's precisely what we want, because if there are more hosts available on the platform, then we have devices unique to their named scopes.Backwards compatibility
To ease upgrading, v0.16 and earlier users relying on
name()can still use that and will get the same results as they did before. In our ALSA example: just "hw:0,0" without the "alsa:" prefix.