Volver a la página principal
sábado 21 septiembre 2024
8

Cómo obtener un listado de dispositivos de audio mediante la API de Windows en Rust

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.

Requisitos previos

Antes de comenzar, asegúrate de tener lo siguiente:

  • Rust instalado: Puedes instalar Rust siguiendo las instrucciones oficiales en rust-lang.org.
  • Paquete windows-sys: Este paquete permite trabajar directamente con las APIs de Windows en Rust.

Configuración del proyecto

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.

Uso de la API de Windows para listar 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.

Paso 1: Importar las dependencias necesarias

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};

Paso 2: Inicializar COM

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(())
}

Paso 3: Crear el enumerador de dispositivos de audio

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)
}

Paso 4: Enumerar los dispositivos de audio

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)
}

Paso 5: Obtener los nombres de los dispositivos

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)
}

Paso 6: Listar todos los dispositivos

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),
    }
}

Explicación del código

  • 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.

Conclusión

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.

Etiquetas:
windows rust
Compartir:
Creado por:
Author photo

Jorge García

Fullstack developer