Skip to content

Conversation

xephyris
Copy link

@xephyris xephyris commented Sep 23, 2025

This PR adds a Device::id() command that returns the OS native device id on supported operating systems.
Currently, only macOS and Windows (WASAPI) are supported.

Function

By returning a Device::id(), it will be easier to ensure that the same device gets connected on application startup. This also allows for greater extension capability for other libraries to extend upon cpal function.

Architecture

The OS specific device ids are all stored in a DeviceId enum, that allows for support for the various data types that different operating systems and APIs use to store device ids (e.g. u32 for macOS and String for WASAPI.

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum DeviceId {
    CoreAudio(String),
    WASAPI(String),
    // ..
}

To handle any errors that might be outputted in when running Device::id(), there is also a new DeviceIdError enum

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum DeviceIdError {
    /// See the [`BackendSpecificError`] docs for more information about this error variant.
    BackendSpecific { err: BackendSpecificError },
    UnsupportedOS,
    ParseError,
}

Calling Device::id() will return a Result<DeviceId, DeviceIdError>. If the OS is supported and the device id is obtained, it will return a DeviceId. If the OS is unsupported, a DeviceIdError::UnsupportedOS error will be returned.

Current Status

Currently, support for Windows WASAPI String Ids has been added, along with support for macOS u32 Ids. I am working to add support for ALSA and maybe pulseaudio (once that gets merged).

For other APIs, I do not have the hardware to test or program for them, so they currently return a DeviceIdError::UnsupportedOS error.

Testing

Device::id() for macOS should return the standard u32 device uid that can be used anywhere.
For Windows, to convert the Rust String type to a PWSTR, the following code can be used

// device_id is the id returned by cpal
 let mut id = format!("{}\0", device_id).encode_utf16().collect::<Vec<u16>>(); 
 let pwstr = PWSTR(id.as_mut_ptr());

Copy link
Member

@roderickvd roderickvd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great! Your proposal literally crossed paths as I was working on something also. No matter, all the better I could provide some review comments with actual code suggestions.

Please also add a changelog, it's worth it 😄

@xephyris
Copy link
Author

Hello @roderickvd !

I have implemented most of the changes requested and renamed the enum variants to the suggested names.

For jack and aaudio, I just copied the code that you provided, and because I cannot test it, there may or may not be some errors.

I have also added alsa support which currently just returns the same content as name, because I was unable to find any other specific identifiers that could work better.

I will work on changing the macOS CoreAudio type to kAudioDevicePropertyDeviceUID in the coming days.

The changelog has also been updated 😄.

@roderickvd
Copy link
Member

Cool stuff let me know when you're ready for me to take another look.

@xephyris
Copy link
Author

I have finished the changes and now DeviceId::CoreAudio has a String type instead of u32.

One thing that I noticed was that there was already a uid() function in loopback.rs for Device, however I wasn't sure if it should be moved into device.rs or not as it seems to rely on some types inside of loopback.rs.

The code I'm currently using is basically 90% the same as the uid() function, just with a different return type. If needed, I can attempt moving the uid() function over into device.rs

@xephyris xephyris requested a review from roderickvd September 26, 2025 02:32
Copy link
Member

@roderickvd roderickvd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some ideas and one point about the macOS implementation.

@xephyris
Copy link
Author

I have updated the ASIO configuration and fixed the naming for macOS.

I have also made emscripten, webaudio, and iOS return Ok(DeviceId::"API_NAME"("default")) instead of an error.

Currently, I think it is fine as all three of those APIs seem to only return the default device, however I think if they were expanded to be able to select audio devices, it would be better to leave them returning DeviceIdError::UnsupportedPlatform than default, as if someone were to save the id while a different device is selected, it would incorrectly select the device.

As for the syncing issue, I'm unsure about what you mean, would you care to elaborate?

@roderickvd roderickvd changed the title Implement Device::id() for macOS and Windows (WASAPI) feat: stable Device::id() Sep 28, 2025
@roderickvd
Copy link
Member

From your PR description, which I edited to show the progress made since:

For Windows, to convert the Rust String type to a PWSTR, the following code can be used

Is that still necessary?

@xephyris
Copy link
Author

Thanks for the update @roderickvd ! I'll try getting the code updated soon for another review.

Is that still necessary?

Currently I believe it is still necessary to convert it to a PWSTR because to get the IMMDevice from the IMMDeviceEnumerator, it requires a PWSTR at least from what I recall.

This is primarily for if you are using the Device::id() to extend upon cpal functionality like I'm currently working on cpvc and adding the volume controls. If you are just trying to re-select a device from cpal, then that code to convert to PWSTR isn't necessary.

@roderickvd
Copy link
Member

But it already does pwstr.to_string() right in src/host/wasapi/device.rs line 332?

I don't have a Windows machine myself, so am trying to understand.

@xephyris
Copy link
Author

So right now I'm having it return the Device::id() as a String guid for Windows and if you iterate with device_by_id(id) to get the device, it will work fine.

The snippet about PWSTR that I included is mainly for if you want to access a Windows WASAPI audio device yourself using the id provided by cpal.

That code is more about external access using the ids from cpal, not if you are only using cpal to access and adjust audio devices.

@xephyris xephyris requested a review from roderickvd September 29, 2025 06:23
@xephyris xephyris requested a review from roderickvd October 1, 2025 06:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants