En este artículo, exploraremos cómo enumerar dispositivos de audio en un sistema Windows utilizando Rust y las API proporcionadas por la biblioteca de Windows. A través del uso de CoInitialize
, CoCreateInstance
y las interfaces de IMMDeviceEnumerator
, es posible obtener una lista detallada de los dispositivos de audio disponibles en el sistema. Esto incluye tanto los dispositivos de entrada como los de salida de audio, junto con propiedades como su nombre y estado.
Antes de sumergirnos en el código, es importante entender brevemente el modelo COM. El Component Object Model (COM) es una plataforma de Microsoft que permite la interacción entre objetos en diferentes lenguajes de programación. En este caso, utilizamos COM para interactuar con las interfaces de audio de Windows y obtener detalles sobre los dispositivos disponibles en el sistema.
El siguiente código en Rust utiliza las API de Windows para inicializar COM, crear una instancia del enumerador de dispositivos y luego iterar a través de todos los dispositivos de audio conectados. El código devuelve información útil sobre cada dispositivo, como su nombre y su ID.
use windows::Win32::Media::Audio::{
IMMDevice,
IMMDeviceEnumerator,
MMDeviceEnumerator,
};
use windows::Win32::Media::Audio::{
EDataFlow,
DEVICE_STATE
};
use windows::core::{GUID, Result as WindowsResult};
use windows::core::*;
use windows::Win32::System::Com::{CoCreateInstance, CLSCTX_ALL, CoInitialize, STGM_READ};
use windows::Win32::UI::Shell::PropertiesSystem::{IPropertyStore, PROPERTYKEY};
let mut devices_all: Vec<PROPVARIANT> = Vec::new();
unsafe {
// Importar el CLSID del enumerador
const CLSID_MMDeviceEnumerator: GUID = GUID::from_u128(0xA95664D2_9614_4F35_A746_DE8DB63617E6);
const IID_IMMDeviceEnumerator: GUID = GUID::from_u128(0xA95664D2_9614_4F35_A746_DE8DB63617E6);
// Inicializamos COM (Component Object Model)
let INIT_COM = CoInitialize(Some(std::ptr::null_mut()));
if INIT_COM.is_ok() {
println!("COM inicializado correctamente.");
} else if INIT_COM.is_err() {
println!("COM ya estaba inicializado.");
}
let hr: WindowsResult<IMMDeviceEnumerator> =
CoCreateInstance(
&MMDeviceEnumerator,
None,
CLSCTX_ALL
);
if let Ok(InstanceCOM) = hr {
let all_devices = InstanceCOM.EnumAudioEndpoints(
EDataFlow(2), // eAll, para obtener todos los dispositivos
DEVICE_STATE(0x1) // Solo los dispositivos activos
);
if let Ok(devices) = all_devices {
let count_devices = devices.GetCount();
if let Ok(count) = count_devices {
for i in 0..count {
let device: Result<IMMDevice> = devices.Item(i).clone();
// Obtén el ID del dispositivo
let id = device.unwrap();
if let Ok(properties) = devices.Item(i) {
let properties: IPropertyStore = properties.OpenPropertyStore(
STGM_READ
).unwrap();
let name = properties.GetValue(&PROPERTYKEY {
fmtid: GUID::from_u128(0xa45c254e_df1c_4efd_8020_67d146a850e0),
pid: 14
});
if let Ok(variant) = name {
if let Ok(id) = id.GetId() {
let aaa = id.display().to_string();
println!("Name: {:?}, ID: {:?}", variant.to_string(), aaa);
}
devices_all.push(variant);
}
}
}
}
}
}
}
1. Inicialización de COM: El código comienza inicializando el sistema COM utilizando CoInitialize
. Esto es necesario ya que las interfaces de Windows, como las de enumeración de dispositivos de audio, dependen de COM para su funcionamiento.
let INIT_COM = CoInitialize(Some(std::ptr::null_mut()));
Si CoInitialize
regresa con éxito, se procede con el resto del código. Si no, se indica que ya estaba inicializado.
2. Creación del Enumerador de Dispositivos: Utilizamos CoCreateInstance
para crear una instancia de IMMDeviceEnumerator
, que es la interfaz que nos permite acceder a los dispositivos de audio.
let hr: WindowsResult<IMMDeviceEnumerator> =
CoCreateInstance(
&MMDeviceEnumerator,
None,
CLSCTX_ALL
);
3. Obtención de Dispositivos de Audio: Utilizando el método EnumAudioEndpoints
, se obtienen todos los dispositivos de audio (de entrada y salida) que están activos en el sistema. Los parámetros de EDataFlow(2)
y DEVICE_STATE(0x1)
especifican que queremos obtener todos los dispositivos de audio (eAll) y aquellos que están activos.
4. Iteración sobre los Dispositivos: Una vez que tenemos la lista de dispositivos, iteramos sobre ellos utilizando devices.GetCount()
y devices.Item(i)
para obtener cada dispositivo.
for i in 0..count {
let device: Result<IMMDevice> = devices.Item(i).clone();
let id = device.unwrap();
if let Ok(properties) = devices.Item(i) {
let properties: IPropertyStore = properties.OpenPropertyStore(STGM_READ).unwrap();
let name = properties.GetValue(&PROPERTYKEY {
fmtid: GUID::from_u128(0xa45c254e_df1c_4efd_8020_67d146a850e0),
pid: 14
});
}
}
5. Extracción de Propiedades: Para cada dispositivo, abrimos su almacén de propiedades mediante OpenPropertyStore
y accedemos a su nombre utilizando GetValue
con un PROPERTYKEY
específico.
El código imprime una lista de dispositivos con sus nombres y IDs en la consola. Aquí hay un ejemplo de salida que muestra varios dispositivos de audio conectados:
Name: "MSI G24C4 E2 (NVIDIA High Definition Audio)", ID: "{0.0.0.00000000}.{69ea9b05-4a45-48bf-baa3-931e3862265c}"
Name: "Audífono de los auriculares con micrófono (G435 Wireless Gaming Headset)", ID: "{0.0.0.00000000}.{a26a15f1-3417-4c82-b008-6dce9e9332ba}"
Name: "Realtek Digital Output (Realtek High Definition Audio)", ID: "{0.0.0.00000000}.{c53dfd1b-5501-4910-81fc-a3b502d8d915}"
Name: "24G2W1G3- (NVIDIA High Definition Audio)", ID: "{0.0.0.00000000}.{e18ec46f-f3cf-4d25-a88b-aa121016e36b}"
Name: "Micrófono (G435 Wireless Gaming Headset)", ID: "{0.0.1.00000000}.{1ea472c2-6174-4d7a-861b-791eb6fcf4ff}"
Name: "Mezcla estéreo (Realtek High Definition Audio)", ID: "{0.0.1.00000000}.{43392da8-40aa-46c1-ac70-e4cc5b61e972}"
Name: "Micrófono (Razer Seiren Mini)", ID: "{0.0.1.00000000}.{f0d71a46-d85f-4b66-a390-481494df1535}"
Cada dispositivo tiene un nombre y un ID único, lo que facilita la identificación de dispositivos específicos en aplicaciones de audio.
La enumeración de dispositivos de audio en Windows utilizando Rust es un proceso relativamente sencillo gracias a las API proporcionadas por la plataforma de Windows y el modelo COM. Con las funciones proporcionadas, es posible obtener una lista detallada de los dispositivos de entrada y salida de audio, permitiendo a los desarrolladores crear aplicaciones que interactúen con dispositivos específicos.
Jorge García
Fullstack developer