]> git.sesse.net Git - vlc/blob - modules/audio_output/wasapi.c
1b001cde4da1b39b4e5a826f4374f6936b1c2335
[vlc] / modules / audio_output / wasapi.c
1 /*****************************************************************************
2  * wasapi.c : Windows Audio Session API 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 #define INITGUID
26 #define COBJMACROS
27 #define CONST_VTABLE
28
29 #include <stdlib.h>
30 #include <assert.h>
31 #include <audioclient.h>
32 #include <audiopolicy.h>
33 #include <mmdeviceapi.h>
34
35 #include <vlc_common.h>
36 #include <vlc_plugin.h>
37 #include <vlc_aout.h>
38 #include <vlc_charset.h>
39
40 DEFINE_GUID (GUID_VLC_AUD_OUT, 0x4533f59d, 0x59ee, 0x00c6,
41    0xad, 0xb2, 0xc6, 0x8b, 0x50, 0x1a, 0x66, 0x55);
42
43 DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd,
44    0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
45
46 static int Open(vlc_object_t *);
47 static void Close(vlc_object_t *);
48
49 vlc_module_begin()
50     set_shortname("WASAPI")
51     set_description(N_("Windows Audio Session output") )
52     set_capability("audio output", 150)
53     set_category(CAT_AUDIO)
54     set_subcategory(SUBCAT_AUDIO_AOUT)
55     add_shortcut("was", "audioclient")
56     set_callbacks(Open, Close)
57 vlc_module_end()
58
59 static LARGE_INTEGER freq; /* performance counters frequency */
60
61 BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID); /* avoid warning */
62
63 BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, LPVOID reserved)
64 {
65     (void) dll;
66     (void) reserved;
67
68     switch (reason)
69     {
70         case DLL_PROCESS_ATTACH:
71             if (!QueryPerformanceFrequency(&freq))
72                 return FALSE;
73             break;
74     }
75     return TRUE;
76 }
77
78 static UINT64 GetQPC(void)
79 {
80     LARGE_INTEGER counter;
81
82     if (!QueryPerformanceCounter(&counter))
83         abort();
84
85     lldiv_t d = lldiv(counter.QuadPart, freq.QuadPart);
86     return (d.quot * 10000000) + ((d.rem * 10000000) / freq.QuadPart);
87 }
88
89 static int TryEnter(vlc_object_t *obj)
90 {
91     HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
92     if (unlikely(FAILED(hr)))
93     {
94         msg_Err (obj, "cannot initialize COM (error 0x%lx)", hr);
95         return -1;
96     }
97     return 0;
98 }
99 #define TryEnter(o) TryEnter(VLC_OBJECT(o))
100
101 static void Enter(void)
102 {
103     HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
104     if (unlikely(FAILED(hr)))
105         abort();
106 }
107
108 static void Leave(void)
109 {
110     CoUninitialize();
111 }
112
113 struct aout_sys_t
114 {
115     audio_output_t *aout;
116     IMMDeviceEnumerator *it;
117     IAudioClient *client;
118     IAudioRenderClient *render;
119     IAudioClock *clock;
120
121     IAudioSessionControl *control;
122     struct IAudioSessionEvents events;
123     LONG refs;
124
125     uint8_t chans_table[AOUT_CHAN_MAX];
126     uint8_t chans_to_reorder;
127     uint8_t bits; /**< Bits per sample */
128     unsigned rate; /**< Sample rate */
129     unsigned bytes_per_frame;
130     UINT32 frames; /**< Total buffer size (frames) */
131
132     HANDLE ready; /**< Semaphore from MTA thread */
133     HANDLE done; /**< Semaphore to MTA thread */
134 };
135
136
137 /*** VLC audio output callbacks ***/
138 static int TimeGet(audio_output_t *aout, mtime_t *restrict pts)
139 {
140     aout_sys_t *sys = aout->sys;
141     UINT64 pos, qpcpos;
142     HRESULT hr;
143
144     if (sys->clock == NULL)
145         return -1;
146
147     Enter();
148     hr = IAudioClock_GetPosition(sys->clock, &pos, &qpcpos);
149     Leave();
150     if (FAILED(hr))
151     {
152         msg_Err(aout, "cannot get position (error 0x%lx)", hr);
153         return -1;
154     }
155
156     if (pos == 0)
157     {
158         msg_Dbg(aout, "cannot compute position: still propagating buffers");
159         return -1;
160     }
161
162     mtime_t delay =  ((GetQPC() - qpcpos) / (10000000 / CLOCK_FREQ));
163     static_assert((10000000 % CLOCK_FREQ) == 0, "Frequency conversion broken");
164
165     *pts = mdate() + delay;
166     return 0;
167 }
168
169 static void Play(audio_output_t *aout, block_t *block)
170 {
171     aout_sys_t *sys = aout->sys;
172     HRESULT hr = S_OK;
173
174     if (sys->chans_to_reorder)
175         aout_ChannelReorder(block->p_buffer, block->i_buffer,
176                           sys->chans_to_reorder, sys->chans_table, sys->bits);
177
178     Enter();
179     for (;;)
180     {
181         UINT32 frames;
182         hr = IAudioClient_GetCurrentPadding(sys->client, &frames);
183         if (FAILED(hr))
184         {
185             msg_Err(aout, "cannot get current padding (error 0x%lx)", hr);
186             break;
187         }
188
189         assert(frames <= sys->frames);
190         frames = sys->frames - frames;
191         if (frames > block->i_nb_samples)
192             frames = block->i_nb_samples;
193
194         BYTE *dst;
195         hr = IAudioRenderClient_GetBuffer(sys->render, frames, &dst);
196         if (FAILED(hr))
197         {
198             msg_Err(aout, "cannot get buffer (error 0x%lx)", hr);
199             break;
200         }
201
202         const size_t copy = frames * sys->bytes_per_frame;
203
204         memcpy(dst, block->p_buffer, copy);
205         hr = IAudioRenderClient_ReleaseBuffer(sys->render, frames, 0);
206         if (FAILED(hr))
207         {
208             msg_Err(aout, "cannot release buffer (error 0x%lx)", hr);
209             break;
210         }
211         IAudioClient_Start(sys->client);
212
213         block->p_buffer += copy;
214         block->i_buffer -= copy;
215         block->i_nb_samples -= frames;
216         if (block->i_nb_samples == 0)
217             break; /* done */
218
219         /* Out of buffer space, sleep */
220         msleep(AOUT_MIN_PREPARE_TIME
221              + block->i_nb_samples * CLOCK_FREQ / sys->rate);
222     }
223
224     Leave();
225     block_Release(block);
226
227     /* Restart on unplug */
228     if (unlikely(hr == AUDCLNT_E_DEVICE_INVALIDATED))
229         var_TriggerCallback(aout, "audio-device");
230 }
231
232 static void Pause(audio_output_t *aout, bool paused, mtime_t date)
233 {
234     aout_sys_t *sys = aout->sys;
235     HRESULT hr;
236
237     Enter();
238     if (paused)
239         hr = IAudioClient_Stop(sys->client);
240     else
241         hr = IAudioClient_Start(sys->client);
242     if (FAILED(hr))
243         msg_Warn(aout, "cannot %s stream (error 0x%lx)",
244                  paused ? "stop" : "start", hr);
245     Leave();
246     (void) date;
247 }
248
249 static void Flush(audio_output_t *aout, bool wait)
250 {
251     aout_sys_t *sys = aout->sys;
252     HRESULT hr;
253
254     if (wait)
255         return; /* Drain not implemented */
256
257     Enter();
258     IAudioClient_Stop(sys->client);
259     hr = IAudioClient_Reset(sys->client);
260     if (FAILED(hr))
261         msg_Warn(aout, "cannot reset stream (error 0x%lx)", hr);
262     Leave();
263 }
264
265 static int SimpleVolumeSet(audio_output_t *aout, float vol)
266 {
267     ISimpleAudioVolume *simple;
268     HRESULT hr;
269
270     if (TryEnter(aout))
271         return -1;
272     hr = IAudioClient_GetService(aout->sys->client, &IID_ISimpleAudioVolume,
273                                  (void **)&simple);
274     if (SUCCEEDED(hr))
275     {
276         hr = ISimpleAudioVolume_SetMasterVolume(simple, vol, NULL);
277         ISimpleAudioVolume_Release(simple);
278     }
279     Leave();
280
281     if (FAILED(hr))
282     {
283         msg_Err(aout, "cannot set volume (error 0x%lx)", hr);
284         return -1;
285     }
286     return 0;
287 }
288
289 static int SimpleMuteSet(audio_output_t *aout, bool mute)
290 {
291     ISimpleAudioVolume *simple;
292     HRESULT hr;
293
294     if (TryEnter(aout))
295         return -1;
296     hr = IAudioClient_GetService(aout->sys->client, &IID_ISimpleAudioVolume,
297                                  (void **)&simple);
298     if (SUCCEEDED(hr))
299     {
300         hr = ISimpleAudioVolume_SetMute(simple, mute, NULL);
301         ISimpleAudioVolume_Release(simple);
302     }
303     Leave();
304
305     if (FAILED(hr))
306     {
307         msg_Err(aout, "cannot set mute (error 0x%lx)", hr);
308         return -1;
309     }
310     return 0;
311 }
312
313
314 /*** Audio devices ***/
315 static int DeviceChanged(vlc_object_t *obj, const char *varname,
316                          vlc_value_t prev, vlc_value_t cur, void *data)
317 {
318     aout_ChannelsRestart(obj, varname, prev, cur, data);
319
320     if (!var_Type (obj, "wasapi-audio-device"))
321         var_Create (obj, "wasapi-audio-device", VLC_VAR_STRING);
322     var_SetString (obj, "wasapi-audio-device", cur.psz_string);
323     return VLC_SUCCESS;
324 }
325
326 static void GetDevices(vlc_object_t *obj, IMMDeviceEnumerator *it)
327 {
328     HRESULT hr;
329     vlc_value_t val, text;
330
331     var_Create (obj, "audio-device", VLC_VAR_STRING | VLC_VAR_HASCHOICE);
332     text.psz_string = _("Audio Device");
333     var_Change (obj, "audio-device", VLC_VAR_SETTEXT, &text, NULL);
334
335     IMMDeviceCollection *devs;
336     hr = IMMDeviceEnumerator_EnumAudioEndpoints(it, eRender,
337                                                 DEVICE_STATE_ACTIVE, &devs);
338     if (FAILED(hr))
339     {
340         msg_Warn (obj, "cannot enumerate audio endpoints (error 0x%lx)", hr);
341         return;
342     }
343
344     UINT n;
345     hr = IMMDeviceCollection_GetCount(devs, &n);
346     if (FAILED(hr))
347     {
348         msg_Warn (obj, "cannot count audio endpoints (error 0x%lx)", hr);
349         n = 0;
350     }
351     else
352         msg_Dbg(obj, "Available Windows Audio devices:");
353
354     while (n > 0)
355     {
356         IMMDevice *dev;
357
358         hr = IMMDeviceCollection_Item(devs, --n, &dev);
359         if (FAILED(hr))
360             continue;
361
362         /* Unique device ID */
363         LPWSTR devid;
364         hr = IMMDevice_GetId(dev, &devid);
365         if (FAILED(hr))
366         {
367             IMMDevice_Release(dev);
368             continue;
369         }
370         val.psz_string = FromWide(devid);
371         CoTaskMemFree(devid);
372         text.psz_string = val.psz_string;
373
374         /* User-readable device name */
375         IPropertyStore *props;
376         hr = IMMDevice_OpenPropertyStore(dev, STGM_READ, &props);
377         if (SUCCEEDED(hr))
378         {
379             PROPVARIANT v;
380
381             PropVariantInit(&v);
382             hr = IPropertyStore_GetValue(props, &PKEY_Device_FriendlyName, &v);
383             if (SUCCEEDED(hr))
384                 text.psz_string = FromWide(v.pwszVal);
385             PropVariantClear(&v);
386             IPropertyStore_Release(props);
387         }
388         IMMDevice_Release(dev);
389
390         msg_Dbg(obj, "%s (%s)", val.psz_string, text.psz_string);
391         var_Change(obj, "audio-device", VLC_VAR_ADDCHOICE, &val, &text);
392         if (likely(text.psz_string != val.psz_string))
393             free(text.psz_string);
394         free(val.psz_string);
395     }
396     IMMDeviceCollection_Release(devs);
397 }
398
399
400 /*** Audio session events ***/
401 static inline aout_sys_t *vlc_AudioSessionEvents_sys(IAudioSessionEvents *this)
402 {
403     return (aout_sys_t *)(((char *)this) - offsetof(aout_sys_t, events));
404 }
405
406 static STDMETHODIMP
407 vlc_AudioSessionEvents_QueryInterface(IAudioSessionEvents *this, REFIID riid,
408                                       void **ppv)
409 {
410     if (IsEqualIID(riid, &IID_IUnknown)
411      || IsEqualIID(riid, &IID_IAudioSessionEvents))
412     {
413         *ppv = this;
414         IUnknown_AddRef(this);
415         return S_OK;
416     }
417     else
418     {
419        *ppv = NULL;
420         return E_NOINTERFACE;
421     }
422 }
423
424 static STDMETHODIMP_(ULONG)
425 vlc_AudioSessionEvents_AddRef(IAudioSessionEvents *this)
426 {
427     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
428     return InterlockedIncrement(&sys->refs);
429 }
430
431 static STDMETHODIMP_(ULONG)
432 vlc_AudioSessionEvents_Release(IAudioSessionEvents *this)
433 {
434     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
435     return InterlockedDecrement(&sys->refs);
436 }
437
438 static STDMETHODIMP
439 vlc_AudioSessionEvents_OnDisplayNameChanged(IAudioSessionEvents *this,
440                                             LPCWSTR wname, LPCGUID ctx)
441 {
442     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
443     audio_output_t *aout = sys->aout;
444
445     msg_Dbg(aout, "display name changed: %ls", wname);
446     (void) ctx;
447     return S_OK;
448 }
449
450 static STDMETHODIMP
451 vlc_AudioSessionEvents_OnIconPathChanged(IAudioSessionEvents *this,
452                                          LPCWSTR wpath, LPCGUID ctx)
453 {
454     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
455     audio_output_t *aout = sys->aout;
456
457     msg_Dbg(aout, "icon path changed: %ls", wpath);
458     (void) ctx;
459     return S_OK;
460 }
461
462 static STDMETHODIMP
463 vlc_AudioSessionEvents_OnSimpleVolumeChanged(IAudioSessionEvents *this, float vol,
464                                              WINBOOL mute, LPCGUID ctx)
465 {
466     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
467     audio_output_t *aout = sys->aout;
468
469     msg_Dbg(aout, "simple volume changed: %f, muting %sabled", vol,
470             mute ? "en" : "dis");
471     aout_VolumeReport(aout, vol);
472     aout_MuteReport(aout, mute == TRUE);
473     (void) ctx;
474     return S_OK;
475 }
476
477 static STDMETHODIMP
478 vlc_AudioSessionEvents_OnChannelVolumeChanged(IAudioSessionEvents *this,
479                                               DWORD count, float *vols,
480                                               DWORD changed, LPCGUID ctx)
481 {
482     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
483     audio_output_t *aout = sys->aout;
484
485     msg_Dbg(aout, "channel volume %lu of %lu changed: %f", changed, count,
486             vols[changed]);
487     (void) ctx;
488     return S_OK;
489 }
490
491 static STDMETHODIMP
492 vlc_AudioSessionEvents_OnGroupingParamChanged(IAudioSessionEvents *this,
493                                               LPCGUID param, LPCGUID ctx)
494
495 {
496     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
497     audio_output_t *aout = sys->aout;
498
499     msg_Dbg(aout, "grouping parameter changed");
500     (void) param;
501     (void) ctx;
502     return S_OK;
503 }
504
505 static STDMETHODIMP
506 vlc_AudioSessionEvents_OnStateChanged(IAudioSessionEvents *this,
507                                       AudioSessionState state)
508 {
509     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
510     audio_output_t *aout = sys->aout;
511
512     msg_Dbg(aout, "state changed: %d", state);
513     return S_OK;
514 }
515
516 static STDMETHODIMP
517 vlc_AudioSessionEvents_OnSessionDisconnected(IAudioSessionEvents *this,
518                                              AudioSessionDisconnectReason reason)
519 {
520     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
521     audio_output_t *aout = sys->aout;
522
523     msg_Dbg(aout, "session disconnected: reason %d", reason);
524     return S_OK;
525 }
526
527 static const struct IAudioSessionEventsVtbl vlc_AudioSessionEvents =
528 {
529     vlc_AudioSessionEvents_QueryInterface,
530     vlc_AudioSessionEvents_AddRef,
531     vlc_AudioSessionEvents_Release,
532
533     vlc_AudioSessionEvents_OnDisplayNameChanged,
534     vlc_AudioSessionEvents_OnIconPathChanged,
535     vlc_AudioSessionEvents_OnSimpleVolumeChanged,
536     vlc_AudioSessionEvents_OnChannelVolumeChanged,
537     vlc_AudioSessionEvents_OnGroupingParamChanged,
538     vlc_AudioSessionEvents_OnStateChanged,
539     vlc_AudioSessionEvents_OnSessionDisconnected,
540 };
541
542
543 /*** Initialization / deinitialization **/
544 static const uint32_t chans_out[] = {
545     SPEAKER_FRONT_LEFT, SPEAKER_FRONT_RIGHT,
546     SPEAKER_FRONT_CENTER, SPEAKER_LOW_FREQUENCY,
547     SPEAKER_BACK_LEFT, SPEAKER_BACK_RIGHT, SPEAKER_BACK_CENTER,
548     SPEAKER_SIDE_LEFT, SPEAKER_SIDE_RIGHT, 0
549 };
550 static const uint32_t chans_in[] = {
551     SPEAKER_FRONT_LEFT, SPEAKER_FRONT_RIGHT,
552     SPEAKER_SIDE_LEFT, SPEAKER_SIDE_RIGHT,
553     SPEAKER_BACK_LEFT, SPEAKER_BACK_RIGHT, SPEAKER_BACK_CENTER,
554     SPEAKER_FRONT_CENTER, SPEAKER_LOW_FREQUENCY, 0
555 };
556
557 static void vlc_ToWave(WAVEFORMATEXTENSIBLE *restrict wf,
558                        audio_sample_format_t *restrict audio)
559 {
560     switch (audio->i_format)
561     {
562         case VLC_CODEC_FL64:
563             audio->i_format = VLC_CODEC_FL32;
564         case VLC_CODEC_FL32:
565             wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
566             break;
567
568         case VLC_CODEC_S8:
569         case VLC_CODEC_U8:
570             audio->i_format = VLC_CODEC_S16N;
571         case VLC_CODEC_S16N:
572             wf->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
573             break;
574
575         default:
576             audio->i_format = VLC_CODEC_FL32;
577             wf->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
578             break;
579     }
580     aout_FormatPrepare (audio);
581
582     wf->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
583     wf->Format.nChannels = audio->i_channels;
584     wf->Format.nSamplesPerSec = audio->i_rate;
585     wf->Format.nAvgBytesPerSec = audio->i_bytes_per_frame * audio->i_rate;
586     wf->Format.nBlockAlign = audio->i_bytes_per_frame;
587     wf->Format.wBitsPerSample = audio->i_bitspersample;
588     wf->Format.cbSize = sizeof (*wf) - sizeof (wf->Format);
589
590     wf->Samples.wValidBitsPerSample = audio->i_bitspersample;
591
592     wf->dwChannelMask = 0;
593     for (unsigned i = 0; pi_vlc_chan_order_wg4[i]; i++)
594         if (audio->i_physical_channels & pi_vlc_chan_order_wg4[i])
595             wf->dwChannelMask |= chans_in[i];
596 }
597
598 static int vlc_FromWave(const WAVEFORMATEX *restrict wf,
599                         audio_sample_format_t *restrict audio)
600 {
601     audio->i_rate = wf->nSamplesPerSec;
602
603     if (wf->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
604     {
605         const WAVEFORMATEXTENSIBLE *wfe = (void *)wf;
606
607         audio->i_physical_channels = 0;
608         for (unsigned i = 0; chans_in[i]; i++)
609             if (wfe->dwChannelMask & chans_in[i])
610                 audio->i_physical_channels |= pi_vlc_chan_order_wg4[i];
611     }
612
613     audio->i_original_channels = audio->i_physical_channels;
614     aout_FormatPrepare (audio);
615
616     if (wf->nChannels != audio->i_channels)
617         return -1;
618     return 0;
619 }
620
621 static wchar_t *var_InheritWide(vlc_object_t *obj, const char *name)
622 {
623     char *v8 = var_InheritString(obj, name);
624     if (v8 == NULL)
625         return NULL;
626
627     wchar_t *v16 = ToWide(v8);
628     free(v8);
629     return v16;
630 }
631 #define var_InheritWide(o,n) var_InheritWide(VLC_OBJECT(o),n)
632
633 static int var_SetWide(vlc_object_t *obj, const char *name, const wchar_t *val)
634 {
635     char *str = FromWide(val);
636     if (unlikely(str == NULL))
637         return VLC_ENOMEM;
638
639     int ret = var_SetString(obj, name, str);
640     free(str);
641     return ret;
642 }
643 #define var_SetWide(o,n,v) var_SetWide(VLC_OBJECT(o),n,v)
644
645 /* Dummy thread to create and release COM interfaces when needed. */
646 static void MTAThread(void *data)
647 {
648     audio_output_t *aout = data;
649     aout_sys_t *sys = aout->sys;
650     HRESULT hr;
651
652     Enter();
653
654     hr = IAudioClient_GetService(sys->client, &IID_IAudioRenderClient,
655                                  (void **)&sys->render);
656     if (FAILED(hr))
657     {
658         msg_Err(aout, "cannot get audio render service (error 0x%lx)", hr);
659         goto fail;
660     }
661
662     hr = IAudioClient_GetService(sys->client, &IID_IAudioClock,
663                                  (void **)&sys->clock);
664     if (FAILED(hr))
665         msg_Warn(aout, "cannot get audio clock (error 0x%lx)", hr);
666
667     hr = IAudioClient_GetService(sys->client, &IID_IAudioSessionControl,
668                                  (void **)&sys->control);
669     if (FAILED(hr))
670         msg_Warn(aout, "cannot get audio session control (error 0x%lx)", hr);
671     else
672     {
673         wchar_t *ua = var_InheritWide(aout, "user-agent");
674         IAudioSessionControl_SetDisplayName(sys->control, ua, NULL);
675         free(ua);
676     }
677
678     /* do nothing until the audio session terminates */
679     ReleaseSemaphore(sys->ready, 1, NULL);
680     WaitForSingleObject(sys->done, INFINITE);
681
682     if (sys->control != NULL)
683         IAudioSessionControl_Release(sys->control);
684     if (sys->clock != NULL)
685         IAudioClock_Release(sys->clock);
686     IAudioRenderClient_Release(sys->render);
687 fail:
688     Leave();
689     ReleaseSemaphore(sys->ready, 1, NULL);
690 }
691
692 static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
693 {
694     aout_sys_t *sys = aout->sys;
695     HRESULT hr;
696
697     sys->client = NULL;
698     sys->render = NULL;
699     sys->clock = NULL;
700     sys->events.lpVtbl = &vlc_AudioSessionEvents;
701     sys->refs = 1;
702     sys->ready = NULL;
703     sys->done = NULL;
704
705     Enter();
706 retry:
707     /* Get audio device according to policy */
708     // Without configuration item, the variable must be created explicitly.
709     var_Create (aout, "wasapi-audio-device", VLC_VAR_STRING);
710     LPWSTR devid = var_InheritWide (aout, "wasapi-audio-device");
711     var_Destroy (aout, "wasapi-audio-device");
712
713     IMMDevice *dev = NULL;
714     if (devid != NULL)
715     {
716         msg_Dbg (aout, "using selected device %ls", devid);
717         hr = IMMDeviceEnumerator_GetDevice (sys->it, devid, &dev);
718         if (FAILED(hr))
719             msg_Warn(aout, "cannot get audio endpoint (error 0x%lx)", hr);
720         free (devid);
721     }
722     if (dev == NULL)
723     {
724         msg_Dbg (aout, "using default device");
725         hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(sys->it, eRender,
726                                                          eConsole, &dev);
727     }
728     if (FAILED(hr))
729     {
730         msg_Err(aout, "cannot get audio endpoint (error 0x%lx)", hr);
731         goto error;
732     }
733
734     hr = IMMDevice_GetId(dev, &devid);
735     if (SUCCEEDED(hr))
736     {
737         msg_Dbg(aout, "using device %ls", devid);
738         var_SetWide (aout, "audio-device", devid);
739         CoTaskMemFree(devid);
740     }
741
742     hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_ALL, NULL,
743                             (void **)&sys->client);
744     IMMDevice_Release(dev);
745     if (FAILED(hr))
746     {
747         msg_Err(aout, "cannot activate audio client (error 0x%lx)", hr);
748         goto error;
749     }
750
751     /* Configure audio stream */
752     WAVEFORMATEXTENSIBLE wf;
753     WAVEFORMATEX *pwf;
754
755     vlc_ToWave(&wf, fmt);
756     hr = IAudioClient_IsFormatSupported(sys->client, AUDCLNT_SHAREMODE_SHARED,
757                                         &wf.Format, &pwf);
758     if (FAILED(hr))
759     {
760         msg_Err(aout, "cannot negotiate audio format (error 0x%lx)", hr);
761         goto error;
762     }
763
764     if (hr == S_FALSE)
765     {
766         assert(pwf != NULL);
767         if (vlc_FromWave(pwf, fmt))
768         {
769             CoTaskMemFree(pwf);
770             msg_Err(aout, "unsupported audio format");
771             goto error;
772         }
773         msg_Dbg(aout, "modified format");
774     }
775     else
776         assert(pwf == NULL);
777
778     sys->chans_to_reorder = aout_CheckChannelReorder(chans_in, chans_out,
779                                                      fmt->i_physical_channels,
780                                                      sys->chans_table);
781     sys->bits = fmt->i_bitspersample;
782
783     hr = IAudioClient_Initialize(sys->client, AUDCLNT_SHAREMODE_SHARED, 0,
784                                  AOUT_MAX_PREPARE_TIME * 10, 0,
785                                  (hr == S_OK) ? &wf.Format : pwf,
786                                  &GUID_VLC_AUD_OUT);
787     CoTaskMemFree(pwf);
788     if (FAILED(hr))
789     {
790         msg_Err(aout, "cannot initialize audio client (error 0x%lx)", hr);
791         goto error;
792     }
793
794     hr = IAudioClient_GetBufferSize(sys->client, &sys->frames);
795     if (FAILED(hr))
796     {
797         msg_Err(aout, "cannot get buffer size (error 0x%lx)", hr);
798         goto error;
799     }
800
801     sys->ready = CreateSemaphore(NULL, 0, 1, NULL);
802     sys->done = CreateSemaphore(NULL, 0, 1, NULL);
803     if (unlikely(sys->ready == NULL || sys->done == NULL))
804         goto error;
805     /* Note: thread handle released by CRT, ignore it. */
806     if (_beginthread(MTAThread, 0, aout) == (uintptr_t)-1)
807         goto error;
808
809     WaitForSingleObject(sys->ready, INFINITE);
810     if (sys->render == NULL)
811         goto error;
812
813     Leave();
814
815     sys->rate = fmt->i_rate;
816     sys->bytes_per_frame = fmt->i_bytes_per_frame;
817     aout->time_get = TimeGet;
818     aout->play = Play;
819     aout->pause = Pause;
820     aout->flush = Flush;
821     if (likely(sys->control != NULL))
822        IAudioSessionControl_RegisterAudioSessionNotification(sys->control,
823                                                              &sys->events);
824     var_AddCallback (aout, "audio-device", DeviceChanged, NULL);
825
826     return VLC_SUCCESS;
827 error:
828     if (sys->done != NULL)
829         CloseHandle(sys->done);
830     if (sys->ready != NULL)
831         CloseHandle(sys->done);
832     if (sys->client != NULL)
833         IAudioClient_Release(sys->client);
834     if (hr == AUDCLNT_E_DEVICE_INVALIDATED)
835     {
836         var_SetString(aout, "audio-device", "");
837         msg_Warn(aout, "device invalidated, retrying");
838         goto retry;
839     }
840     Leave();
841     return VLC_EGENERIC;
842 }
843
844 static void Stop(audio_output_t *aout)
845 {
846     aout_sys_t *sys = aout->sys;
847
848     Enter();
849     if (likely(sys->control != NULL))
850        IAudioSessionControl_UnregisterAudioSessionNotification(sys->control,
851                                                                &sys->events);
852     ReleaseSemaphore(sys->done, 1, NULL); /* tell MTA thread to finish */
853     WaitForSingleObject(sys->ready, INFINITE); /* wait for that ^ */
854     IAudioClient_Stop(sys->client); /* should not be needed */
855     IAudioClient_Release(sys->client);
856     Leave();
857
858     var_DelCallback (aout, "audio-device", DeviceChanged, NULL);
859
860     CloseHandle(sys->done);
861     CloseHandle(sys->ready);
862 }
863
864 static int Open(vlc_object_t *obj)
865 {
866     audio_output_t *aout = (audio_output_t *)obj;
867     void *pv;
868     HRESULT hr;
869
870     if (!aout->b_force && var_InheritBool(aout, "spdif"))
871         /* Fallback to other plugin until pass-through is implemented */
872         return VLC_EGENERIC;
873
874     aout_sys_t *sys = malloc(sizeof (*sys));
875     if (unlikely(sys == NULL))
876         return VLC_ENOMEM;
877     sys->aout = aout;
878
879     if (TryEnter(aout))
880         goto error;
881
882     hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
883                           &IID_IMMDeviceEnumerator, &pv);
884     if (FAILED(hr))
885     {
886         msg_Dbg(aout, "cannot create device enumerator (error 0x%lx)", hr);
887         Leave();
888         goto error;
889     }
890     sys->it = pv;
891     GetDevices(obj, sys->it);
892     Leave();
893
894     aout->sys = sys;
895     aout->start = Start;
896     aout->stop = Stop;
897     aout->volume_set = SimpleVolumeSet; /* FIXME */
898     aout->mute_set = SimpleMuteSet;
899     return VLC_SUCCESS;
900 error:
901     free(sys);
902     return VLC_EGENERIC;
903 }
904
905 static void Close(vlc_object_t *obj)
906 {
907     audio_output_t *aout = (audio_output_t *)obj;
908     aout_sys_t *sys = aout->sys;
909
910     var_Destroy (aout, "audio-device");
911
912     Enter();
913     IMMDeviceEnumerator_Release(sys->it);
914     Leave();
915
916     free(sys);
917 }