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