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