1 /** 2 Helpers for using WASAPI. 3 */ 4 module wasapi.comutils; 5 version(Windows): 6 7 import wasapi.coreaudio; 8 import core.sys.windows.windows; 9 import core.sys.windows.objidl; 10 import core.sys.windows.wtypes; 11 12 wstring getWstringProp(IPropertyStore propStore, const ref PROPERTYKEY key) { 13 if (!propStore) 14 return null; 15 PROPVARIANT var; 16 // Initialize container for property value. 17 //PropVariantInit(&varName); 18 // Get the endpoint's friendly-name property. 19 HRESULT hr = propStore.GetValue(key, var); 20 if (hr) 21 return null; // not found 22 if (var.vt == VARENUM.VT_BSTR) { 23 return fromWstringz(var.bstrVal); 24 } 25 if (var.vt == VARENUM.VT_LPWSTR) { 26 return fromWstringz(var.pwszVal); 27 } 28 //Log.d("Unknown variant type ", var.vt); 29 return null; 30 } 31 32 struct ComAutoPtr(T : IUnknown) { 33 T ptr; 34 alias ptr this; 35 ref ComAutoPtr opAssign(T value) { 36 if (ptr) 37 clear(); 38 ptr = value; 39 return this; 40 } 41 ~this() { 42 clear(); 43 } 44 @property bool isNull() { 45 return !ptr; 46 } 47 void clear() { 48 if (ptr) { 49 ptr.Release(); 50 ptr = null; 51 } 52 } 53 } 54 55 struct WstrAutoPtr { 56 LPWSTR ptr; 57 alias ptr this; 58 ref WstrAutoPtr opAssign(LPWSTR value) { 59 if (value && ptr && value !is ptr) 60 CoTaskMemFree(ptr); 61 ptr = value; 62 return this; 63 } 64 ~this() { 65 if (ptr) 66 CoTaskMemFree(ptr); 67 } 68 @property wstring toWstring() { 69 if (!ptr) 70 return null; 71 return fromWstringz(ptr); 72 } 73 @property string toString() { 74 if (!ptr) 75 return null; 76 import std.utf : toUTF8; 77 return fromWstringz(ptr).toUTF8; 78 } 79 @property bool isNull() { 80 return !ptr; 81 } 82 } 83 84 struct PropVariant { 85 PROPVARIANT var; 86 alias var this; 87 ~this() { 88 PropVariantClear(&var); 89 } 90 } 91 92 string getStringProp(IPropertyStore propStore, const ref PROPERTYKEY key) { 93 if (!propStore) 94 return null; 95 PROPVARIANT var; 96 // Initialize container for property value. 97 //PropVariantInit(&varName); 98 // Get the endpoint's friendly-name property. 99 HRESULT hr = propStore.GetValue(key, var); 100 if (hr) 101 return null; // not found 102 import std.utf; 103 if (var.vt == VARENUM.VT_BSTR) { 104 return fromWstringz(var.bstrVal).toUTF8; 105 } 106 if (var.vt == VARENUM.VT_LPWSTR) { 107 return fromWstringz(var.pwszVal).toUTF8; 108 } 109 //Log.d("Unknown variant type ", var.vt); 110 return null; 111 } 112 113 wstring fromWstringz(wchar * s) { 114 if (!s) 115 return null; 116 int len = 0; 117 for(; s[len]; len++) { 118 } 119 return s[0 .. len].dup; 120 } 121 122 class MMDevice { 123 string id; 124 string friendlyName; 125 string interfaceName; 126 string desc; 127 uint state; 128 bool isDefault; 129 130 override string toString() { 131 return (isDefault ? "DEFAULT:" : "") ~ id ~ " : \"" ~ friendlyName ~ "\""; 132 } 133 } 134 135 class MMDevices { 136 private bool _initialized = false; 137 private IMMDeviceEnumerator _pEnum; 138 this() { 139 } 140 @property bool initialized() { return _initialized; } 141 142 /// get endpoint interface for device 143 IMMEndpoint getEndpoint(MMDevice dev) { 144 return getEndpoint(dev.id); 145 } 146 147 /// get endpoint interface by device id 148 IMMDevice getDevice(string deviceId) { 149 ComAutoPtr!IMMDeviceCollection pDevices; 150 151 HRESULT hr = _pEnum.EnumAudioEndpoints( 152 /* [in] */ 153 EDataFlow.eRender, 154 /* [in] */ 155 DEVICE_STATE_ACTIVE, //DWORD dwStateMask, 156 /* [out] */ 157 pDevices); 158 if (hr) { 159 //Log.e("MMDeviceEnumerator.EnumAudioEndpoints failed"); 160 return null; 161 } 162 163 DWORD count; 164 hr = pDevices.GetCount(count); 165 166 for (DWORD i = 0; i < count; i++) { 167 ComAutoPtr!IMMDevice pDevice; 168 hr = pDevices.Item(i, pDevice); 169 if (!hr) { 170 WstrAutoPtr pId; 171 hr = pDevice.GetId(pId); 172 string id = pId.toString; 173 if (id == deviceId) { 174 pDevice.AddRef(); 175 return pDevice; 176 } 177 } 178 } 179 return null; 180 } 181 182 183 /// get endpoint interface by device id 184 IMMEndpoint getEndpoint(string deviceId) { 185 ComAutoPtr!IMMDevice pDevice; 186 pDevice = getDevice(deviceId); 187 if (!pDevice.isNull) { 188 IMMEndpoint res; 189 HRESULT hr = pDevice.QueryInterface(&IID_IMMEndpoint, cast(void**)(&res)); 190 if (hr) { 191 //Log.e("QueryInterface IID_IMMEndpoint failed for device ", deviceId); 192 return null; 193 } 194 // FOUND! 195 return res; 196 } 197 return null; 198 } 199 200 IAudioClient getAudioClient(string deviceId) { 201 ComAutoPtr!IMMDevice pDevice; 202 pDevice = getDevice(deviceId); 203 if (!pDevice.isNull) { 204 IAudioClient pAudioClient; 205 HRESULT hr = pDevice.Activate( 206 IID_IAudioClient, CLSCTX_ALL, 207 null, cast(void**)&pAudioClient); 208 if (hr) { 209 //Log.e("Activate IID_IAudioClient failed for device ", deviceId); 210 return null; 211 } 212 return pAudioClient; 213 } 214 return null; 215 } 216 217 /// read device list 218 MMDevice[] getPlaybackDevices() { 219 import std.utf : toUTF8; 220 if (!_initialized || !_pEnum) 221 return null; 222 223 HRESULT hr; 224 string defDeviceId; 225 { 226 ComAutoPtr!IMMDevice defDevice; 227 hr = _pEnum.GetDefaultAudioEndpoint( 228 /* [in] */ 229 EDataFlow.eRender, 230 /* [in] */ 231 ERole.eMultimedia, 232 /* [out] */ 233 defDevice); 234 if (!hr) { 235 WstrAutoPtr pId; 236 hr = defDevice.GetId(pId); 237 defDeviceId = pId.toString; 238 } 239 } 240 //Log.d("Default device ID=", defDeviceId); 241 242 MMDevice[] res; 243 ComAutoPtr!IMMDeviceCollection pDevices; 244 hr = _pEnum.EnumAudioEndpoints( 245 /* [in] */ 246 EDataFlow.eRender, 247 /* [in] */ 248 DEVICE_STATE_ACTIVE, //DWORD dwStateMask, 249 /* [out] */ 250 pDevices); 251 if (hr) { 252 //Log.e("MMDeviceEnumerator.EnumAudioEndpoints failed"); 253 return null; 254 } 255 256 257 DWORD count; 258 hr = pDevices.GetCount(count); 259 260 int defaultIndex = -1; 261 for (DWORD i = 0; i < count; i++) { 262 ComAutoPtr!IMMDevice pDevice; 263 hr = pDevices.Item(i, pDevice); 264 if (!hr) { 265 MMDevice dev = new MMDevice(); 266 WstrAutoPtr pId; 267 hr = pDevice.GetId(pId); 268 string id = pId.toString; 269 DWORD state; 270 hr = pDevice.GetState(state); 271 272 dev.id = id; 273 dev.state = state; 274 275 ComAutoPtr!IPropertyStore propStore; 276 hr = pDevice.OpenPropertyStore(STGM_READ, propStore); 277 if (!hr) { 278 dev.friendlyName = propStore.getStringProp(DEVPKEY_Device_FriendlyName); 279 dev.interfaceName = propStore.getStringProp(DEVPKEY_DeviceInterface_FriendlyName); 280 dev.desc = propStore.getStringProp(DEVPKEY_Device_DeviceDesc); 281 } 282 dev.isDefault = dev.id == defDeviceId; 283 if (dev.isDefault) 284 defaultIndex = i; 285 //Log.d("ID: ", id, " state:", state, " friendlyName:", friendlyName, "\nintfName=", interfaceFriendlyName, "\ndesc:", deviceDesc); 286 res ~= dev; 287 } 288 } 289 // move default to top 290 if (defaultIndex > 0) { 291 // swap 292 MMDevice tmp = res[0]; 293 res[0] = res[defaultIndex]; 294 res[defaultIndex] = tmp; 295 } 296 return res; 297 } 298 bool init() { 299 HRESULT hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, null, 300 CLSCTX_ALL, 301 &IID_IMMDeviceEnumerator, 302 cast(void**)&_pEnum); 303 if (hr) { 304 //Log.e("CoCreateInstance for MMDeviceEnumerator is failed"); 305 return false; 306 } 307 _initialized = true; 308 return true; 309 } 310 void uninit() { 311 if (_pEnum) { 312 _pEnum.Release(); 313 _pEnum = null; 314 } 315 } 316 ~this() { 317 uninit(); 318 } 319 }