]> git.sesse.net Git - vlc/blob - modules/audio_output/mmdevice.c
android/surface: fix prototype
[vlc] / modules / audio_output / mmdevice.c
1 /*****************************************************************************
2  * mmdevice.c : Windows Multimedia Device API audio output plugin for VLC
3  *****************************************************************************
4  * Copyright (C) 2012-2014 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 <math.h>
31 #include <assert.h>
32 #include <audiopolicy.h>
33 #include <mmdeviceapi.h>
34 #include <endpointvolume.h>
35
36 DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd,
37    0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
38
39 #include <vlc_common.h>
40 #include <vlc_plugin.h>
41 #include <vlc_aout.h>
42 #include <vlc_charset.h>
43 #include <vlc_modules.h>
44 #include "audio_output/mmdevice.h"
45
46 #if (_WIN32_WINNT < 0x600)
47 static VOID WINAPI (*InitializeConditionVariable)(PCONDITION_VARIABLE);
48 static BOOL WINAPI (*SleepConditionVariableCS)(PCONDITION_VARIABLE,
49                                                PCRITICAL_SECTION, DWORD);
50 static VOID WINAPI (*WakeConditionVariable)(PCONDITION_VARIABLE);
51 #define LOOKUP(s) \
52     if (((s) = (void *)GetProcAddress(h, #s)) == NULL) return FALSE
53
54 BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID); /* avoid warning */
55 BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, LPVOID reserved)
56 {
57     (void) dll;
58     (void) reserved;
59
60     switch (reason)
61     {
62         case DLL_PROCESS_ATTACH:
63         {
64             HANDLE h = GetModuleHandle(TEXT("kernel32.dll"));
65             if (unlikely(h == NULL))
66                 return FALSE;
67             LOOKUP(InitializeConditionVariable);
68             LOOKUP(SleepConditionVariableCS);
69             LOOKUP(WakeConditionVariable);
70             break;
71         }
72     }
73     return TRUE;
74 }
75 #endif
76
77 DEFINE_GUID (GUID_VLC_AUD_OUT, 0x4533f59d, 0x59ee, 0x00c6,
78    0xad, 0xb2, 0xc6, 0x8b, 0x50, 0x1a, 0x66, 0x55);
79
80 static int TryEnterMTA(vlc_object_t *obj)
81 {
82     HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
83     if (unlikely(FAILED(hr)))
84     {
85         msg_Err (obj, "cannot initialize COM (error 0x%lx)", hr);
86         return -1;
87     }
88     return 0;
89 }
90 #define TryEnterMTA(o) TryEnterMTA(VLC_OBJECT(o))
91
92 static void EnterMTA(void)
93 {
94     HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
95     if (unlikely(FAILED(hr)))
96         abort();
97 }
98
99 static void LeaveMTA(void)
100 {
101     CoUninitialize();
102 }
103
104 static wchar_t default_device[1] = L"";
105
106 struct aout_sys_t
107 {
108     aout_stream_t *stream; /**< Underlying audio output stream */
109     module_t *module;
110     audio_output_t *aout;
111     IMMDeviceEnumerator *it; /**< Device enumerator, NULL when exiting */
112     IMMDevice *dev; /**< Selected output device, NULL if none */
113
114     struct IMMNotificationClient device_events;
115     struct IAudioSessionEvents session_events;
116     struct IAudioVolumeDuckNotification duck;
117
118     LONG refs;
119     unsigned ducks;
120     float gain; /**< Current software gain volume */
121
122     wchar_t *device; /**< Requested device identifier, NULL if none */
123     float volume; /**< Requested volume, negative if none */
124     signed char mute; /**< Requested mute, negative if none */
125     CRITICAL_SECTION lock;
126     CONDITION_VARIABLE work;
127     CONDITION_VARIABLE ready;
128     vlc_thread_t thread; /**< Thread for audio session control */
129 };
130
131 /* NOTE: The Core Audio API documentation totally fails to specify the thread
132  * safety (or lack thereof) of the interfaces. This code takes the most
133  * restrictive assumption: no thread safety. The background thread (MMThread)
134  * only runs at specified times, namely between the device_ready and
135  * device_changed events (effectively a thread synchronization barrier, but
136  * only Windows 8 natively provides such a primitive).
137  *
138  * The audio output owner (i.e. the audio output core) is responsible for
139  * serializing callbacks. This code only needs to be concerned with
140  * synchronization between the set of audio output callbacks, MMThread()
141  * and (trivially) the device and session notifications. */
142
143 static int vlc_FromHR(audio_output_t *aout, HRESULT hr)
144 {
145     /* Restart on unplug */
146     if (unlikely(hr == AUDCLNT_E_DEVICE_INVALIDATED))
147         aout_RestartRequest(aout, AOUT_RESTART_OUTPUT);
148     return SUCCEEDED(hr) ? 0 : -1;
149 }
150
151 /*** VLC audio output callbacks ***/
152 static int TimeGet(audio_output_t *aout, mtime_t *restrict delay)
153 {
154     aout_sys_t *sys = aout->sys;
155     HRESULT hr;
156
157     EnterMTA();
158     hr = aout_stream_TimeGet(sys->stream, delay);
159     LeaveMTA();
160
161     return SUCCEEDED(hr) ? 0 : -1;
162 }
163
164 static void Play(audio_output_t *aout, block_t *block)
165 {
166     aout_sys_t *sys = aout->sys;
167     HRESULT hr;
168
169     EnterMTA();
170     hr = aout_stream_Play(sys->stream, block);
171     LeaveMTA();
172
173     vlc_FromHR(aout, hr);
174 }
175
176 static void Pause(audio_output_t *aout, bool paused, mtime_t date)
177 {
178     aout_sys_t *sys = aout->sys;
179     HRESULT hr;
180
181     EnterMTA();
182     hr = aout_stream_Pause(sys->stream, paused);
183     LeaveMTA();
184
185     vlc_FromHR(aout, hr);
186     (void) date;
187 }
188
189 static void Flush(audio_output_t *aout, bool wait)
190 {
191     aout_sys_t *sys = aout->sys;
192
193     EnterMTA();
194     aout_stream_Flush(sys->stream, wait);
195     LeaveMTA();
196 }
197
198 static int VolumeSet(audio_output_t *aout, float vol)
199 {
200     aout_sys_t *sys = aout->sys;
201     float gain = 1.f;
202
203     vol = vol * vol * vol; /* ISimpleAudioVolume is tapered linearly. */
204
205     if (vol > 1.f)
206     {
207         gain = vol;
208         vol = 1.f;
209     }
210
211     aout_GainRequest(aout, gain);
212
213     EnterCriticalSection(&sys->lock);
214     sys->gain = gain;
215     sys->volume = vol;
216     WakeConditionVariable(&sys->work);
217     LeaveCriticalSection(&sys->lock);
218     return 0;
219 }
220
221 static int MuteSet(audio_output_t *aout, bool mute)
222 {
223     aout_sys_t *sys = aout->sys;
224
225     EnterCriticalSection(&sys->lock);
226     sys->mute = mute;
227     WakeConditionVariable(&sys->work);
228     LeaveCriticalSection(&sys->lock);
229     return 0;
230 }
231
232 /*** Audio session events ***/
233 static inline aout_sys_t *vlc_AudioSessionEvents_sys(IAudioSessionEvents *this)
234 {
235     return (void *)(((char *)this) - offsetof(aout_sys_t, session_events));
236 }
237
238 static STDMETHODIMP
239 vlc_AudioSessionEvents_QueryInterface(IAudioSessionEvents *this, REFIID riid,
240                                       void **ppv)
241 {
242     if (IsEqualIID(riid, &IID_IUnknown)
243      || IsEqualIID(riid, &IID_IAudioSessionEvents))
244     {
245         *ppv = this;
246         IUnknown_AddRef(this);
247         return S_OK;
248     }
249     else
250     {
251        *ppv = NULL;
252         return E_NOINTERFACE;
253     }
254 }
255
256 static STDMETHODIMP_(ULONG)
257 vlc_AudioSessionEvents_AddRef(IAudioSessionEvents *this)
258 {
259     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
260     return InterlockedIncrement(&sys->refs);
261 }
262
263 static STDMETHODIMP_(ULONG)
264 vlc_AudioSessionEvents_Release(IAudioSessionEvents *this)
265 {
266     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
267     return InterlockedDecrement(&sys->refs);
268 }
269
270 static STDMETHODIMP
271 vlc_AudioSessionEvents_OnDisplayNameChanged(IAudioSessionEvents *this,
272                                             LPCWSTR wname, LPCGUID ctx)
273 {
274     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
275     audio_output_t *aout = sys->aout;
276
277     msg_Dbg(aout, "display name changed: %ls", wname);
278     (void) ctx;
279     return S_OK;
280 }
281
282 static STDMETHODIMP
283 vlc_AudioSessionEvents_OnIconPathChanged(IAudioSessionEvents *this,
284                                          LPCWSTR wpath, LPCGUID ctx)
285 {
286     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
287     audio_output_t *aout = sys->aout;
288
289     msg_Dbg(aout, "icon path changed: %ls", wpath);
290     (void) ctx;
291     return S_OK;
292 }
293
294 static STDMETHODIMP
295 vlc_AudioSessionEvents_OnSimpleVolumeChanged(IAudioSessionEvents *this,
296                                              float vol, BOOL mute,
297                                              LPCGUID ctx)
298 {
299     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
300     audio_output_t *aout = sys->aout;
301
302     msg_Dbg(aout, "simple volume changed: %f, muting %sabled", vol,
303             mute ? "en" : "dis");
304     EnterCriticalSection(&sys->lock);
305     WakeConditionVariable(&sys->work); /* implicit state: vol & mute */
306     LeaveCriticalSection(&sys->lock);
307     (void) ctx;
308     return S_OK;
309 }
310
311 static STDMETHODIMP
312 vlc_AudioSessionEvents_OnChannelVolumeChanged(IAudioSessionEvents *this,
313                                               DWORD count, float *vols,
314                                               DWORD changed, LPCGUID ctx)
315 {
316     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
317     audio_output_t *aout = sys->aout;
318
319     if (changed != (DWORD)-1)
320         msg_Dbg(aout, "channel volume %lu of %lu changed: %f", changed, count,
321                 vols[changed]);
322     else
323         msg_Dbg(aout, "%lu channels volume changed", count);
324
325     (void) ctx;
326     return S_OK;
327 }
328
329 static STDMETHODIMP
330 vlc_AudioSessionEvents_OnGroupingParamChanged(IAudioSessionEvents *this,
331                                               LPCGUID param, LPCGUID ctx)
332
333 {
334     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
335     audio_output_t *aout = sys->aout;
336
337     msg_Dbg(aout, "grouping parameter changed");
338     (void) param;
339     (void) ctx;
340     return S_OK;
341 }
342
343 static STDMETHODIMP
344 vlc_AudioSessionEvents_OnStateChanged(IAudioSessionEvents *this,
345                                       AudioSessionState state)
346 {
347     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
348     audio_output_t *aout = sys->aout;
349
350     msg_Dbg(aout, "state changed: %d", state);
351     return S_OK;
352 }
353
354 static STDMETHODIMP
355 vlc_AudioSessionEvents_OnSessionDisconnected(IAudioSessionEvents *this,
356                                            AudioSessionDisconnectReason reason)
357 {
358     aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
359     audio_output_t *aout = sys->aout;
360
361     switch (reason)
362     {
363         case DisconnectReasonDeviceRemoval:
364             msg_Warn(aout, "session disconnected: %s", "device removed");
365             break;
366         case DisconnectReasonServerShutdown:
367             msg_Err(aout, "session disconnected: %s", "service stopped");
368             return S_OK;
369         case DisconnectReasonFormatChanged:
370             msg_Warn(aout, "session disconnected: %s", "format changed");
371             break;
372         case DisconnectReasonSessionLogoff:
373             msg_Err(aout, "session disconnected: %s", "user logged off");
374             return S_OK;
375         case DisconnectReasonSessionDisconnected:
376             msg_Err(aout, "session disconnected: %s", "session disconnected");
377             return S_OK;
378         case DisconnectReasonExclusiveModeOverride:
379             msg_Err(aout, "session disconnected: %s", "stream overriden");
380             return S_OK;
381         default:
382             msg_Warn(aout, "session disconnected: unknown reason %d", reason);
383             return S_OK;
384     }
385     /* NOTE: audio decoder thread should get invalidated device and restart */
386     return S_OK;
387 }
388
389 static const struct IAudioSessionEventsVtbl vlc_AudioSessionEvents =
390 {
391     vlc_AudioSessionEvents_QueryInterface,
392     vlc_AudioSessionEvents_AddRef,
393     vlc_AudioSessionEvents_Release,
394
395     vlc_AudioSessionEvents_OnDisplayNameChanged,
396     vlc_AudioSessionEvents_OnIconPathChanged,
397     vlc_AudioSessionEvents_OnSimpleVolumeChanged,
398     vlc_AudioSessionEvents_OnChannelVolumeChanged,
399     vlc_AudioSessionEvents_OnGroupingParamChanged,
400     vlc_AudioSessionEvents_OnStateChanged,
401     vlc_AudioSessionEvents_OnSessionDisconnected,
402 };
403
404 static inline aout_sys_t *vlc_AudioVolumeDuckNotification_sys(IAudioVolumeDuckNotification *this)
405 {
406     return (void *)(((char *)this) - offsetof(aout_sys_t, duck));
407 }
408
409 static STDMETHODIMP
410 vlc_AudioVolumeDuckNotification_QueryInterface(
411     IAudioVolumeDuckNotification *this, REFIID riid, void **ppv)
412 {
413     if (IsEqualIID(riid, &IID_IUnknown)
414      || IsEqualIID(riid, &IID_IAudioVolumeDuckNotification))
415     {
416         *ppv = this;
417         IUnknown_AddRef(this);
418         return S_OK;
419     }
420     else
421     {
422        *ppv = NULL;
423         return E_NOINTERFACE;
424     }
425 }
426
427 static STDMETHODIMP_(ULONG)
428 vlc_AudioVolumeDuckNotification_AddRef(IAudioVolumeDuckNotification *this)
429 {
430     aout_sys_t *sys = vlc_AudioVolumeDuckNotification_sys(this);
431     return InterlockedIncrement(&sys->refs);
432 }
433
434 static STDMETHODIMP_(ULONG)
435 vlc_AudioVolumeDuckNotification_Release(IAudioVolumeDuckNotification *this)
436 {
437     aout_sys_t *sys = vlc_AudioVolumeDuckNotification_sys(this);
438     return InterlockedDecrement(&sys->refs);
439 }
440
441 static STDMETHODIMP
442 vlc_AudioVolumeDuckNotification_OnVolumeDuckNotification(
443     IAudioVolumeDuckNotification *this, LPCWSTR sid, UINT32 count)
444 {
445     aout_sys_t *sys = vlc_AudioVolumeDuckNotification_sys(this);
446     audio_output_t *aout = sys->aout;
447
448     msg_Dbg(aout, "volume ducked by %ls of %u sessions", sid, count);
449     sys->ducks++;
450     aout_PolicyReport(aout, true);
451     return S_OK;
452 }
453
454 static STDMETHODIMP
455 vlc_AudioVolumeDuckNotification_OnVolumeUnduckNotification(
456     IAudioVolumeDuckNotification *this, LPCWSTR sid)
457 {
458     aout_sys_t *sys = vlc_AudioVolumeDuckNotification_sys(this);
459     audio_output_t *aout = sys->aout;
460
461     msg_Dbg(aout, "volume unducked by %ls", sid);
462     sys->ducks--;
463     aout_PolicyReport(aout, sys->ducks != 0);
464     return S_OK;
465 }
466
467 static const struct IAudioVolumeDuckNotificationVtbl vlc_AudioVolumeDuckNotification =
468 {
469     vlc_AudioVolumeDuckNotification_QueryInterface,
470     vlc_AudioVolumeDuckNotification_AddRef,
471     vlc_AudioVolumeDuckNotification_Release,
472
473     vlc_AudioVolumeDuckNotification_OnVolumeDuckNotification,
474     vlc_AudioVolumeDuckNotification_OnVolumeUnduckNotification,
475 };
476
477
478 /*** Audio devices ***/
479
480 /** Gets the user-readable device name */
481 static char *DeviceName(IMMDevice *dev)
482 {
483     IPropertyStore *props;
484     char *name = NULL;
485     PROPVARIANT v;
486     HRESULT hr;
487
488     hr = IMMDevice_OpenPropertyStore(dev, STGM_READ, &props);
489     if (FAILED(hr))
490         return NULL;
491
492     PropVariantInit(&v);
493     hr = IPropertyStore_GetValue(props, &PKEY_Device_FriendlyName, &v);
494     if (SUCCEEDED(hr))
495     {
496         name = FromWide(v.pwszVal);
497         PropVariantClear(&v);
498     }
499     IPropertyStore_Release(props);
500     return name;
501 }
502
503 /** Checks that a device is an output device */
504 static bool DeviceIsRender(IMMDevice *dev)
505 {
506     void *pv;
507
508     if (FAILED(IMMDevice_QueryInterface(dev, &IID_IMMEndpoint, &pv)))
509         return false;
510
511     IMMEndpoint *ep = pv;
512     EDataFlow flow;
513
514     if (FAILED(IMMEndpoint_GetDataFlow(ep, &flow)))
515         flow = eCapture;
516
517     IMMEndpoint_Release(ep);
518     return flow == eRender;
519 }
520
521 static HRESULT DeviceUpdated(audio_output_t *aout, LPCWSTR wid)
522 {
523     aout_sys_t *sys = aout->sys;
524     HRESULT hr;
525
526     IMMDevice *dev;
527     hr = IMMDeviceEnumerator_GetDevice(sys->it, wid, &dev);
528     if (FAILED(hr))
529         return hr;
530
531     if (!DeviceIsRender(dev))
532     {
533         IMMDevice_Release(dev);
534         return S_OK;
535     }
536
537     char *id = FromWide(wid);
538     if (unlikely(id == NULL))
539     {
540         IMMDevice_Release(dev);
541         return E_OUTOFMEMORY;
542     }
543
544     char *name = DeviceName(dev);
545     IMMDevice_Release(dev);
546
547     aout_HotplugReport(aout, id, (name != NULL) ? name : id);
548     free(name);
549     free(id);
550     return S_OK;
551 }
552
553 static inline aout_sys_t *vlc_MMNotificationClient_sys(IMMNotificationClient *this)
554 {
555     return (void *)(((char *)this) - offsetof(aout_sys_t, device_events));
556 }
557
558 static STDMETHODIMP
559 vlc_MMNotificationClient_QueryInterface(IMMNotificationClient *this,
560                                         REFIID riid, void **ppv)
561 {
562     if (IsEqualIID(riid, &IID_IUnknown)
563      || IsEqualIID(riid, &IID_IMMNotificationClient))
564     {
565         *ppv = this;
566         IUnknown_AddRef(this);
567         return S_OK;
568     }
569     else
570     {
571        *ppv = NULL;
572         return E_NOINTERFACE;
573     }
574 }
575
576 static STDMETHODIMP_(ULONG)
577 vlc_MMNotificationClient_AddRef(IMMNotificationClient *this)
578 {
579     aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
580     return InterlockedIncrement(&sys->refs);
581 }
582
583 static STDMETHODIMP_(ULONG)
584 vlc_MMNotificationClient_Release(IMMNotificationClient *this)
585 {
586     aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
587     return InterlockedDecrement(&sys->refs);
588 }
589
590 static STDMETHODIMP
591 vlc_MMNotificationClient_OnDefaultDeviceChange(IMMNotificationClient *this,
592                                                EDataFlow flow, ERole role,
593                                                LPCWSTR wid)
594 {
595     aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
596     audio_output_t *aout = sys->aout;
597
598     if (flow != eRender)
599         return S_OK;
600     if (role != eConsole)
601         return S_OK;
602
603     msg_Dbg(aout, "default device changed: %ls", wid); /* TODO? migrate */
604     return S_OK;
605 }
606
607 static STDMETHODIMP
608 vlc_MMNotificationClient_OnDeviceAdded(IMMNotificationClient *this,
609                                        LPCWSTR wid)
610 {
611     aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
612     audio_output_t *aout = sys->aout;
613
614     msg_Dbg(aout, "device %ls added", wid);
615     return DeviceUpdated(aout, wid);
616 }
617
618 static STDMETHODIMP
619 vlc_MMNotificationClient_OnDeviceRemoved(IMMNotificationClient *this,
620                                          LPCWSTR wid)
621 {
622     aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
623     audio_output_t *aout = sys->aout;
624     char *id = FromWide(wid);
625
626     msg_Dbg(aout, "device %ls removed", wid);
627     if (unlikely(id == NULL))
628         return E_OUTOFMEMORY;
629
630     aout_HotplugReport(aout, id, NULL);
631     free(id);
632     return S_OK;
633 }
634
635 static STDMETHODIMP
636 vlc_MMNotificationClient_OnDeviceStateChanged(IMMNotificationClient *this,
637                                               LPCWSTR wid, DWORD state)
638 {
639     aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
640     audio_output_t *aout = sys->aout;
641
642     /* TODO: show device state / ignore missing devices */
643     msg_Dbg(aout, "device %ls state changed %08lx", wid, state);
644     return S_OK;
645 }
646
647 static STDMETHODIMP
648 vlc_MMNotificationClient_OnPropertyValueChanged(IMMNotificationClient *this,
649                                                 LPCWSTR wid,
650                                                 const PROPERTYKEY key)
651 {
652     aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
653     audio_output_t *aout = sys->aout;
654
655     if (key.pid == PKEY_Device_FriendlyName.pid)
656     {
657         msg_Dbg(aout, "device %ls name changed", wid);
658         return DeviceUpdated(aout, wid);
659     }
660     return S_OK;
661 }
662
663 static const struct IMMNotificationClientVtbl vlc_MMNotificationClient =
664 {
665     vlc_MMNotificationClient_QueryInterface,
666     vlc_MMNotificationClient_AddRef,
667     vlc_MMNotificationClient_Release,
668
669     vlc_MMNotificationClient_OnDeviceStateChanged,
670     vlc_MMNotificationClient_OnDeviceAdded,
671     vlc_MMNotificationClient_OnDeviceRemoved,
672     vlc_MMNotificationClient_OnDefaultDeviceChange,
673     vlc_MMNotificationClient_OnPropertyValueChanged,
674 };
675
676 static int DevicesEnum(audio_output_t *aout, IMMDeviceEnumerator *it)
677 {
678     HRESULT hr;
679     IMMDeviceCollection *devs;
680
681     hr = IMMDeviceEnumerator_EnumAudioEndpoints(it, eRender,
682                                                 DEVICE_STATE_ACTIVE, &devs);
683     if (FAILED(hr))
684     {
685         msg_Warn(aout, "cannot enumerate audio endpoints (error 0x%lx)", hr);
686         return -1;
687     }
688
689     UINT count;
690     hr = IMMDeviceCollection_GetCount(devs, &count);
691     if (FAILED(hr))
692     {
693         msg_Warn(aout, "cannot count audio endpoints (error 0x%lx)", hr);
694         count = 0;
695     }
696
697     unsigned n = 0;
698
699     for (UINT i = 0; i < count; i++)
700     {
701         IMMDevice *dev;
702         char *id, *name;
703
704         hr = IMMDeviceCollection_Item(devs, i, &dev);
705         if (FAILED(hr))
706             continue;
707
708         /* Unique device ID */
709         LPWSTR devid;
710         hr = IMMDevice_GetId(dev, &devid);
711         if (FAILED(hr))
712         {
713             IMMDevice_Release(dev);
714             continue;
715         }
716         id = FromWide(devid);
717         CoTaskMemFree(devid);
718
719         name = DeviceName(dev);
720         IMMDevice_Release(dev);
721
722         aout_HotplugReport(aout, id, (name != NULL) ? name : id);
723         free(name);
724         free(id);
725         n++;
726     }
727     IMMDeviceCollection_Release(devs);
728     return n;
729 }
730
731 static int DeviceSelect(audio_output_t *aout, const char *id)
732 {
733     aout_sys_t *sys = aout->sys;
734     wchar_t *device;
735
736     if (id != NULL)
737     {
738         device = ToWide(id);
739         if (unlikely(device == NULL))
740             return -1;
741     }
742     else
743         device = default_device;
744
745     EnterCriticalSection(&sys->lock);
746     assert(sys->device == NULL);
747     sys->device = device;
748
749     WakeConditionVariable(&sys->work);
750     while (sys->device != NULL)
751         SleepConditionVariableCS(&sys->ready, &sys->lock, INFINITE);
752     LeaveCriticalSection(&sys->lock);
753
754     if (sys->stream != NULL && sys->dev != NULL)
755         /* Request restart of stream with the new device */
756         aout_RestartRequest(aout, AOUT_RESTART_OUTPUT);
757     return (sys->dev != NULL) ? 0 : -1;
758 }
759
760 /*** Initialization / deinitialization **/
761 static wchar_t *var_InheritWide(vlc_object_t *obj, const char *name)
762 {
763     char *v8 = var_InheritString(obj, name);
764     if (v8 == NULL)
765         return NULL;
766
767     wchar_t *v16 = ToWide(v8);
768     free(v8);
769     return v16;
770 }
771 #define var_InheritWide(o,n) var_InheritWide(VLC_OBJECT(o),n)
772
773 /** MMDevice audio output thread.
774  * This thread takes cares of the audio session control. Inconveniently enough,
775  * the audio session control interface must:
776  *  - be created and destroyed from the same thread, and
777  *  - survive across VLC audio output calls.
778  * The only way to reconcile both requirements is a custom thread.
779  * The thread also ensure that the COM Multi-Thread Apartment is continuously
780  * referenced so that MMDevice objects are not destroyed early.
781  * Furthermore, VolumeSet() and MuteSet() may be called from a thread with a
782  * COM STA, so that it cannot access the COM MTA for audio controls.
783  */
784 static HRESULT MMSession(audio_output_t *aout, IMMDeviceEnumerator *it)
785 {
786     aout_sys_t *sys = aout->sys;
787     IAudioSessionManager *manager;
788     IAudioSessionControl *control;
789     ISimpleAudioVolume *volume;
790     IAudioEndpointVolume *endpoint;
791     void *pv;
792     HRESULT hr;
793
794     assert(sys->device != NULL);
795     assert(sys->dev == NULL);
796
797     if (sys->device != default_device) /* Device selected explicitly */
798     {
799         msg_Dbg(aout, "using selected device %ls", sys->device);
800         hr = IMMDeviceEnumerator_GetDevice(it, sys->device, &sys->dev);
801         if (FAILED(hr))
802             msg_Err(aout, "cannot get selected device %ls (error 0x%lx)",
803                     sys->device, hr);
804         free(sys->device);
805     }
806     else
807         hr = AUDCLNT_E_DEVICE_INVALIDATED;
808
809     while (hr == AUDCLNT_E_DEVICE_INVALIDATED)
810     {   /* Default device selected by policy and with stream routing.
811          * "Do not use eMultimedia" says MSDN. */
812         msg_Dbg(aout, "using default device");
813         hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(it, eRender,
814                                                          eConsole, &sys->dev);
815         if (FAILED(hr))
816             msg_Err(aout, "cannot get default device (error 0x%lx)", hr);
817     }
818
819     sys->device = NULL;
820     WakeConditionVariable(&sys->ready);
821
822     if (SUCCEEDED(hr))
823     {   /* Report actual device */
824         LPWSTR wdevid;
825
826         hr = IMMDevice_GetId(sys->dev, &wdevid);
827         if (SUCCEEDED(hr))
828         {
829             char *id = FromWide(wdevid);
830             CoTaskMemFree(wdevid);
831             if (likely(id != NULL))
832             {
833                 aout_DeviceReport(aout, id);
834                 free(id);
835             }
836         }
837     }
838     else
839     {
840         msg_Err(aout, "cannot get device identifier (error 0x%lx)", hr);
841         return hr;
842     }
843
844     /* Create session manager (for controls even w/o active audio client) */
845     hr = IMMDevice_Activate(sys->dev, &IID_IAudioSessionManager,
846                             CLSCTX_ALL, NULL, &pv);
847     manager = pv;
848     if (SUCCEEDED(hr))
849     {
850         LPCGUID guid = &GUID_VLC_AUD_OUT;
851
852         /* Register session control */
853         hr = IAudioSessionManager_GetAudioSessionControl(manager, guid, 0,
854                                                          &control);
855         if (SUCCEEDED(hr))
856         {
857             wchar_t *ua = var_InheritWide(aout, "user-agent");
858             IAudioSessionControl_SetDisplayName(control, ua, NULL);
859             free(ua);
860
861             IAudioSessionControl_RegisterAudioSessionNotification(control,
862                                                          &sys->session_events);
863         }
864         else
865             msg_Err(aout, "cannot get session control (error 0x%lx)", hr);
866
867         hr = IAudioSessionManager_GetSimpleAudioVolume(manager, guid, FALSE,
868                                                        &volume);
869         if (FAILED(hr))
870             msg_Err(aout, "cannot get simple volume (error 0x%lx)", hr);
871
872         /* Try to get version 2 (Windows 7) of the manager & control */
873         wchar_t *siid = NULL;
874
875         hr = IAudioSessionManager_QueryInterface(manager,
876                                               &IID_IAudioSessionControl2, &pv);
877         if (SUCCEEDED(hr))
878         {
879             IAudioSessionControl2 *c2 = pv;
880
881             IAudioSessionControl2_SetDuckingPreference(c2, FALSE);
882             hr = IAudioSessionControl2_GetSessionInstanceIdentifier(c2, &siid);
883             if (FAILED(hr))
884                 siid = NULL;
885             IAudioSessionControl2_Release(c2);
886         }
887         else
888             msg_Dbg(aout, "version 2 session control unavailable");
889
890         hr = IAudioSessionManager_QueryInterface(manager,
891                                               &IID_IAudioSessionManager2, &pv);
892         if (SUCCEEDED(hr))
893         {
894             IAudioSessionManager2 *m2 = pv;
895
896             IAudioSessionManager2_RegisterDuckNotification(m2, siid,
897                                                            &sys->duck);
898             IAudioSessionManager2_Release(m2);
899         }
900         else
901             msg_Dbg(aout, "version 2 session management unavailable");
902
903         CoTaskMemFree(siid);
904     }
905     else
906     {
907         msg_Err(aout, "cannot activate session manager (error 0x%lx)", hr);
908         control = NULL;
909         volume = NULL;
910     }
911
912     hr = IMMDevice_Activate(sys->dev, &IID_IAudioEndpointVolume,
913                             CLSCTX_ALL, NULL, &pv);
914     endpoint = pv;
915     if (SUCCEEDED(hr))
916     {
917         float min, max, inc;
918
919         hr = IAudioEndpointVolume_GetVolumeRange(endpoint, &min, &max, &inc);
920         if (SUCCEEDED(hr))
921             msg_Dbg(aout, "volume from %+f dB to %+f dB with %f dB increments",
922                     min, max, inc);
923         else
924             msg_Err(aout, "cannot get volume range (error 0x%lx)", hr);
925     }
926     else
927         msg_Err(aout, "cannot activate endpoint volume (error %lx)", hr);
928
929     /* Main loop (adjust volume as long as device is unchanged) */
930     while (sys->device == NULL)
931     {
932         if (volume != NULL)
933         {
934             float level;
935
936             hr = ISimpleAudioVolume_GetMasterVolume(volume, &level);
937             if (SUCCEEDED(hr))
938                 aout_VolumeReport(aout, cbrtf(level * sys->gain));
939             else
940                 msg_Err(aout, "cannot get master volume (error 0x%lx)", hr);
941
942             level = sys->volume;
943             if (level >= 0.f)
944             {
945                 hr = ISimpleAudioVolume_SetMasterVolume(volume, level, NULL);
946                 if (FAILED(hr))
947                     msg_Err(aout, "cannot set master volume (error 0x%lx)",
948                             hr);
949             }
950             sys->volume = -1.f;
951
952             BOOL mute;
953
954             hr = ISimpleAudioVolume_GetMute(volume, &mute);
955             if (SUCCEEDED(hr))
956                 aout_MuteReport(aout, mute != FALSE);
957             else
958                 msg_Err(aout, "cannot get mute (error 0x%lx)", hr);
959
960             if (sys->mute >= 0)
961             {
962                 mute = sys->mute ? TRUE : FALSE;
963
964                 hr = ISimpleAudioVolume_SetMute(volume, mute, NULL);
965                 if (FAILED(hr))
966                     msg_Err(aout, "cannot set mute (error 0x%lx)", hr);
967             }
968             sys->mute = -1;
969         }
970
971         SleepConditionVariableCS(&sys->work, &sys->lock, INFINITE);
972     }
973     LeaveCriticalSection(&sys->lock);
974
975     if (endpoint != NULL)
976         IAudioEndpointVolume_Release(endpoint);
977
978     if (manager != NULL)
979     {   /* Deregister callbacks *without* the lock */
980         hr = IAudioSessionManager_QueryInterface(manager,
981                                               &IID_IAudioSessionManager2, &pv);
982         if (SUCCEEDED(hr))
983         {
984             IAudioSessionManager2 *m2 = pv;
985
986             IAudioSessionManager2_UnregisterDuckNotification(m2, &sys->duck);
987             IAudioSessionManager2_Release(m2);
988         }
989
990         if (volume != NULL)
991             ISimpleAudioVolume_Release(volume);
992
993         if (control != NULL)
994         {
995             IAudioSessionControl_UnregisterAudioSessionNotification(control,
996                                                          &sys->session_events);
997             IAudioSessionControl_Release(control);
998         }
999
1000         IAudioSessionManager_Release(manager);
1001     }
1002
1003     EnterCriticalSection(&sys->lock);
1004     IMMDevice_Release(sys->dev);
1005     sys->dev = NULL;
1006     return S_OK;
1007 }
1008
1009 static void *MMThread(void *data)
1010 {
1011     audio_output_t *aout = data;
1012     aout_sys_t *sys = aout->sys;
1013     IMMDeviceEnumerator *it = sys->it;
1014
1015     EnterMTA();
1016     IMMDeviceEnumerator_RegisterEndpointNotificationCallback(it,
1017                                                           &sys->device_events);
1018     DevicesEnum(aout, it);
1019
1020     EnterCriticalSection(&sys->lock);
1021
1022     do
1023         if (FAILED(MMSession(aout, it)))
1024             SleepConditionVariableCS(&sys->work, &sys->lock, INFINITE);
1025     while (sys->it != NULL);
1026
1027     LeaveCriticalSection(&sys->lock);
1028
1029     IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(it,
1030                                                           &sys->device_events);
1031     IMMDeviceEnumerator_Release(it);
1032     LeaveMTA();
1033     return NULL;
1034 }
1035
1036 /**
1037  * Callback for aout_stream_t to create a stream on the device.
1038  * This can instantiate an IAudioClient or IDirectSound(8) object.
1039  */
1040 static HRESULT ActivateDevice(void *opaque, REFIID iid, PROPVARIANT *actparms,
1041                               void **restrict pv)
1042 {
1043     IMMDevice *dev = opaque;
1044     return IMMDevice_Activate(dev, iid, CLSCTX_ALL, actparms, pv);
1045 }
1046
1047 static int aout_stream_Start(void *func, va_list ap)
1048 {
1049     aout_stream_start_t start = func;
1050     aout_stream_t *s = va_arg(ap, aout_stream_t *);
1051     audio_sample_format_t *fmt = va_arg(ap, audio_sample_format_t *);
1052     HRESULT *hr = va_arg(ap, HRESULT *);
1053
1054     *hr = start(s, fmt, &GUID_VLC_AUD_OUT);
1055     if (*hr == AUDCLNT_E_DEVICE_INVALIDATED)
1056         return VLC_ETIMEOUT;
1057     return SUCCEEDED(*hr) ? VLC_SUCCESS : VLC_EGENERIC;
1058 }
1059
1060 static void aout_stream_Stop(void *func, va_list ap)
1061 {
1062     aout_stream_stop_t stop = func;
1063     aout_stream_t *s = va_arg(ap, aout_stream_t *);
1064
1065     stop(s);
1066 }
1067
1068 static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
1069 {
1070     aout_sys_t *sys = aout->sys;
1071
1072     if (sys->dev == NULL)
1073         return -1;
1074
1075     aout_stream_t *s = vlc_object_create(aout, sizeof (*s));
1076     if (unlikely(s == NULL))
1077         return -1;
1078
1079     s->owner.device = sys->dev;
1080     s->owner.activate = ActivateDevice;
1081
1082     EnterMTA();
1083     for (;;)
1084     {
1085         HRESULT hr;
1086
1087         /* TODO: Do not overload the "aout" configuration item. */
1088         sys->module = vlc_module_load(s, "aout stream", "$aout", false,
1089                                       aout_stream_Start, s, fmt, &hr);
1090         if (hr != AUDCLNT_E_DEVICE_INVALIDATED || DeviceSelect(aout, NULL))
1091             break;
1092     }
1093     LeaveMTA();
1094
1095     if (sys->module == NULL)
1096     {
1097         vlc_object_release(s);
1098         return -1;
1099     }
1100
1101     assert (sys->stream == NULL);
1102     sys->stream = s;
1103     aout_GainRequest(aout, sys->gain);
1104     return 0;
1105 }
1106
1107 static void Stop(audio_output_t *aout)
1108 {
1109     aout_sys_t *sys = aout->sys;
1110
1111     assert(sys->stream != NULL);
1112
1113     EnterMTA();
1114     vlc_module_unload(sys->module, aout_stream_Stop, sys->stream);
1115     LeaveMTA();
1116
1117     vlc_object_release(sys->stream);
1118     sys->stream = NULL;
1119 }
1120
1121 static int Open(vlc_object_t *obj)
1122 {
1123     audio_output_t *aout = (audio_output_t *)obj;
1124
1125     aout_sys_t *sys = malloc(sizeof (*sys));
1126     if (unlikely(sys == NULL))
1127         return VLC_ENOMEM;
1128
1129     aout->sys = sys;
1130     sys->stream = NULL;
1131     sys->aout = aout;
1132     sys->it = NULL;
1133     sys->dev = NULL;
1134     sys->device_events.lpVtbl = &vlc_MMNotificationClient;
1135     sys->session_events.lpVtbl = &vlc_AudioSessionEvents;
1136     sys->duck.lpVtbl = &vlc_AudioVolumeDuckNotification;
1137     sys->refs = 1;
1138     sys->ducks = 0;
1139
1140     sys->device = default_device;
1141     sys->gain = 1.f;
1142     sys->volume = -1.f;
1143     sys->mute = -1;
1144     InitializeCriticalSection(&sys->lock);
1145     InitializeConditionVariable(&sys->work);
1146     InitializeConditionVariable(&sys->ready);
1147
1148     /* Initialize MMDevice API */
1149     if (TryEnterMTA(aout))
1150         goto error;
1151
1152     void *pv;
1153     HRESULT hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
1154                                   &IID_IMMDeviceEnumerator, &pv);
1155     if (FAILED(hr))
1156     {
1157         LeaveMTA();
1158         msg_Dbg(aout, "cannot create device enumerator (error 0x%lx)", hr);
1159         goto error;
1160     }
1161     sys->it = pv;
1162
1163     if (vlc_clone(&sys->thread, MMThread, aout, VLC_THREAD_PRIORITY_LOW))
1164     {
1165         IMMDeviceEnumerator_Release(sys->it);
1166         LeaveMTA();
1167         goto error;
1168     }
1169
1170     EnterCriticalSection(&sys->lock);
1171     while (sys->device != NULL)
1172         SleepConditionVariableCS(&sys->ready, &sys->lock, INFINITE);
1173     LeaveCriticalSection(&sys->lock);
1174     LeaveMTA(); /* Leave MTA after thread has entered MTA */
1175
1176     aout->start = Start;
1177     aout->stop = Stop;
1178     aout->time_get = TimeGet;
1179     aout->play = Play;
1180     aout->pause = Pause;
1181     aout->flush = Flush;
1182     aout->volume_set = VolumeSet;
1183     aout->mute_set = MuteSet;
1184     aout->device_select = DeviceSelect;
1185     return VLC_SUCCESS;
1186
1187 error:
1188     DeleteCriticalSection(&sys->lock);
1189     free(sys);
1190     return VLC_EGENERIC;
1191 }
1192
1193 static void Close(vlc_object_t *obj)
1194 {
1195     audio_output_t *aout = (audio_output_t *)obj;
1196     aout_sys_t *sys = aout->sys;
1197
1198     EnterCriticalSection(&sys->lock);
1199     sys->device = default_device; /* break out of MMSession() loop */
1200     sys->it = NULL; /* break out of MMThread() loop */
1201     WakeConditionVariable(&sys->work);
1202     LeaveCriticalSection(&sys->lock);
1203
1204     vlc_join(sys->thread, NULL);
1205     DeleteCriticalSection(&sys->lock);
1206     free(sys);
1207 }
1208
1209 vlc_module_begin()
1210     set_shortname("MMDevice")
1211     set_description(N_("Windows Multimedia Device output"))
1212     set_capability("audio output", 150)
1213     set_category(CAT_AUDIO)
1214     set_subcategory(SUBCAT_AUDIO_AOUT)
1215     add_shortcut("wasapi", "directsound")
1216     set_callbacks(Open, Close)
1217 vlc_module_end()