]> git.sesse.net Git - vlc/blob - modules/audio_output/mmdevice.c
mmdevice: implement devices selection (fix #7855)
[vlc] / modules / audio_output / mmdevice.c
1 /*****************************************************************************
2  * mmdevice.c : Windows Multimedia Device API audio output plugin for VLC
3  *****************************************************************************
4  * Copyright (C) 2012 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 #undef _WIN32_WINNT
26 #define _WIN32_WINNT 0x600 /* Windows Vista */
27 #define INITGUID
28 #define COBJMACROS
29 #define CONST_VTABLE
30
31 #include <stdlib.h>
32 #include <assert.h>
33 #include <audiopolicy.h>
34 #include <mmdeviceapi.h>
35
36 DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd,
37    0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
38
39 #include <vlc_common.h>
40 #include <vlc_plugin.h>
41 #include <vlc_aout.h>
42 #include <vlc_charset.h>
43 #include "mmdevice.h"
44
45 DEFINE_GUID (GUID_VLC_AUD_OUT, 0x4533f59d, 0x59ee, 0x00c6,
46    0xad, 0xb2, 0xc6, 0x8b, 0x50, 0x1a, 0x66, 0x55);
47
48 static int TryEnterMTA(vlc_object_t *obj)
49 {
50     HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
51     if (unlikely(FAILED(hr)))
52     {
53         msg_Err (obj, "cannot initialize COM (error 0x%lx)", hr);
54         return -1;
55     }
56     return 0;
57 }
58 #define TryEnterMTA(o) TryEnterMTA(VLC_OBJECT(o))
59
60 static void EnterMTA(void)
61 {
62     HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
63     if (unlikely(FAILED(hr)))
64         abort();
65 }
66
67 static void LeaveMTA(void)
68 {
69     CoUninitialize();
70 }
71
72 struct aout_sys_t
73 {
74     audio_output_t *aout;
75     aout_stream_t *stream; /**< Underlying audio output stream */
76
77     IMMDeviceEnumerator *it; /**< Device enumerator, NULL when exiting */
78     /*TODO: IMMNotificationClient*/
79
80     IMMDevice *dev; /**< Selected output device, NULL if none */
81     IAudioSessionManager *manager; /**< Session for the output device */
82     struct IAudioSessionEvents session_events;
83     ISimpleAudioVolume *volume; /**< Volume setter */
84
85     LONG refs;
86     HANDLE device_changed; /**< Event to reset thread */
87     HANDLE device_ready; /**< Event when thread is reset */
88     vlc_thread_t thread; /**< Thread for audio session control */
89 };
90
91 /* NOTE: The Core Audio API documentation totally fails to specify the thread
92  * safety (or lack thereof) of the interfaces. This code takes the most
93  * restrictive assumption, no thread safety: The background thread (MMThread)
94  * only runs at specified times, namely between the device_ready and
95  * device_changed events (effectively, a thread barrier but only Windows 8
96  * provides thread barriers natively).
97  *
98  * The audio output owner (i.e. the audio output core) is responsible for
99  * serializing callbacks. This code only needs to be concerned with
100  * synchronization between the set of audio output callbacks, MMThread()
101  * and (trivially) the device and session notifications. */
102
103 static int vlc_FromHR(audio_output_t *aout, HRESULT hr)
104 {
105     /* Restart on unplug */
106     if (unlikely(hr == AUDCLNT_E_DEVICE_INVALIDATED))
107     {
108         vlc_value_t d;
109         aout_ChannelsRestart(VLC_OBJECT(aout), "audio-device", d, d, NULL);
110     }
111     return SUCCEEDED(hr) ? 0 : -1;
112 }
113
114 /*** VLC audio output callbacks ***/
115 static int TimeGet(audio_output_t *aout, mtime_t *restrict delay)
116 {
117     aout_sys_t *sys = aout->sys;
118     HRESULT hr;
119
120     EnterMTA();
121     hr = aout_stream_TimeGet(sys->stream, delay);
122     LeaveMTA();
123
124     return SUCCEEDED(hr) ? 0 : -1;
125 }
126
127 static void Play(audio_output_t *aout, block_t *block)
128 {
129     aout_sys_t *sys = aout->sys;
130     HRESULT hr;
131
132     EnterMTA();
133     hr = aout_stream_Play(sys->stream, block);
134     LeaveMTA();
135
136     vlc_FromHR(aout, hr);
137 }
138
139 static void Pause(audio_output_t *aout, bool paused, mtime_t date)
140 {
141     aout_sys_t *sys = aout->sys;
142     HRESULT hr;
143
144     EnterMTA();
145     hr = aout_stream_Pause(sys->stream, paused);
146     LeaveMTA();
147
148     vlc_FromHR(aout, hr);
149     (void) date;
150 }
151
152 static void Flush(audio_output_t *aout, bool wait)
153 {
154     aout_sys_t *sys = aout->sys;
155
156     EnterMTA();
157
158     if (wait)
159     {   /* Loosy drain emulation */
160         mtime_t delay;
161
162         if (SUCCEEDED(aout_stream_TimeGet(sys->stream, &delay)))
163             Sleep((delay / (CLOCK_FREQ / 1000)) + 1);
164     }
165     else
166         aout_stream_Flush(sys->stream);
167
168     LeaveMTA();
169
170 }
171
172 static int VolumeSet(audio_output_t *aout, float vol)
173 {
174     ISimpleAudioVolume *volume = aout->sys->volume;
175     if (volume == NULL)
176         return -1;
177
178     if (TryEnterMTA(aout))
179         return -1;
180
181     HRESULT hr = ISimpleAudioVolume_SetMasterVolume(volume, vol, NULL);
182     if (FAILED(hr))
183         msg_Err(aout, "cannot set volume (error 0x%lx)", hr);
184     LeaveMTA();
185
186     return FAILED(hr) ? -1 : 0;
187 }
188
189 static int MuteSet(audio_output_t *aout, bool mute)
190 {
191     ISimpleAudioVolume *volume = aout->sys->volume;
192     if (volume == NULL)
193         return -1;
194
195     if (TryEnterMTA(aout))
196         return -1;
197
198     HRESULT hr = ISimpleAudioVolume_SetMute(volume, mute ? TRUE : FALSE, NULL);
199     if (FAILED(hr))
200         msg_Err(aout, "cannot set volume (error 0x%lx)", hr);
201     LeaveMTA();
202
203     return FAILED(hr) ? -1 : 0;
204 }
205
206 /*** Audio session events ***/
207 static inline aout_sys_t *vlc_AudioSessionEvents_sys(IAudioSessionEvents *this)
208 {
209     return (void *)(((char *)this) - offsetof(aout_sys_t, session_events));
210 }
211
212 static STDMETHODIMP
213 vlc_AudioSessionEvents_QueryInterface(IAudioSessionEvents *this, REFIID riid,
214                                       void **ppv)
215 {
216     if (IsEqualIID(riid, &IID_IUnknown)
217      || IsEqualIID(riid, &IID_IAudioSessionEvents))
218     {
219         *ppv = this;
220         IUnknown_AddRef(this);
221         return S_OK;
222     }
223     else
224     {
225        *ppv = NULL;
226         return E_NOINTERFACE;
227     }
228 }
229
230 static STDMETHODIMP_(ULONG)
231 vlc_AudioSessionEvents_AddRef(IAudioSessionEvents *this)
232 {
233     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
234     return InterlockedIncrement(&sys->refs);
235 }
236
237 static STDMETHODIMP_(ULONG)
238 vlc_AudioSessionEvents_Release(IAudioSessionEvents *this)
239 {
240     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
241     return InterlockedDecrement(&sys->refs);
242 }
243
244 static STDMETHODIMP
245 vlc_AudioSessionEvents_OnDisplayNameChanged(IAudioSessionEvents *this,
246                                             LPCWSTR wname, LPCGUID ctx)
247 {
248     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
249     audio_output_t *aout = sys->aout;
250
251     msg_Dbg(aout, "display name changed: %ls", wname);
252     (void) ctx;
253     return S_OK;
254 }
255
256 static STDMETHODIMP
257 vlc_AudioSessionEvents_OnIconPathChanged(IAudioSessionEvents *this,
258                                          LPCWSTR wpath, LPCGUID ctx)
259 {
260     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
261     audio_output_t *aout = sys->aout;
262
263     msg_Dbg(aout, "icon path changed: %ls", wpath);
264     (void) ctx;
265     return S_OK;
266 }
267
268 static STDMETHODIMP
269 vlc_AudioSessionEvents_OnSimpleVolumeChanged(IAudioSessionEvents *this,
270                                              float vol, WINBOOL mute,
271                                              LPCGUID ctx)
272 {
273     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
274     audio_output_t *aout = sys->aout;
275
276     msg_Dbg(aout, "simple volume changed: %f, muting %sabled", vol,
277             mute ? "en" : "dis");
278     aout_VolumeReport(aout, vol);
279     aout_MuteReport(aout, mute == TRUE);
280     (void) ctx;
281     return S_OK;
282 }
283
284 static STDMETHODIMP
285 vlc_AudioSessionEvents_OnChannelVolumeChanged(IAudioSessionEvents *this,
286                                               DWORD count, float *vols,
287                                               DWORD changed, LPCGUID ctx)
288 {
289     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
290     audio_output_t *aout = sys->aout;
291
292     msg_Dbg(aout, "channel volume %lu of %lu changed: %f", changed, count,
293             vols[changed]);
294     (void) ctx;
295     return S_OK;
296 }
297
298 static STDMETHODIMP
299 vlc_AudioSessionEvents_OnGroupingParamChanged(IAudioSessionEvents *this,
300                                               LPCGUID param, LPCGUID ctx)
301
302 {
303     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
304     audio_output_t *aout = sys->aout;
305
306     msg_Dbg(aout, "grouping parameter changed");
307     (void) param;
308     (void) ctx;
309     return S_OK;
310 }
311
312 static STDMETHODIMP
313 vlc_AudioSessionEvents_OnStateChanged(IAudioSessionEvents *this,
314                                       AudioSessionState state)
315 {
316     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
317     audio_output_t *aout = sys->aout;
318
319     msg_Dbg(aout, "state changed: %d", state);
320     return S_OK;
321 }
322
323 static STDMETHODIMP
324 vlc_AudioSessionEvents_OnSessionDisconnected(IAudioSessionEvents *this,
325                                            AudioSessionDisconnectReason reason)
326 {
327     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
328     audio_output_t *aout = sys->aout;
329
330     switch (reason)
331     {
332         case DisconnectReasonDeviceRemoval:
333             msg_Warn(aout, "session disconnected: %s", "device removed");
334             break;
335         case DisconnectReasonServerShutdown:
336             msg_Err(aout, "session disconnected: %s", "service stopped");
337             return S_OK;
338         case DisconnectReasonFormatChanged:
339             msg_Warn(aout, "session disconnected: %s", "format changed");
340             break;
341         case DisconnectReasonSessionLogoff:
342             msg_Err(aout, "session disconnected: %s", "user logged off");
343             return S_OK;
344         case DisconnectReasonSessionDisconnected:
345             msg_Err(aout, "session disconnected: %s", "session disconnected");
346             return S_OK;
347         case DisconnectReasonExclusiveModeOverride:
348             msg_Err(aout, "session disconnected: %s", "stream overriden");
349             return S_OK;
350         default:
351             msg_Warn(aout, "session disconnected: unknown reason %d", reason);
352             return S_OK;
353     }
354     /* NOTE: audio decoder thread should get invalidated device and restart */
355     return S_OK;
356 }
357
358 static const struct IAudioSessionEventsVtbl vlc_AudioSessionEvents =
359 {
360     vlc_AudioSessionEvents_QueryInterface,
361     vlc_AudioSessionEvents_AddRef,
362     vlc_AudioSessionEvents_Release,
363
364     vlc_AudioSessionEvents_OnDisplayNameChanged,
365     vlc_AudioSessionEvents_OnIconPathChanged,
366     vlc_AudioSessionEvents_OnSimpleVolumeChanged,
367     vlc_AudioSessionEvents_OnChannelVolumeChanged,
368     vlc_AudioSessionEvents_OnGroupingParamChanged,
369     vlc_AudioSessionEvents_OnStateChanged,
370     vlc_AudioSessionEvents_OnSessionDisconnected,
371 };
372
373
374 /*** Initialization / deinitialization **/
375 static wchar_t *var_InheritWide(vlc_object_t *obj, const char *name)
376 {
377     char *v8 = var_InheritString(obj, name);
378     if (v8 == NULL)
379         return NULL;
380
381     wchar_t *v16 = ToWide(v8);
382     free(v8);
383     return v16;
384 }
385 #define var_InheritWide(o,n) var_InheritWide(VLC_OBJECT(o),n)
386
387 static void MMSession(audio_output_t *aout, aout_sys_t *sys)
388 {
389     IAudioSessionControl *control;
390     HRESULT hr;
391
392     /* Register session control */
393     if (sys->manager != NULL)
394     {
395         hr = IAudioSessionManager_GetSimpleAudioVolume(sys->manager,
396                                                        &GUID_VLC_AUD_OUT,
397                                                        FALSE, &sys->volume);
398         if (FAILED(hr))
399             msg_Err(aout, "cannot get simple volume (error 0x%lx)", hr);
400
401         hr = IAudioSessionManager_GetAudioSessionControl(sys->manager,
402                                                          &GUID_VLC_AUD_OUT, 0,
403                                                          &control);
404         if (FAILED(hr))
405             msg_Err(aout, "cannot get session control (error 0x%lx)", hr);
406     }
407     else
408     {
409         sys->volume = NULL;
410         control = NULL;
411     }
412
413     if (control != NULL)
414     {
415         wchar_t *ua = var_InheritWide(aout, "user-agent");
416         IAudioSessionControl_SetDisplayName(control, ua, NULL);
417         free(ua);
418
419         IAudioSessionControl_RegisterAudioSessionNotification(control,
420                                                          &sys->session_events);
421     }
422
423     if (sys->volume != NULL)
424     {   /* Get current values (_after_ changes notification registration) */
425         BOOL mute;
426         float level;
427
428         hr = ISimpleAudioVolume_GetMute(sys->volume, &mute);
429         if (FAILED(hr))
430             msg_Err(aout, "cannot get mute (error 0x%lx)", hr);
431         else
432             aout_MuteReport(aout, mute != FALSE);
433
434         hr = ISimpleAudioVolume_GetMasterVolume(sys->volume, &level);
435         if (FAILED(hr))
436             msg_Err(aout, "cannot get mute (error 0x%lx)", hr);
437         else
438             aout_VolumeReport(aout, level);
439     }
440
441     SetEvent(sys->device_ready);
442     /* Wait until device change or exit */
443     WaitForSingleObject(sys->device_changed, INFINITE);
444
445     /* Deregister session control */
446     if (control != NULL)
447     {
448         IAudioSessionControl_UnregisterAudioSessionNotification(control,
449                                                          &sys->session_events);
450         IAudioSessionControl_Release(control);
451     }
452
453     if (sys->volume != NULL)
454         ISimpleAudioVolume_Release(sys->volume);
455 }
456
457 /** MMDevice audio output thread.
458  * This thread takes cares of the audio session control. Inconveniently enough,
459  * the audio session control interface must:
460  *  - be created and destroyed from the same thread, and
461  *  - survive across VLC audio output calls.
462  * The only way to reconcile both requirements is a custom thread.
463  * The thread also ensure that the COM Multi-Thread Apartment is continuously
464  * referenced so that MMDevice objects are not destroyed early.
465  */
466 static void *MMThread(void *data)
467 {
468     audio_output_t *aout = data;
469     aout_sys_t *sys = aout->sys;
470
471     EnterMTA();
472     while (sys->it != NULL)
473         MMSession(aout, sys);
474     LeaveMTA();
475     return NULL;
476 }
477
478 /*** Audio devices ***/
479 static int DevicesEnum(audio_output_t *aout, char ***idp, char ***namep)
480 {
481     aout_sys_t *sys = aout->sys;
482     HRESULT hr;
483     IMMDeviceCollection *devs;
484
485     hr = IMMDeviceEnumerator_EnumAudioEndpoints(sys->it, eRender,
486                                                 DEVICE_STATE_ACTIVE, &devs);
487     if (FAILED(hr))
488     {
489         msg_Warn(aout, "cannot enumerate audio endpoints (error 0x%lx)", hr);
490         return -1;
491     }
492
493     UINT count;
494     hr = IMMDeviceCollection_GetCount(devs, &count);
495     if (FAILED(hr))
496     {
497         msg_Warn(aout, "cannot count audio endpoints (error 0x%lx)", hr);
498         count = 0;
499     }
500     else
501         msg_Dbg(aout, "Available Windows Audio devices:");
502
503     char **ids = xmalloc (count * sizeof (*ids));
504     char **names = xmalloc (count * sizeof (*names));
505     unsigned n = 0;
506
507     for (UINT i = 0; i < count; i++)
508     {
509         IMMDevice *dev;
510
511         hr = IMMDeviceCollection_Item(devs, i, &dev);
512         if (FAILED(hr))
513             continue;
514
515         /* Unique device ID */
516         LPWSTR devid;
517         hr = IMMDevice_GetId(dev, &devid);
518         if (FAILED(hr))
519         {
520             IMMDevice_Release(dev);
521             continue;
522         }
523         ids[n] = FromWide(devid);
524         names[n] = NULL;
525         CoTaskMemFree(devid);
526
527         /* User-readable device name */
528         IPropertyStore *props;
529         hr = IMMDevice_OpenPropertyStore(dev, STGM_READ, &props);
530         if (SUCCEEDED(hr))
531         {
532             PROPVARIANT v;
533
534             PropVariantInit(&v);
535             hr = IPropertyStore_GetValue(props, &PKEY_Device_FriendlyName, &v);
536             if (SUCCEEDED(hr))
537                 names[n] = FromWide(v.pwszVal);
538             PropVariantClear(&v);
539             IPropertyStore_Release(props);
540         }
541         IMMDevice_Release(dev);
542         if (names[n] == NULL)
543             names[n] = xstrdup(ids[n]);
544
545         msg_Dbg(aout, "%s (%s)", ids[n], names[n]);
546         n++;
547     }
548     IMMDeviceCollection_Release(devs);
549
550     *idp = ids;
551     *namep = names;
552     return n;
553 }
554
555 /**
556  * Opens the selected audio output device.
557  */
558 static HRESULT OpenDevice(audio_output_t *aout, const char *devid)
559 {
560     aout_sys_t *sys = aout->sys;
561     HRESULT hr;
562
563     assert(sys->dev == NULL);
564
565     if (devid != NULL) /* Device selected explicitly */
566     {
567         msg_Dbg(aout, "using selected device %s", devid);
568
569         wchar_t *wdevid = ToWide(devid);
570         if (likely(wdevid != NULL))
571         {
572             hr = IMMDeviceEnumerator_GetDevice(sys->it, wdevid, &sys->dev);
573             free (wdevid);
574         }
575         else
576             hr = E_OUTOFMEMORY;
577     }
578     else /* Default device selected by policy */
579     {
580         msg_Dbg(aout, "using default device");
581         hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(sys->it, eRender,
582                                                          eConsole, &sys->dev);
583     }
584     assert(sys->manager == NULL);
585     if (FAILED(hr))
586     {
587         msg_Err(aout, "cannot get device (error 0x%lx)", hr);
588         goto out;
589     }
590
591     /* Create session manager (for controls even w/o active audio client) */
592     void *pv;
593     hr = IMMDevice_Activate(sys->dev, &IID_IAudioSessionManager,
594                             CLSCTX_ALL, NULL, &pv);
595     if (FAILED(hr))
596         msg_Err(aout, "cannot activate session manager (error 0x%lx)", hr);
597     else
598         sys->manager = pv;
599
600     /* Report actual device */
601     LPWSTR wdevid;
602     hr = IMMDevice_GetId(sys->dev, &wdevid);
603     if (SUCCEEDED(hr))
604     {
605         char *id = FromWide(wdevid);
606         CoTaskMemFree(wdevid);
607         if (likely(id != NULL))
608         {
609             aout_DeviceReport(aout, id);
610             free(id);
611         }
612     }
613 out:
614     SetEvent(sys->device_changed);
615     WaitForSingleObject(sys->device_ready, INFINITE);
616     return hr;
617 }
618
619 /**
620  * Closes the opened audio output device (if any).
621  */
622 static void CloseDevice(audio_output_t *aout)
623 {
624     aout_sys_t *sys = aout->sys;
625
626     assert(sys->dev != NULL);
627
628     if (sys->manager != NULL)
629     {
630         IAudioSessionManager_Release(sys->manager);
631         sys->manager = NULL;
632     }
633
634     IMMDevice_Release(sys->dev);
635     sys->dev = NULL;
636 }
637
638 static int DeviceSelect(audio_output_t *aout, const char *id)
639 {
640     aout_sys_t *sys = aout->sys;
641     HRESULT hr;
642
643     if (TryEnterMTA(aout))
644         return -1;
645
646     if (sys->dev != NULL)
647         CloseDevice(aout);
648
649     hr = OpenDevice(aout, id);
650     while (hr == AUDCLNT_E_DEVICE_INVALIDATED)
651         hr = OpenDevice(aout, NULL); /* Fallback to default device */
652     LeaveMTA();
653
654     if (sys->stream != NULL)
655     {
656         vlc_value_t d;
657         /* Request restart of stream with the new device */
658         aout_ChannelsRestart(VLC_OBJECT(aout), "audio-device", d, d, NULL);
659     }
660     return FAILED(hr) ? -1 : 0;
661 }
662
663 /**
664  * Callback for aout_stream_t to create a stream on the device.
665  * This can instantiate an IAudioClient or IDirectSound(8) object.
666  */
667 static HRESULT ActivateDevice(void *opaque, REFIID iid, PROPVARIANT *actparms,
668                               void **restrict pv)
669 {
670     IMMDevice *dev = opaque;
671
672     return IMMDevice_Activate(dev, iid, CLSCTX_ALL, actparms, pv);
673 }
674
675 static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
676 {
677     aout_sys_t *sys = aout->sys;
678     HRESULT hr;
679
680     assert (sys->stream == NULL);
681     /* Open the default device if required (to deal with restarts) */
682     if (sys->dev == NULL && FAILED(DeviceSelect(aout, NULL)))
683         return -1;
684
685     aout_stream_t *s = vlc_object_create(aout, sizeof (*s));
686     if (unlikely(s == NULL))
687         return -1;
688
689     s->owner.device = sys->dev;
690     s->owner.activate = ActivateDevice;
691
692     EnterMTA();
693     hr = aout_stream_Start(s, fmt, &GUID_VLC_AUD_OUT);
694     if (SUCCEEDED(hr))
695         sys->stream = s;
696     else
697         vlc_object_release(s);
698     LeaveMTA();
699
700     return vlc_FromHR(aout, hr);
701 }
702
703 static void Stop(audio_output_t *aout)
704 {
705     aout_sys_t *sys = aout->sys;
706
707     assert (sys->stream != NULL);
708
709     EnterMTA();
710     aout_stream_Stop(sys->stream);
711     LeaveMTA();
712
713     vlc_object_release(sys->stream);
714     sys->stream = NULL;
715 }
716
717 static int Open(vlc_object_t *obj)
718 {
719     audio_output_t *aout = (audio_output_t *)obj;
720     void *pv;
721     HRESULT hr;
722
723     if (!aout->b_force && var_InheritBool(aout, "spdif"))
724         /* Fallback to other plugin until pass-through is implemented */
725         return VLC_EGENERIC;
726
727     aout_sys_t *sys = malloc(sizeof (*sys));
728     if (unlikely(sys == NULL))
729         return VLC_ENOMEM;
730
731     aout->sys = sys;
732     sys->aout = aout;
733     sys->stream = NULL;
734     sys->it = NULL;
735     sys->dev = NULL;
736     sys->manager = NULL;
737     sys->session_events.lpVtbl = &vlc_AudioSessionEvents;
738     sys->refs = 1;
739
740     sys->device_changed = CreateEvent(NULL, FALSE, FALSE, NULL);
741     sys->device_ready = CreateEvent(NULL, FALSE, FALSE, NULL);
742     if (unlikely(sys->device_changed == NULL || sys->device_ready == NULL))
743         goto error;
744
745     /* Initialize MMDevice API */
746     if (TryEnterMTA(aout))
747         goto error;
748
749     hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
750                           &IID_IMMDeviceEnumerator, &pv);
751     if (FAILED(hr))
752     {
753         LeaveMTA();
754         msg_Dbg(aout, "cannot create device enumerator (error 0x%lx)", hr);
755         goto error;
756     }
757     sys->it = pv;
758
759     if (vlc_clone(&sys->thread, MMThread, aout, VLC_THREAD_PRIORITY_LOW))
760         goto error;
761     WaitForSingleObject(sys->device_ready, INFINITE);
762
763     DeviceSelect(aout, NULL); /* Get a device to start with */
764     LeaveMTA(); /* leave MTA after thread has entered MTA */
765
766     aout->start = Start;
767     aout->stop = Stop;
768     aout->time_get = TimeGet;
769     aout->play = Play;
770     aout->pause = Pause;
771     aout->flush = Flush;
772     aout->volume_set = VolumeSet;
773     aout->mute_set = MuteSet;
774     aout->device_enum = DevicesEnum;
775     aout->device_select = DeviceSelect;
776     return VLC_SUCCESS;
777
778 error:
779     if (sys->it != NULL)
780     {
781         IMMDeviceEnumerator_Release(sys->it);
782         LeaveMTA();
783     }
784     if (sys->device_ready != NULL)
785         CloseHandle(sys->device_ready);
786     if (sys->device_changed != NULL)
787         CloseHandle(sys->device_changed);
788     free(sys);
789     return VLC_EGENERIC;
790 }
791
792 static void Close(vlc_object_t *obj)
793 {
794     audio_output_t *aout = (audio_output_t *)obj;
795     aout_sys_t *sys = aout->sys;
796
797     EnterMTA(); /* enter MTA before thread leaves MTA */
798     if (sys->dev != NULL)
799         CloseDevice(aout);
800
801     IMMDeviceEnumerator_Release(sys->it);
802     sys->it = NULL;
803
804     SetEvent(sys->device_changed);
805     vlc_join(sys->thread, NULL);
806     LeaveMTA();
807
808     CloseHandle(sys->device_ready);
809     CloseHandle(sys->device_changed);
810     free(sys);
811 }
812
813 vlc_module_begin()
814     set_shortname("MMDevice")
815     set_description(N_("Windows Multimedia Device output"))
816     set_capability("audio output", 150)
817     set_category(CAT_AUDIO)
818     set_subcategory(SUBCAT_AUDIO_AOUT)
819     add_shortcut("wasapi")
820     set_callbacks(Open, Close)
821 vlc_module_end()