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