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 }