]> git.sesse.net Git - vlc/blob - modules/audio_output/mmdevice.c
mmdevice: guard against out of range values
[vlc] / modules / audio_output / mmdevice.c
1 /*****************************************************************************
2  * mmdevice.c : Windows Multimedia Device API audio output plugin for VLC
3  *****************************************************************************
4  * Copyright (C) 2012-2013 RĂ©mi Denis-Courmont
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2.1 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19  *****************************************************************************/
20
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24
25 #define INITGUID
26 #define COBJMACROS
27 #define CONST_VTABLE
28
29 #include <stdlib.h>
30 #include <math.h>
31 #include <assert.h>
32 #include <audiopolicy.h>
33 #include <mmdeviceapi.h>
34
35 DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd,
36    0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
37
38 #include <vlc_common.h>
39 #include <vlc_plugin.h>
40 #include <vlc_aout.h>
41 #include <vlc_charset.h>
42 #include <vlc_modules.h>
43 #include "audio_output/mmdevice.h"
44
45 #if (_WIN32_WINNT < 0x600)
46 static VOID WINAPI (*InitializeConditionVariable)(PCONDITION_VARIABLE);
47 static BOOL WINAPI (*SleepConditionVariableCS)(PCONDITION_VARIABLE,
48                                                PCRITICAL_SECTION, DWORD);
49 static VOID WINAPI (*WakeConditionVariable)(PCONDITION_VARIABLE);
50 #define LOOKUP(s) \
51     if (((s) = (void *)GetProcAddress(h, #s)) == NULL) return FALSE
52
53 BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID); /* avoid warning */
54 BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, LPVOID reserved)
55 {
56     (void) dll;
57     (void) reserved;
58
59     switch (reason)
60     {
61         case DLL_PROCESS_ATTACH:
62         {
63             HANDLE h = GetModuleHandle(TEXT("kernel32.dll"));
64             if (unlikely(h == NULL))
65                 return FALSE;
66             LOOKUP(InitializeConditionVariable);
67             LOOKUP(SleepConditionVariableCS);
68             LOOKUP(WakeConditionVariable);
69             break;
70         }
71     }
72     return TRUE;
73 }
74 #endif
75
76 DEFINE_GUID (GUID_VLC_AUD_OUT, 0x4533f59d, 0x59ee, 0x00c6,
77    0xad, 0xb2, 0xc6, 0x8b, 0x50, 0x1a, 0x66, 0x55);
78
79 static int TryEnterMTA(vlc_object_t *obj)
80 {
81     HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
82     if (unlikely(FAILED(hr)))
83     {
84         msg_Err (obj, "cannot initialize COM (error 0x%lx)", hr);
85         return -1;
86     }
87     return 0;
88 }
89 #define TryEnterMTA(o) TryEnterMTA(VLC_OBJECT(o))
90
91 static void EnterMTA(void)
92 {
93     HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
94     if (unlikely(FAILED(hr)))
95         abort();
96 }
97
98 static void LeaveMTA(void)
99 {
100     CoUninitialize();
101 }
102
103 static wchar_t default_device[1] = L"";
104
105 struct aout_sys_t
106 {
107     aout_stream_t *stream; /**< Underlying audio output stream */
108     module_t *module;
109     audio_output_t *aout;
110     IMMDeviceEnumerator *it; /**< Device enumerator, NULL when exiting */
111     IMMDevice *dev; /**< Selected output device, NULL if none */
112
113     struct IMMNotificationClient device_events;
114     struct IAudioSessionEvents session_events;
115
116     LONG refs;
117
118     wchar_t *device; /**< Requested device identifier, NULL if none */
119     float volume; /**< Requested volume, negative if none */
120     signed char mute; /**< Requested mute, negative if none */
121     CRITICAL_SECTION lock;
122     CONDITION_VARIABLE work;
123     CONDITION_VARIABLE ready;
124     vlc_thread_t thread; /**< Thread for audio session control */
125 };
126
127 /* NOTE: The Core Audio API documentation totally fails to specify the thread
128  * safety (or lack thereof) of the interfaces. This code takes the most
129  * restrictive assumption: no thread safety. The background thread (MMThread)
130  * only runs at specified times, namely between the device_ready and
131  * device_changed events (effectively a thread synchronization barrier, but
132  * only Windows 8 natively provides such a primitive).
133  *
134  * The audio output owner (i.e. the audio output core) is responsible for
135  * serializing callbacks. This code only needs to be concerned with
136  * synchronization between the set of audio output callbacks, MMThread()
137  * and (trivially) the device and session notifications. */
138
139 static int vlc_FromHR(audio_output_t *aout, HRESULT hr)
140 {
141     /* Restart on unplug */
142     if (unlikely(hr == AUDCLNT_E_DEVICE_INVALIDATED))
143         aout_RestartRequest(aout, AOUT_RESTART_OUTPUT);
144     return SUCCEEDED(hr) ? 0 : -1;
145 }
146
147 /*** VLC audio output callbacks ***/
148 static int TimeGet(audio_output_t *aout, mtime_t *restrict delay)
149 {
150     aout_sys_t *sys = aout->sys;
151     HRESULT hr;
152
153     EnterMTA();
154     hr = aout_stream_TimeGet(sys->stream, delay);
155     LeaveMTA();
156
157     return SUCCEEDED(hr) ? 0 : -1;
158 }
159
160 static void Play(audio_output_t *aout, block_t *block)
161 {
162     aout_sys_t *sys = aout->sys;
163     HRESULT hr;
164
165     EnterMTA();
166     hr = aout_stream_Play(sys->stream, block);
167     LeaveMTA();
168
169     vlc_FromHR(aout, hr);
170 }
171
172 static void Pause(audio_output_t *aout, bool paused, mtime_t date)
173 {
174     aout_sys_t *sys = aout->sys;
175     HRESULT hr;
176
177     EnterMTA();
178     hr = aout_stream_Pause(sys->stream, paused);
179     LeaveMTA();
180
181     vlc_FromHR(aout, hr);
182     (void) date;
183 }
184
185 static void Flush(audio_output_t *aout, bool wait)
186 {
187     aout_sys_t *sys = aout->sys;
188
189     EnterMTA();
190     aout_stream_Flush(sys->stream, wait);
191     LeaveMTA();
192
193 }
194
195 static int VolumeSet(audio_output_t *aout, float vol)
196 {
197     aout_sys_t *sys = aout->sys;
198
199     vol = vol * vol * vol; /* ISimpleAudioVolume is tapered linearly. */
200     EnterCriticalSection(&sys->lock);
201     sys->volume = vol;
202     WakeConditionVariable(&sys->work);
203     LeaveCriticalSection(&sys->lock);
204     return 0;
205 }
206
207 static int MuteSet(audio_output_t *aout, bool mute)
208 {
209     aout_sys_t *sys = aout->sys;
210
211     EnterCriticalSection(&sys->lock);
212     sys->mute = mute;
213     WakeConditionVariable(&sys->work);
214     LeaveCriticalSection(&sys->lock);
215     return 0;
216 }
217
218 /*** Audio session events ***/
219 static inline aout_sys_t *vlc_AudioSessionEvents_sys(IAudioSessionEvents *this)
220 {
221     return (void *)(((char *)this) - offsetof(aout_sys_t, session_events));
222 }
223
224 static STDMETHODIMP
225 vlc_AudioSessionEvents_QueryInterface(IAudioSessionEvents *this, REFIID riid,
226                                       void **ppv)
227 {
228     if (IsEqualIID(riid, &IID_IUnknown)
229      || IsEqualIID(riid, &IID_IAudioSessionEvents))
230     {
231         *ppv = this;
232         IUnknown_AddRef(this);
233         return S_OK;
234     }
235     else
236     {
237        *ppv = NULL;
238         return E_NOINTERFACE;
239     }
240 }
241
242 static STDMETHODIMP_(ULONG)
243 vlc_AudioSessionEvents_AddRef(IAudioSessionEvents *this)
244 {
245     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
246     return InterlockedIncrement(&sys->refs);
247 }
248
249 static STDMETHODIMP_(ULONG)
250 vlc_AudioSessionEvents_Release(IAudioSessionEvents *this)
251 {
252     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
253     return InterlockedDecrement(&sys->refs);
254 }
255
256 static STDMETHODIMP
257 vlc_AudioSessionEvents_OnDisplayNameChanged(IAudioSessionEvents *this,
258                                             LPCWSTR wname, LPCGUID ctx)
259 {
260     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
261     audio_output_t *aout = sys->aout;
262
263     msg_Dbg(aout, "display name changed: %ls", wname);
264     (void) ctx;
265     return S_OK;
266 }
267
268 static STDMETHODIMP
269 vlc_AudioSessionEvents_OnIconPathChanged(IAudioSessionEvents *this,
270                                          LPCWSTR wpath, LPCGUID ctx)
271 {
272     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
273     audio_output_t *aout = sys->aout;
274
275     msg_Dbg(aout, "icon path changed: %ls", wpath);
276     (void) ctx;
277     return S_OK;
278 }
279
280 static STDMETHODIMP
281 vlc_AudioSessionEvents_OnSimpleVolumeChanged(IAudioSessionEvents *this,
282                                              float vol, WINBOOL mute,
283                                              LPCGUID ctx)
284 {
285     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
286     audio_output_t *aout = sys->aout;
287
288     msg_Dbg(aout, "simple volume changed: %f, muting %sabled", vol,
289             mute ? "en" : "dis");
290     aout_VolumeReport(aout, cbrtf(vol));
291     aout_MuteReport(aout, mute == TRUE);
292     (void) ctx;
293     return S_OK;
294 }
295
296 static STDMETHODIMP
297 vlc_AudioSessionEvents_OnChannelVolumeChanged(IAudioSessionEvents *this,
298                                               DWORD count, float *vols,
299                                               DWORD changed, LPCGUID ctx)
300 {
301     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
302     audio_output_t *aout = sys->aout;
303
304     msg_Dbg(aout, "channel volume %lu of %lu changed: %f", changed, count,
305             vols[changed]);
306     (void) ctx;
307     return S_OK;
308 }
309
310 static STDMETHODIMP
311 vlc_AudioSessionEvents_OnGroupingParamChanged(IAudioSessionEvents *this,
312                                               LPCGUID param, LPCGUID ctx)
313
314 {
315     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
316     audio_output_t *aout = sys->aout;
317
318     msg_Dbg(aout, "grouping parameter changed");
319     (void) param;
320     (void) ctx;
321     return S_OK;
322 }
323
324 static STDMETHODIMP
325 vlc_AudioSessionEvents_OnStateChanged(IAudioSessionEvents *this,
326                                       AudioSessionState state)
327 {
328     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
329     audio_output_t *aout = sys->aout;
330
331     msg_Dbg(aout, "state changed: %d", state);
332     return S_OK;
333 }
334
335 static STDMETHODIMP
336 vlc_AudioSessionEvents_OnSessionDisconnected(IAudioSessionEvents *this,
337                                            AudioSessionDisconnectReason reason)
338 {
339     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
340     audio_output_t *aout = sys->aout;
341
342     switch (reason)
343     {
344         case DisconnectReasonDeviceRemoval:
345             msg_Warn(aout, "session disconnected: %s", "device removed");
346             break;
347         case DisconnectReasonServerShutdown:
348             msg_Err(aout, "session disconnected: %s", "service stopped");
349             return S_OK;
350         case DisconnectReasonFormatChanged:
351             msg_Warn(aout, "session disconnected: %s", "format changed");
352             break;
353         case DisconnectReasonSessionLogoff:
354             msg_Err(aout, "session disconnected: %s", "user logged off");
355             return S_OK;
356         case DisconnectReasonSessionDisconnected:
357             msg_Err(aout, "session disconnected: %s", "session disconnected");
358             return S_OK;
359         case DisconnectReasonExclusiveModeOverride:
360             msg_Err(aout, "session disconnected: %s", "stream overriden");
361             return S_OK;
362         default:
363             msg_Warn(aout, "session disconnected: unknown reason %d", reason);
364             return S_OK;
365     }
366     /* NOTE: audio decoder thread should get invalidated device and restart */
367     return S_OK;
368 }
369
370 static const struct IAudioSessionEventsVtbl vlc_AudioSessionEvents =
371 {
372     vlc_AudioSessionEvents_QueryInterface,
373     vlc_AudioSessionEvents_AddRef,
374     vlc_AudioSessionEvents_Release,
375
376     vlc_AudioSessionEvents_OnDisplayNameChanged,
377     vlc_AudioSessionEvents_OnIconPathChanged,
378     vlc_AudioSessionEvents_OnSimpleVolumeChanged,
379     vlc_AudioSessionEvents_OnChannelVolumeChanged,
380     vlc_AudioSessionEvents_OnGroupingParamChanged,
381     vlc_AudioSessionEvents_OnStateChanged,
382     vlc_AudioSessionEvents_OnSessionDisconnected,
383 };
384
385 /*** Audio devices ***/
386
387 /** Gets the user-readable device name */
388 static char *DeviceName(IMMDevice *dev)
389 {
390     IPropertyStore *props;
391     char *name = NULL;
392     PROPVARIANT v;
393     HRESULT hr;
394
395     hr = IMMDevice_OpenPropertyStore(dev, STGM_READ, &props);
396     if (FAILED(hr))
397         return NULL;
398
399     PropVariantInit(&v);
400     hr = IPropertyStore_GetValue(props, &PKEY_Device_FriendlyName, &v);
401     if (SUCCEEDED(hr))
402     {
403         name = FromWide(v.pwszVal);
404         PropVariantClear(&v);
405     }
406     IPropertyStore_Release(props);
407     return name;
408 }
409
410 /** Checks that a device is an output device */
411 static bool DeviceIsRender(IMMDevice *dev)
412 {
413     void *pv;
414
415     if (FAILED(IMMDevice_QueryInterface(dev, &IID_IMMEndpoint, &pv)))
416         return false;
417
418     IMMEndpoint *ep = pv;
419     EDataFlow flow;
420
421     if (FAILED(IMMEndpoint_GetDataFlow(ep, &flow)))
422         flow = eCapture;
423
424     IMMEndpoint_Release(ep);
425     return flow == eRender;
426 }
427
428 static HRESULT DeviceUpdated(audio_output_t *aout, LPCWSTR wid)
429 {
430     aout_sys_t *sys = aout->sys;
431     HRESULT hr;
432
433     IMMDevice *dev;
434     hr = IMMDeviceEnumerator_GetDevice(sys->it, wid, &dev);
435     if (FAILED(hr))
436         return hr;
437
438     if (!DeviceIsRender(dev))
439     {
440         IMMDevice_Release(dev);
441         return S_OK;
442     }
443
444     char *id = FromWide(wid);
445     if (unlikely(id == NULL))
446     {
447         IMMDevice_Release(dev);
448         return E_OUTOFMEMORY;
449     }
450
451     char *name = DeviceName(dev);
452     IMMDevice_Release(dev);
453
454     aout_HotplugReport(aout, id, (name != NULL) ? name : id);
455     free(name);
456     free(id);
457     return S_OK;
458 }
459
460 static inline aout_sys_t *vlc_MMNotificationClient_sys(IMMNotificationClient *this)
461 {
462     return (void *)(((char *)this) - offsetof(aout_sys_t, device_events));
463 }
464
465 static STDMETHODIMP
466 vlc_MMNotificationClient_QueryInterface(IMMNotificationClient *this,
467                                         REFIID riid, void **ppv)
468 {
469     if (IsEqualIID(riid, &IID_IUnknown)
470      || IsEqualIID(riid, &IID_IMMNotificationClient))
471     {
472         *ppv = this;
473         IUnknown_AddRef(this);
474         return S_OK;
475     }
476     else
477     {
478        *ppv = NULL;
479         return E_NOINTERFACE;
480     }
481 }
482
483 static STDMETHODIMP_(ULONG)
484 vlc_MMNotificationClient_AddRef(IMMNotificationClient *this)
485 {
486     aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
487     return InterlockedIncrement(&sys->refs);
488 }
489
490 static STDMETHODIMP_(ULONG)
491 vlc_MMNotificationClient_Release(IMMNotificationClient *this)
492 {
493     aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
494     return InterlockedDecrement(&sys->refs);
495 }
496
497 static STDMETHODIMP
498 vlc_MMNotificationClient_OnDefaultDeviceChange(IMMNotificationClient *this,
499                                                EDataFlow flow, ERole role,
500                                                LPCWSTR wid)
501 {
502     aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
503     audio_output_t *aout = sys->aout;
504
505     if (flow != eRender)
506         return S_OK;
507     if (role != eConsole)
508         return S_OK;
509
510     msg_Dbg(aout, "default device changed: %ls", wid); /* TODO? migrate */
511     return S_OK;
512 }
513
514 static STDMETHODIMP
515 vlc_MMNotificationClient_OnDeviceAdded(IMMNotificationClient *this,
516                                        LPCWSTR wid)
517 {
518     aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
519     audio_output_t *aout = sys->aout;
520
521     msg_Dbg(aout, "device %ls added", wid);
522     return DeviceUpdated(aout, wid);
523 }
524
525 static STDMETHODIMP
526 vlc_MMNotificationClient_OnDeviceRemoved(IMMNotificationClient *this,
527                                          LPCWSTR wid)
528 {
529     aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
530     audio_output_t *aout = sys->aout;
531     char *id = FromWide(wid);
532
533     msg_Dbg(aout, "device %ls removed", wid);
534     if (unlikely(id == NULL))
535         return E_OUTOFMEMORY;
536
537     aout_HotplugReport(aout, id, NULL);
538     free(id);
539     return S_OK;
540 }
541
542 static STDMETHODIMP
543 vlc_MMNotificationClient_OnDeviceStateChanged(IMMNotificationClient *this,
544                                               LPCWSTR wid, DWORD state)
545 {
546     aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
547     audio_output_t *aout = sys->aout;
548
549     /* TODO: show device state / ignore missing devices */
550     msg_Dbg(aout, "device %ls state changed %08lx", wid, state);
551     return S_OK;
552 }
553
554 static STDMETHODIMP
555 vlc_MMNotificationClient_OnPropertyValueChanged(IMMNotificationClient *this,
556                                                 LPCWSTR wid,
557                                                 const PROPERTYKEY key)
558 {
559     aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
560     audio_output_t *aout = sys->aout;
561
562     if (key.pid == PKEY_Device_FriendlyName.pid)
563     {
564         msg_Dbg(aout, "device %ls name changed", wid);
565         return DeviceUpdated(aout, wid);
566     }
567     return S_OK;
568 }
569
570 static const struct IMMNotificationClientVtbl vlc_MMNotificationClient =
571 {
572     vlc_MMNotificationClient_QueryInterface,
573     vlc_MMNotificationClient_AddRef,
574     vlc_MMNotificationClient_Release,
575
576     vlc_MMNotificationClient_OnDeviceStateChanged,
577     vlc_MMNotificationClient_OnDeviceAdded,
578     vlc_MMNotificationClient_OnDeviceRemoved,
579     vlc_MMNotificationClient_OnDefaultDeviceChange,
580     vlc_MMNotificationClient_OnPropertyValueChanged,
581 };
582
583 static int DevicesEnum(audio_output_t *aout, IMMDeviceEnumerator *it)
584 {
585     HRESULT hr;
586     IMMDeviceCollection *devs;
587
588     hr = IMMDeviceEnumerator_EnumAudioEndpoints(it, eRender,
589                                                 DEVICE_STATE_ACTIVE, &devs);
590     if (FAILED(hr))
591     {
592         msg_Warn(aout, "cannot enumerate audio endpoints (error 0x%lx)", hr);
593         return -1;
594     }
595
596     UINT count;
597     hr = IMMDeviceCollection_GetCount(devs, &count);
598     if (FAILED(hr))
599     {
600         msg_Warn(aout, "cannot count audio endpoints (error 0x%lx)", hr);
601         count = 0;
602     }
603
604     unsigned n = 0;
605
606     for (UINT i = 0; i < count; i++)
607     {
608         IMMDevice *dev;
609         char *id, *name;
610
611         hr = IMMDeviceCollection_Item(devs, i, &dev);
612         if (FAILED(hr))
613             continue;
614
615         /* Unique device ID */
616         LPWSTR devid;
617         hr = IMMDevice_GetId(dev, &devid);
618         if (FAILED(hr))
619         {
620             IMMDevice_Release(dev);
621             continue;
622         }
623         id = FromWide(devid);
624         CoTaskMemFree(devid);
625
626         name = DeviceName(dev);
627         IMMDevice_Release(dev);
628
629         aout_HotplugReport(aout, id, (name != NULL) ? name : id);
630         free(name);
631         free(id);
632         n++;
633     }
634     IMMDeviceCollection_Release(devs);
635     return n;
636 }
637
638 static int DeviceSelect(audio_output_t *aout, const char *id)
639 {
640     aout_sys_t *sys = aout->sys;
641     wchar_t *device;
642
643     if (id != NULL)
644     {
645         device = ToWide(id);
646         if (unlikely(device == NULL))
647             return -1;
648     }
649     else
650         device = default_device;
651
652     EnterCriticalSection(&sys->lock);
653     assert(sys->device == NULL);
654     sys->device = device;
655
656     WakeConditionVariable(&sys->work);
657     while (sys->device != NULL)
658         SleepConditionVariableCS(&sys->ready, &sys->lock, INFINITE);
659     LeaveCriticalSection(&sys->lock);
660
661     if (sys->stream != NULL && sys->dev != NULL)
662         /* Request restart of stream with the new device */
663         aout_RestartRequest(aout, AOUT_RESTART_OUTPUT);
664     return (sys->dev != NULL) ? 0 : -1;
665 }
666
667 /*** Initialization / deinitialization **/
668 static wchar_t *var_InheritWide(vlc_object_t *obj, const char *name)
669 {
670     char *v8 = var_InheritString(obj, name);
671     if (v8 == NULL)
672         return NULL;
673
674     wchar_t *v16 = ToWide(v8);
675     free(v8);
676     return v16;
677 }
678 #define var_InheritWide(o,n) var_InheritWide(VLC_OBJECT(o),n)
679
680 /** MMDevice audio output thread.
681  * This thread takes cares of the audio session control. Inconveniently enough,
682  * the audio session control interface must:
683  *  - be created and destroyed from the same thread, and
684  *  - survive across VLC audio output calls.
685  * The only way to reconcile both requirements is a custom thread.
686  * The thread also ensure that the COM Multi-Thread Apartment is continuously
687  * referenced so that MMDevice objects are not destroyed early.
688  * Furthermore, VolumeSet() and MuteSet() may be called from a thread with a
689  * COM STA, so that it cannot access the COM MTA for audio controls.
690  */
691 static HRESULT MMSession(audio_output_t *aout, IMMDeviceEnumerator *it)
692 {
693     aout_sys_t *sys = aout->sys;
694     IAudioSessionManager *manager;
695     IAudioSessionControl *control;
696     ISimpleAudioVolume *volume;
697     void *pv;
698     HRESULT hr;
699
700     assert(sys->device != NULL);
701     assert(sys->dev == NULL);
702
703     if (sys->device != default_device) /* Device selected explicitly */
704     {
705         msg_Dbg(aout, "using selected device %ls", sys->device);
706         hr = IMMDeviceEnumerator_GetDevice(it, sys->device, &sys->dev);
707         if (FAILED(hr))
708             msg_Err(aout, "cannot get selected device %ls (error 0x%lx)",
709                     sys->device, hr);
710         free(sys->device);
711     }
712     else
713         hr = AUDCLNT_E_DEVICE_INVALIDATED;
714
715     while (hr == AUDCLNT_E_DEVICE_INVALIDATED)
716     {   /* Default device selected by policy and with stream routing.
717          * "Do not use eMultimedia" says MSDN. */
718         msg_Dbg(aout, "using default device");
719         hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(it, eRender,
720                                                          eConsole, &sys->dev);
721         if (FAILED(hr))
722             msg_Err(aout, "cannot get default device (error 0x%lx)", hr);
723     }
724
725     sys->device = NULL;
726     WakeConditionVariable(&sys->ready);
727
728     if (SUCCEEDED(hr))
729     {   /* Report actual device */
730         LPWSTR wdevid;
731
732         hr = IMMDevice_GetId(sys->dev, &wdevid);
733         if (SUCCEEDED(hr))
734         {
735             char *id = FromWide(wdevid);
736             CoTaskMemFree(wdevid);
737             if (likely(id != NULL))
738             {
739                 aout_DeviceReport(aout, id);
740                 free(id);
741             }
742         }
743     }
744     else
745     {
746         msg_Err(aout, "cannot get device identifier (error 0x%lx)", hr);
747         return hr;
748     }
749
750     /* Create session manager (for controls even w/o active audio client) */
751     hr = IMMDevice_Activate(sys->dev, &IID_IAudioSessionManager,
752                             CLSCTX_ALL, NULL, &pv);
753     manager = pv;
754     if (SUCCEEDED(hr))
755     {
756         LPCGUID guid = &GUID_VLC_AUD_OUT;
757
758         /* Register session control */
759         hr = IAudioSessionManager_GetAudioSessionControl(manager, guid, 0,
760                                                          &control);
761         if (SUCCEEDED(hr))
762         {
763             wchar_t *ua = var_InheritWide(aout, "user-agent");
764             IAudioSessionControl_SetDisplayName(control, ua, NULL);
765             free(ua);
766
767             IAudioSessionControl_RegisterAudioSessionNotification(control,
768                                                          &sys->session_events);
769         }
770         else
771             msg_Err(aout, "cannot get session control (error 0x%lx)", hr);
772
773         hr = IAudioSessionManager_GetSimpleAudioVolume(manager, guid, FALSE,
774                                                        &volume);
775         if (SUCCEEDED(hr))
776         {   /* Get current values _after_ registering for notification */
777             BOOL mute;
778             float level;
779
780             hr = ISimpleAudioVolume_GetMute(volume, &mute);
781             if (SUCCEEDED(hr))
782                 aout_MuteReport(aout, mute != FALSE);
783             else
784                 msg_Err(aout, "cannot get mute (error 0x%lx)", hr);
785
786             hr = ISimpleAudioVolume_GetMasterVolume(volume, &level);
787             if (SUCCEEDED(hr))
788                 aout_VolumeReport(aout, level);
789             else
790                 msg_Err(aout, "cannot get mute (error 0x%lx)", hr);
791         }
792         else
793             msg_Err(aout, "cannot get simple volume (error 0x%lx)", hr);
794     }
795     else
796     {
797         msg_Err(aout, "cannot activate session manager (error 0x%lx)", hr);
798         control = NULL;
799         volume = NULL;
800     }
801
802     /* Main loop (adjust volume as long as device is unchanged) */
803     while (sys->device == NULL)
804     {
805         if (volume != NULL && sys->volume >= 0.f)
806         {
807             if (sys->volume > 1.f)
808                 sys->volume = 1.f;
809
810             hr = ISimpleAudioVolume_SetMasterVolume(volume, sys->volume, NULL);
811             if (FAILED(hr))
812                 msg_Err(aout, "cannot set master volume (error 0x%lx)", hr);
813             sys->volume = -1.f;
814         }
815
816         if (volume != NULL && sys->mute >= 0)
817         {
818             hr = ISimpleAudioVolume_SetMute(volume,
819                                             sys->mute ? TRUE : FALSE, NULL);
820             if (FAILED(hr))
821                 msg_Err(aout, "cannot set mute (error 0x%lx)", hr);
822             sys->mute = -1;
823         }
824
825         SleepConditionVariableCS(&sys->work, &sys->lock, INFINITE);
826     }
827
828     if (manager != NULL)
829     {
830         /* Deregister session control */
831         if (volume != NULL)
832             ISimpleAudioVolume_Release(volume);
833
834         if (control != NULL)
835         {
836             IAudioSessionControl_UnregisterAudioSessionNotification(control,
837                                                          &sys->session_events);
838             IAudioSessionControl_Release(control);
839         }
840
841         IAudioSessionManager_Release(manager);
842     }
843
844     IMMDevice_Release(sys->dev);
845     sys->dev = NULL;
846     return S_OK;
847 }
848
849 static void *MMThread(void *data)
850 {
851     audio_output_t *aout = data;
852     aout_sys_t *sys = aout->sys;
853     IMMDeviceEnumerator *it = sys->it;
854
855     EnterMTA();
856     IMMDeviceEnumerator_RegisterEndpointNotificationCallback(it,
857                                                           &sys->device_events);
858     DevicesEnum(aout, it);
859
860     EnterCriticalSection(&sys->lock);
861
862     do
863         if (FAILED(MMSession(aout, it)))
864             SleepConditionVariableCS(&sys->work, &sys->lock, INFINITE);
865     while (sys->it != NULL);
866
867     LeaveCriticalSection(&sys->lock);
868
869     IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(it,
870                                                           &sys->device_events);
871     IMMDeviceEnumerator_Release(it);
872     LeaveMTA();
873     return NULL;
874 }
875
876 /**
877  * Callback for aout_stream_t to create a stream on the device.
878  * This can instantiate an IAudioClient or IDirectSound(8) object.
879  */
880 static HRESULT ActivateDevice(void *opaque, REFIID iid, PROPVARIANT *actparms,
881                               void **restrict pv)
882 {
883     IMMDevice *dev = opaque;
884     return IMMDevice_Activate(dev, iid, CLSCTX_ALL, actparms, pv);
885 }
886
887 static int aout_stream_Start(void *func, va_list ap)
888 {
889     aout_stream_start_t start = func;
890     aout_stream_t *s = va_arg(ap, aout_stream_t *);
891     audio_sample_format_t *fmt = va_arg(ap, audio_sample_format_t *);
892     HRESULT *hr = va_arg(ap, HRESULT *);
893
894     *hr = start(s, fmt, &GUID_VLC_AUD_OUT);
895     if (*hr == AUDCLNT_E_DEVICE_INVALIDATED)
896         return VLC_ETIMEOUT;
897     return SUCCEEDED(*hr) ? VLC_SUCCESS : VLC_EGENERIC;
898 }
899
900 static void aout_stream_Stop(void *func, va_list ap)
901 {
902     aout_stream_stop_t stop = func;
903     aout_stream_t *s = va_arg(ap, aout_stream_t *);
904
905     stop(s);
906 }
907
908 static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
909 {
910     aout_sys_t *sys = aout->sys;
911
912     if (sys->dev == NULL)
913         return -1;
914
915     aout_stream_t *s = vlc_object_create(aout, sizeof (*s));
916     if (unlikely(s == NULL))
917         return -1;
918
919     s->owner.device = sys->dev;
920     s->owner.activate = ActivateDevice;
921
922     EnterMTA();
923     for (;;)
924     {
925         HRESULT hr;
926
927         sys->module = vlc_module_load(s, "aout stream", NULL, false,
928                                       aout_stream_Start, s, fmt, &hr);
929         if (hr != AUDCLNT_E_DEVICE_INVALIDATED || DeviceSelect(aout, NULL))
930             break;
931     }
932     LeaveMTA();
933
934     if (sys->module == NULL)
935     {
936         vlc_object_release(s);
937         return -1;
938     }
939
940     assert (sys->stream == NULL);
941     sys->stream = s;
942     return 0;
943 }
944
945 static void Stop(audio_output_t *aout)
946 {
947     aout_sys_t *sys = aout->sys;
948
949     assert (sys->stream != NULL);
950
951     EnterMTA();
952     vlc_module_unload(sys->module, aout_stream_Stop, sys->stream);
953     LeaveMTA();
954
955     vlc_object_release(sys->stream);
956     sys->stream = NULL;
957 }
958
959 static int Open(vlc_object_t *obj)
960 {
961     audio_output_t *aout = (audio_output_t *)obj;
962
963     aout_sys_t *sys = malloc(sizeof (*sys));
964     if (unlikely(sys == NULL))
965         return VLC_ENOMEM;
966
967     aout->sys = sys;
968     sys->stream = NULL;
969     sys->aout = aout;
970     sys->it = NULL;
971     sys->dev = NULL;
972     sys->device_events.lpVtbl = &vlc_MMNotificationClient;
973     sys->session_events.lpVtbl = &vlc_AudioSessionEvents;
974     sys->refs = 1;
975
976     sys->device = default_device;
977     sys->volume = -1.f;
978     sys->mute = -1;
979     InitializeCriticalSection(&sys->lock);
980     InitializeConditionVariable(&sys->work);
981     InitializeConditionVariable(&sys->ready);
982
983     /* Initialize MMDevice API */
984     if (TryEnterMTA(aout))
985         goto error;
986
987     void *pv;
988     HRESULT hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
989                                   &IID_IMMDeviceEnumerator, &pv);
990     if (FAILED(hr))
991     {
992         LeaveMTA();
993         msg_Dbg(aout, "cannot create device enumerator (error 0x%lx)", hr);
994         goto error;
995     }
996     sys->it = pv;
997
998     if (vlc_clone(&sys->thread, MMThread, aout, VLC_THREAD_PRIORITY_LOW))
999     {
1000         IMMDeviceEnumerator_Release(sys->it);
1001         LeaveMTA();
1002         goto error;
1003     }
1004
1005     EnterCriticalSection(&sys->lock);
1006     while (sys->device != NULL)
1007         SleepConditionVariableCS(&sys->ready, &sys->lock, INFINITE);
1008     LeaveCriticalSection(&sys->lock);
1009     LeaveMTA(); /* Leave MTA after thread has entered MTA */
1010
1011     aout->start = Start;
1012     aout->stop = Stop;
1013     aout->time_get = TimeGet;
1014     aout->play = Play;
1015     aout->pause = Pause;
1016     aout->flush = Flush;
1017     aout->volume_set = VolumeSet;
1018     aout->mute_set = MuteSet;
1019     aout->device_select = DeviceSelect;
1020     return VLC_SUCCESS;
1021
1022 error:
1023     DeleteCriticalSection(&sys->lock);
1024     free(sys);
1025     return VLC_EGENERIC;
1026 }
1027
1028 static void Close(vlc_object_t *obj)
1029 {
1030     audio_output_t *aout = (audio_output_t *)obj;
1031     aout_sys_t *sys = aout->sys;
1032
1033     EnterCriticalSection(&sys->lock);
1034     sys->device = default_device; /* break out of MMSession() loop */
1035     sys->it = NULL; /* break out of MMThread() loop */
1036     WakeConditionVariable(&sys->work);
1037     LeaveCriticalSection(&sys->lock);
1038
1039     vlc_join(sys->thread, NULL);
1040     DeleteCriticalSection(&sys->lock);
1041     free(sys);
1042 }
1043
1044 vlc_module_begin()
1045     set_shortname("MMDevice")
1046     set_description(N_("Windows Multimedia Device output"))
1047     set_capability("audio output", 150)
1048     set_category(CAT_AUDIO)
1049     set_subcategory(SUBCAT_AUDIO_AOUT)
1050     add_shortcut("wasapi")
1051     set_callbacks(Open, Close)
1052 vlc_module_end()