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