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