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