media_pp\platform\windows\wasapi/
device.rs1use windows::{
4 Win32::{
5 Devices::FunctionDiscovery::PKEY_Device_FriendlyName,
6 Foundation::RPC_E_CHANGED_MODE,
7 Media::Audio::{
8 DEVICE_STATE_ACTIVE, IMMDevice, IMMDeviceEnumerator, MMDeviceEnumerator, eCapture,
9 eConsole, eRender,
10 },
11 System::{
12 Com::{
13 CLSCTX_ALL, COINIT_MULTITHREADED, CoCreateInstance, CoInitializeEx, CoUninitialize,
14 STGM_READ,
15 StructuredStorage::{PROPVARIANT, PropVariantClear},
16 },
17 Variant::VT_LPWSTR,
18 },
19 UI::Shell::PropertiesSystem::IPropertyStore,
20 },
21 core::HSTRING,
22};
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum WasapiDeviceKind {
27 Render,
29 Capture,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct WasapiDevice {
36 pub id: String,
38 pub name: String,
41 pub kind: WasapiDeviceKind,
42 pub is_default: bool,
45}
46
47pub(crate) struct ComApartment {
49 uninitialize: bool,
50}
51
52impl ComApartment {
53 pub(crate) fn new() -> windows::core::Result<Self> {
54 let result = unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) };
55 if result == RPC_E_CHANGED_MODE {
56 return Ok(Self {
60 uninitialize: false,
61 });
62 }
63 result.ok()?;
64 Ok(Self { uninitialize: true })
65 }
66}
67
68impl Drop for ComApartment {
69 fn drop(&mut self) {
70 if self.uninitialize {
71 unsafe { CoUninitialize() };
72 }
73 }
74}
75
76pub(crate) fn list_devices(
77 kind_filter: Option<WasapiDeviceKind>,
78) -> windows::core::Result<Vec<WasapiDevice>> {
79 let _apartment = ComApartment::new()?;
80 let enumerator: IMMDeviceEnumerator =
81 unsafe { CoCreateInstance(&MMDeviceEnumerator, None, CLSCTX_ALL)? };
82
83 let kinds: &[(windows::Win32::Media::Audio::EDataFlow, WasapiDeviceKind)] = match kind_filter {
84 Some(WasapiDeviceKind::Render) => &[(eRender, WasapiDeviceKind::Render)],
85 Some(WasapiDeviceKind::Capture) => &[(eCapture, WasapiDeviceKind::Capture)],
86 None => &[
87 (eRender, WasapiDeviceKind::Render),
88 (eCapture, WasapiDeviceKind::Capture),
89 ],
90 };
91
92 let mut devices = Vec::new();
93 for &(dataflow, kind) in kinds {
94 let default_id = unsafe { enumerator.GetDefaultAudioEndpoint(dataflow, eConsole) }
95 .ok()
96 .and_then(|device| unsafe { device.GetId() }.ok())
97 .and_then(|id| unsafe { id.to_string() }.ok());
98
99 let collection = unsafe { enumerator.EnumAudioEndpoints(dataflow, DEVICE_STATE_ACTIVE)? };
100 let count = unsafe { collection.GetCount()? };
101 for index in 0..count {
102 let device = unsafe { collection.Item(index)? };
103 let Some(id) = unsafe { device.GetId() }
104 .ok()
105 .and_then(|id| unsafe { id.to_string() }.ok())
106 else {
107 continue;
108 };
109 let name = device_friendly_name(&device).unwrap_or_else(|| id.clone());
110 let is_default = default_id.as_deref() == Some(id.as_str());
111 devices.push(WasapiDevice {
112 id,
113 name,
114 kind,
115 is_default,
116 });
117 }
118 }
119 Ok(devices)
120}
121
122pub(crate) fn open_device(id: &str) -> windows::core::Result<IMMDevice> {
123 let enumerator: IMMDeviceEnumerator =
124 unsafe { CoCreateInstance(&MMDeviceEnumerator, None, CLSCTX_ALL)? };
125 let id = HSTRING::from(id);
126 unsafe { enumerator.GetDevice(&id) }
127}
128
129fn device_friendly_name(device: &IMMDevice) -> Option<String> {
130 unsafe {
131 let store: IPropertyStore = device.OpenPropertyStore(STGM_READ).ok()?;
132 let mut variant: PROPVARIANT = store.GetValue(&PKEY_Device_FriendlyName).ok()?;
133 let name = property_variant_to_string(&variant);
134 let _ = PropVariantClear(&mut variant);
135 name
136 }
137}
138
139fn property_variant_to_string(variant: &PROPVARIANT) -> Option<String> {
140 unsafe {
141 if variant.Anonymous.Anonymous.vt != VT_LPWSTR {
142 return None;
143 }
144 variant
145 .Anonymous
146 .Anonymous
147 .Anonymous
148 .pwszVal
149 .to_string()
150 .ok()
151 }
152}