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