En este artículo, te mostraré cómo obtener un listado de los dispositivos de audio disponibles en tu sistema Windows utilizando Rust, con el paquete windows-sys
. Este proceso implica interactuar con la API de Windows para acceder a los dispositivos de audio utilizando MMDeviceEnumerator
y otras interfaces relacionadas con el subsistema de audio.
Antes de comenzar, asegúrate de tener lo siguiente:
windows-sys
: Este paquete permite trabajar directamente con las APIs de Windows en Rust.
1. Crea un nuevo proyecto de Rust si no lo tienes ya configurado:
cargo new listado_dispositivos_audio
cd listado_dispositivos_audio
2. Edita el archivo Cargo.toml
para incluir las dependencias necesarias:
[dependencies]
windows-sys = "0.48.0"
El paquete windows-sys
nos permitirá utilizar las interfaces COM necesarias para acceder a los dispositivos de audio.
La API de Windows para dispositivos de audio se encuentra en el subsistema de Multimedia Device (MMDevice), donde se utiliza el enumerador MMDeviceEnumerator
para acceder a los dispositivos de audio. Utilizaremos esta API para obtener una lista de dispositivos de reproducción.
Primero, vamos a importar las librerías que necesitamos para interactuar con las APIs de Windows. En Rust, esto se hace mediante el uso del paquete windows-sys
que proporciona acceso a las APIs de Windows.
Edita el archivo src/main.rs
e importa las siguientes bibliotecas:
use std::ptr::null_mut;
use windows_sys::Win32::Media::Audio::{
eRender, eMultimedia, IMMDeviceEnumerator, IMMDeviceCollection, IMMDevice,
IPropertyStore, DEVICE_STATE_ACTIVE
};
use windows_sys::Win32::System::Com::{CoInitializeEx, CoCreateInstance, COINIT_MULTITHREADED, CLSCTX_ALL};
use windows_sys::Win32::Foundation::E_FAIL;
use windows_sys::Win32::UI::Shell::PropertiesSystem::{PROPERTYKEY, PKEY_Device_FriendlyName, PROPVARIANT};
use windows_sys::core::{GUID, PCWSTR};
Antes de interactuar con cualquier interfaz COM (Component Object Model) en Windows, es necesario inicializar el modelo de objetos COM usando CoInitializeEx
.
fn initialize_com() -> Result<(), String> {
unsafe {
let hr = CoInitializeEx(null_mut(), COINIT_MULTITHREADED);
if hr != 0 {
return Err(format!("Error al inicializar COM: 0x{:X}", hr));
}
}
Ok(())
}
Usaremos CoCreateInstance
para crear una instancia del enumerador de dispositivos de audio MMDeviceEnumerator
. Esta función toma varios parámetros, incluyendo el CLSID del objeto que queremos crear y el IID de la interfaz que vamos a utilizar.
fn create_device_enumerator() -> Result<*mut IMMDeviceEnumerator, String> {
let mut enumerator: *mut IMMDeviceEnumerator = null_mut();
unsafe {
let hr = CoCreateInstance(
&windows_sys::Win32::Media::Audio::MMDeviceEnumerator,
null_mut(),
CLSCTX_ALL,
&IMMDeviceEnumerator::IID,
&mut enumerator as *mut _ as *mut _,
);
if hr != 0 {
return Err(format!("Error al crear el enumerador de dispositivos: 0x{:X}", hr));
}
}
Ok(enumerator)
}
Una vez que tenemos el enumerador de dispositivos, podemos llamar a IMMDeviceEnumerator::EnumAudioEndpoints
para obtener una colección de dispositivos de audio disponibles. Esta función acepta un tipo de dispositivo (en este caso, dispositivos de renderizado de audio) y el estado de los dispositivos que queremos (dispositivos activos).
fn get_audio_devices(enumerator: *mut IMMDeviceEnumerator) -> Result<*mut IMMDeviceCollection, String> {
let mut device_collection: *mut IMMDeviceCollection = null_mut();
unsafe {
let hr = (*enumerator).EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &mut device_collection);
if hr != 0 {
return Err(format!("Error al enumerar los dispositivos de audio: 0x{:X}", hr));
}
}
Ok(device_collection)
}
Ahora que tenemos la colección de dispositivos, podemos iterar sobre ellos y obtener información como el nombre amigable (friendly name) de cada dispositivo usando la interfaz IPropertyStore
.
fn get_device_name(device: *mut IMMDevice) -> Result<String, String> {
let mut property_store: *mut IPropertyStore = null_mut();
unsafe {
let hr = (*device).OpenPropertyStore(windows_sys::Win32::System::PropertiesSystem::STGM_READ, &mut property_store);
if hr != 0 {
return Err(format!("Error al abrir la propiedad del dispositivo: 0x{:X}", hr));
}
let mut prop_variant: PROPVARIANT = std::mem::zeroed();
let hr = (*property_store).GetValue(&PKEY_Device_FriendlyName, &mut prop_variant);
if hr != 0 {
return Err(format!("Error al obtener el nombre del dispositivo: 0x{:X}", hr));
}
// Convertir el nombre a String
let device_name = pwstr_to_string(prop_variant.Anonymous.Anonymous.pwszVal);
// Liberar recursos
windows_sys::Win32::System::PropertiesSystem::PropVariantClear(&mut prop_variant);
Ok(device_name)
}
}
fn pwstr_to_string(pwstr: PCWSTR) -> String {
if pwstr.is_null() {
return String::new();
}
let len = unsafe {
let mut len = 0;
while *pwstr.offset(len) != 0 {
len += 1;
}
len
};
let slice = unsafe { std::slice::from_raw_parts(pwstr, len as usize) };
String::from_utf16_lossy(slice)
}
Finalmente, vamos a iterar sobre la colección de dispositivos y obtener el nombre de cada uno.
fn list_audio_devices() -> Result<(), String> {
initialize_com()?;
let enumerator = create_device_enumerator()?;
let device_collection = get_audio_devices(enumerator)?;
let mut device_count = 0;
unsafe {
let hr = (*device_collection).GetCount(&mut device_count);
if hr != 0 {
return Err(format!("Error al obtener el número de dispositivos: 0x{:X}", hr));
}
}
for i in 0..device_count {
let mut device: *mut IMMDevice = null_mut();
unsafe {
let hr = (*device_collection).Item(i, &mut device);
if hr != 0 {
return Err(format!("Error al obtener el dispositivo en la posición {}: 0x{:X}", i, hr));
}
}
let device_name = get_device_name(device)?;
println!("Dispositivo {}: {}", i + 1, device_name);
}
Ok(())
}
fn main() {
match list_audio_devices() {
Ok(_) => println!("Listado de dispositivos completo."),
Err(e) => eprintln!("Error: {}", e),
}
}
initialize_com()
: Inicializa el sistema COM necesario para trabajar con las APIs de Windows.
create_device_enumerator()
: Crea un objeto IMMDeviceEnumerator
, que es el punto de entrada para interactuar con los dispositivos de audio.
get_audio_devices()
: Recupera una colección de dispositivos de audio activos de tipo eRender
(dispositivos de salida, como altavoces o auriculares).
get_device_name()
: Abre la propiedad PKEY_Device_FriendlyName
para obtener el nombre amigable del dispositivo.
list_audio_devices()
: Itera sobre los dispositivos de audio disponibles e imprime sus nombres en la consola.
En este artículo, hemos mostrado cómo utilizar la API de Windows en Rust para obtener un listado de los dispositivos de audio disponibles en el sistema mediante el uso del paquete windows-sys
. Esto nos permite acceder a información relevante de cada dispositivo, como su nombre amigable, y proporciona una base para interactuar con otras características del sistema de audio de Windows.
Si bien la API de Windows puede ser compleja, Rust y windows-sys
ofrecen herramientas poderosas para trabajar directamente con estas interfaces, permitiendo un control detallado sobre las funcionalidades del sistema operativo.
Jorge García
Fullstack developer