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