access: srt: add support stream encryption
[vlc.git] / modules / audio_output / mmdevice.c
blobe141d721e14a8724b080762ddc28907a6b10ab56
1 /*****************************************************************************
2 * mmdevice.c : Windows Multimedia Device API audio output plugin for VLC
3 *****************************************************************************
4 * Copyright (C) 2012-2017 RĂ©mi Denis-Courmont
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.
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.
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 *****************************************************************************/
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
25 #define INITGUID
26 #define COBJMACROS
27 #define CONST_VTABLE
29 #include <stdlib.h>
30 #include <math.h>
31 #include <assert.h>
32 #include <audiopolicy.h>
33 #include <mmdeviceapi.h>
34 #include <endpointvolume.h>
36 DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd,
37 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
39 #include <vlc_common.h>
40 #include <vlc_plugin.h>
41 #include <vlc_aout.h>
42 #include <vlc_charset.h>
43 #include <vlc_modules.h>
44 #include "audio_output/mmdevice.h"
46 DEFINE_GUID (GUID_VLC_AUD_OUT, 0x4533f59d, 0x59ee, 0x00c6,
47 0xad, 0xb2, 0xc6, 0x8b, 0x50, 0x1a, 0x66, 0x55);
49 static int TryEnterMTA(vlc_object_t *obj)
51 HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
52 if (unlikely(FAILED(hr)))
54 msg_Err (obj, "cannot initialize COM (error 0x%lx)", hr);
55 return -1;
57 return 0;
59 #define TryEnterMTA(o) TryEnterMTA(VLC_OBJECT(o))
61 static void EnterMTA(void)
63 HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
64 if (unlikely(FAILED(hr)))
65 abort();
68 static void LeaveMTA(void)
70 CoUninitialize();
73 static wchar_t default_device[1] = L"";
75 struct aout_sys_t
77 aout_stream_t *stream; /**< Underlying audio output stream */
78 module_t *module;
79 audio_output_t *aout;
80 IMMDeviceEnumerator *it; /**< Device enumerator, NULL when exiting */
81 IMMDevice *dev; /**< Selected output device, NULL if none */
83 struct IMMNotificationClient device_events;
84 struct IAudioSessionEvents session_events;
85 struct IAudioVolumeDuckNotification duck;
87 LONG refs;
88 unsigned ducks;
89 float gain; /**< Current software gain volume */
91 wchar_t *requested_device; /**< Requested device identifier, NULL if none */
92 float requested_volume; /**< Requested volume, negative if none */
93 signed char requested_mute; /**< Requested mute, negative if none */
94 wchar_t *acquired_device; /**< Acquired device identifier, NULL if none */
95 CRITICAL_SECTION lock;
96 CONDITION_VARIABLE work;
97 CONDITION_VARIABLE ready;
98 vlc_thread_t thread; /**< Thread for audio session control */
101 /* NOTE: The Core Audio API documentation totally fails to specify the thread
102 * safety (or lack thereof) of the interfaces. This code takes the most
103 * restrictive assumption: no thread safety. The background thread (MMThread)
104 * only runs at specified times, namely between the device_ready and
105 * device_changed events (effectively a thread synchronization barrier, but
106 * only Windows 8 natively provides such a primitive).
108 * The audio output owner (i.e. the audio output core) is responsible for
109 * serializing callbacks. This code only needs to be concerned with
110 * synchronization between the set of audio output callbacks, MMThread()
111 * and (trivially) the device and session notifications. */
113 static int DeviceSelect(audio_output_t *, const char *);
114 static int vlc_FromHR(audio_output_t *aout, HRESULT hr)
116 /* Select the default device (and restart) on unplug */
117 if (unlikely(hr == AUDCLNT_E_DEVICE_INVALIDATED ||
118 hr == AUDCLNT_E_RESOURCES_INVALIDATED))
119 DeviceSelect(aout, NULL);
120 return SUCCEEDED(hr) ? 0 : -1;
123 /*** VLC audio output callbacks ***/
124 static int TimeGet(audio_output_t *aout, mtime_t *restrict delay)
126 aout_sys_t *sys = aout->sys;
127 HRESULT hr;
129 EnterMTA();
130 hr = aout_stream_TimeGet(sys->stream, delay);
131 LeaveMTA();
133 return SUCCEEDED(hr) ? 0 : -1;
136 static void Play(audio_output_t *aout, block_t *block)
138 aout_sys_t *sys = aout->sys;
139 HRESULT hr;
141 EnterMTA();
142 hr = aout_stream_Play(sys->stream, block);
143 LeaveMTA();
145 vlc_FromHR(aout, hr);
148 static void Pause(audio_output_t *aout, bool paused, mtime_t date)
150 aout_sys_t *sys = aout->sys;
151 HRESULT hr;
153 EnterMTA();
154 hr = aout_stream_Pause(sys->stream, paused);
155 LeaveMTA();
157 vlc_FromHR(aout, hr);
158 (void) date;
161 static void Flush(audio_output_t *aout, bool wait)
163 aout_sys_t *sys = aout->sys;
164 HRESULT hr;
166 EnterMTA();
167 hr = aout_stream_Flush(sys->stream, wait);
168 LeaveMTA();
170 vlc_FromHR(aout, hr);
173 static int VolumeSet(audio_output_t *aout, float vol)
175 aout_sys_t *sys = aout->sys;
176 float gain = 1.f;
178 vol = vol * vol * vol; /* ISimpleAudioVolume is tapered linearly. */
180 if (vol > 1.f)
182 gain = vol;
183 vol = 1.f;
186 aout_GainRequest(aout, gain);
188 EnterCriticalSection(&sys->lock);
189 sys->gain = gain;
190 sys->requested_volume = vol;
191 WakeConditionVariable(&sys->work);
192 LeaveCriticalSection(&sys->lock);
193 return 0;
196 static int MuteSet(audio_output_t *aout, bool mute)
198 aout_sys_t *sys = aout->sys;
200 EnterCriticalSection(&sys->lock);
201 sys->requested_mute = mute;
202 WakeConditionVariable(&sys->work);
203 LeaveCriticalSection(&sys->lock);
204 return 0;
207 /*** Audio session events ***/
208 static STDMETHODIMP
209 vlc_AudioSessionEvents_QueryInterface(IAudioSessionEvents *this, REFIID riid,
210 void **ppv)
212 if (IsEqualIID(riid, &IID_IUnknown)
213 || IsEqualIID(riid, &IID_IAudioSessionEvents))
215 *ppv = this;
216 IUnknown_AddRef(this);
217 return S_OK;
219 else
221 *ppv = NULL;
222 return E_NOINTERFACE;
226 static STDMETHODIMP_(ULONG)
227 vlc_AudioSessionEvents_AddRef(IAudioSessionEvents *this)
229 aout_sys_t *sys = container_of(this, aout_sys_t, session_events);
230 return InterlockedIncrement(&sys->refs);
233 static STDMETHODIMP_(ULONG)
234 vlc_AudioSessionEvents_Release(IAudioSessionEvents *this)
236 aout_sys_t *sys = container_of(this, aout_sys_t, session_events);
237 return InterlockedDecrement(&sys->refs);
240 static STDMETHODIMP
241 vlc_AudioSessionEvents_OnDisplayNameChanged(IAudioSessionEvents *this,
242 LPCWSTR wname, LPCGUID ctx)
244 aout_sys_t *sys = container_of(this, aout_sys_t, session_events);
245 audio_output_t *aout = sys->aout;
247 msg_Dbg(aout, "display name changed: %ls", wname);
248 (void) ctx;
249 return S_OK;
252 static STDMETHODIMP
253 vlc_AudioSessionEvents_OnIconPathChanged(IAudioSessionEvents *this,
254 LPCWSTR wpath, LPCGUID ctx)
256 aout_sys_t *sys = container_of(this, aout_sys_t, session_events);
257 audio_output_t *aout = sys->aout;
259 msg_Dbg(aout, "icon path changed: %ls", wpath);
260 (void) ctx;
261 return S_OK;
264 static STDMETHODIMP
265 vlc_AudioSessionEvents_OnSimpleVolumeChanged(IAudioSessionEvents *this,
266 float vol, BOOL mute,
267 LPCGUID ctx)
269 aout_sys_t *sys = container_of(this, aout_sys_t, session_events);
270 audio_output_t *aout = sys->aout;
272 msg_Dbg(aout, "simple volume changed: %f, muting %sabled", vol,
273 mute ? "en" : "dis");
274 EnterCriticalSection(&sys->lock);
275 WakeConditionVariable(&sys->work); /* implicit state: vol & mute */
276 LeaveCriticalSection(&sys->lock);
277 (void) ctx;
278 return S_OK;
281 static STDMETHODIMP
282 vlc_AudioSessionEvents_OnChannelVolumeChanged(IAudioSessionEvents *this,
283 DWORD count, float *vols,
284 DWORD changed, LPCGUID ctx)
286 aout_sys_t *sys = container_of(this, aout_sys_t, session_events);
287 audio_output_t *aout = sys->aout;
289 if (changed != (DWORD)-1)
290 msg_Dbg(aout, "channel volume %lu of %lu changed: %f", changed, count,
291 vols[changed]);
292 else
293 msg_Dbg(aout, "%lu channels volume changed", count);
295 (void) ctx;
296 return S_OK;
299 static STDMETHODIMP
300 vlc_AudioSessionEvents_OnGroupingParamChanged(IAudioSessionEvents *this,
301 LPCGUID param, LPCGUID ctx)
304 aout_sys_t *sys = container_of(this, aout_sys_t, session_events);
305 audio_output_t *aout = sys->aout;
307 msg_Dbg(aout, "grouping parameter changed");
308 (void) param;
309 (void) ctx;
310 return S_OK;
313 static STDMETHODIMP
314 vlc_AudioSessionEvents_OnStateChanged(IAudioSessionEvents *this,
315 AudioSessionState state)
317 aout_sys_t *sys = container_of(this, aout_sys_t, session_events);
318 audio_output_t *aout = sys->aout;
320 msg_Dbg(aout, "state changed: %d", state);
321 return S_OK;
324 static STDMETHODIMP
325 vlc_AudioSessionEvents_OnSessionDisconnected(IAudioSessionEvents *this,
326 AudioSessionDisconnectReason reason)
328 aout_sys_t *sys = container_of(this, aout_sys_t, session_events);
329 audio_output_t *aout = sys->aout;
331 switch (reason)
333 case DisconnectReasonDeviceRemoval:
334 msg_Warn(aout, "session disconnected: %s", "device removed");
335 break;
336 case DisconnectReasonServerShutdown:
337 msg_Err(aout, "session disconnected: %s", "service stopped");
338 return S_OK;
339 case DisconnectReasonFormatChanged:
340 msg_Warn(aout, "session disconnected: %s", "format changed");
341 break;
342 case DisconnectReasonSessionLogoff:
343 msg_Err(aout, "session disconnected: %s", "user logged off");
344 return S_OK;
345 case DisconnectReasonSessionDisconnected:
346 msg_Err(aout, "session disconnected: %s", "session disconnected");
347 return S_OK;
348 case DisconnectReasonExclusiveModeOverride:
349 msg_Err(aout, "session disconnected: %s", "stream overriden");
350 return S_OK;
351 default:
352 msg_Warn(aout, "session disconnected: unknown reason %d", reason);
353 return S_OK;
355 /* NOTE: audio decoder thread should get invalidated device and restart */
356 return S_OK;
359 static const struct IAudioSessionEventsVtbl vlc_AudioSessionEvents =
361 vlc_AudioSessionEvents_QueryInterface,
362 vlc_AudioSessionEvents_AddRef,
363 vlc_AudioSessionEvents_Release,
365 vlc_AudioSessionEvents_OnDisplayNameChanged,
366 vlc_AudioSessionEvents_OnIconPathChanged,
367 vlc_AudioSessionEvents_OnSimpleVolumeChanged,
368 vlc_AudioSessionEvents_OnChannelVolumeChanged,
369 vlc_AudioSessionEvents_OnGroupingParamChanged,
370 vlc_AudioSessionEvents_OnStateChanged,
371 vlc_AudioSessionEvents_OnSessionDisconnected,
374 static STDMETHODIMP
375 vlc_AudioVolumeDuckNotification_QueryInterface(
376 IAudioVolumeDuckNotification *this, REFIID riid, void **ppv)
378 if (IsEqualIID(riid, &IID_IUnknown)
379 || IsEqualIID(riid, &IID_IAudioVolumeDuckNotification))
381 *ppv = this;
382 IUnknown_AddRef(this);
383 return S_OK;
385 else
387 *ppv = NULL;
388 return E_NOINTERFACE;
392 static STDMETHODIMP_(ULONG)
393 vlc_AudioVolumeDuckNotification_AddRef(IAudioVolumeDuckNotification *this)
395 aout_sys_t *sys = container_of(this, aout_sys_t, duck);
396 return InterlockedIncrement(&sys->refs);
399 static STDMETHODIMP_(ULONG)
400 vlc_AudioVolumeDuckNotification_Release(IAudioVolumeDuckNotification *this)
402 aout_sys_t *sys = container_of(this, aout_sys_t, duck);
403 return InterlockedDecrement(&sys->refs);
406 static STDMETHODIMP
407 vlc_AudioVolumeDuckNotification_OnVolumeDuckNotification(
408 IAudioVolumeDuckNotification *this, LPCWSTR sid, UINT32 count)
410 aout_sys_t *sys = container_of(this, aout_sys_t, duck);
411 audio_output_t *aout = sys->aout;
413 msg_Dbg(aout, "volume ducked by %ls of %u sessions", sid, count);
414 sys->ducks++;
415 aout_PolicyReport(aout, true);
416 return S_OK;
419 static STDMETHODIMP
420 vlc_AudioVolumeDuckNotification_OnVolumeUnduckNotification(
421 IAudioVolumeDuckNotification *this, LPCWSTR sid)
423 aout_sys_t *sys = container_of(this, aout_sys_t, duck);
424 audio_output_t *aout = sys->aout;
426 msg_Dbg(aout, "volume unducked by %ls", sid);
427 sys->ducks--;
428 aout_PolicyReport(aout, sys->ducks != 0);
429 return S_OK;
432 static const struct IAudioVolumeDuckNotificationVtbl vlc_AudioVolumeDuckNotification =
434 vlc_AudioVolumeDuckNotification_QueryInterface,
435 vlc_AudioVolumeDuckNotification_AddRef,
436 vlc_AudioVolumeDuckNotification_Release,
438 vlc_AudioVolumeDuckNotification_OnVolumeDuckNotification,
439 vlc_AudioVolumeDuckNotification_OnVolumeUnduckNotification,
443 /*** Audio devices ***/
445 /** Gets the user-readable device name */
446 static int DeviceHotplugReport(audio_output_t *aout, LPCWSTR wid,
447 IMMDevice *dev)
449 IPropertyStore *props;
450 char *name;
451 PROPVARIANT v;
452 HRESULT hr;
454 char *id = FromWide(wid);
455 if (unlikely(id == NULL))
456 return VLC_ENOMEM;
458 hr = IMMDevice_OpenPropertyStore(dev, STGM_READ, &props);
459 if (FAILED(hr))
461 free(id);
462 return VLC_EGENERIC;
465 PropVariantInit(&v);
466 hr = IPropertyStore_GetValue(props, &PKEY_Device_FriendlyName, &v);
467 if (SUCCEEDED(hr))
469 name = FromWide(v.pwszVal);
470 PropVariantClear(&v);
472 else
473 name = id;
475 IPropertyStore_Release(props);
476 aout_HotplugReport(aout, id, name);
478 free(id);
479 if (id != name)
480 free(name);
481 return VLC_SUCCESS;
484 /** Checks that a device is an output device */
485 static bool DeviceIsRender(IMMDevice *dev)
487 void *pv;
489 if (FAILED(IMMDevice_QueryInterface(dev, &IID_IMMEndpoint, &pv)))
490 return false;
492 IMMEndpoint *ep = pv;
493 EDataFlow flow;
494 HRESULT hr = IMMEndpoint_GetDataFlow(ep, &flow);
496 IMMEndpoint_Release(ep);
497 if (FAILED(hr) || flow != eRender)
498 return false;
500 DWORD pdwState;
501 hr = IMMDevice_GetState(dev, &pdwState);
502 return !FAILED(hr) && pdwState == DEVICE_STATE_ACTIVE;
505 static HRESULT DeviceUpdated(audio_output_t *aout, LPCWSTR wid)
507 aout_sys_t *sys = aout->sys;
508 HRESULT hr;
510 IMMDevice *dev;
511 hr = IMMDeviceEnumerator_GetDevice(sys->it, wid, &dev);
512 if (FAILED(hr))
513 return hr;
515 if (!DeviceIsRender(dev))
517 IMMDevice_Release(dev);
518 return S_OK;
521 DeviceHotplugReport(aout, wid, dev);
522 IMMDevice_Release(dev);
523 return S_OK;
526 static STDMETHODIMP
527 vlc_MMNotificationClient_QueryInterface(IMMNotificationClient *this,
528 REFIID riid, void **ppv)
530 if (IsEqualIID(riid, &IID_IUnknown)
531 || IsEqualIID(riid, &IID_IMMNotificationClient))
533 *ppv = this;
534 IUnknown_AddRef(this);
535 return S_OK;
537 else
539 *ppv = NULL;
540 return E_NOINTERFACE;
544 static STDMETHODIMP_(ULONG)
545 vlc_MMNotificationClient_AddRef(IMMNotificationClient *this)
547 aout_sys_t *sys = container_of(this, aout_sys_t, device_events);
548 return InterlockedIncrement(&sys->refs);
551 static STDMETHODIMP_(ULONG)
552 vlc_MMNotificationClient_Release(IMMNotificationClient *this)
554 aout_sys_t *sys = container_of(this, aout_sys_t, device_events);
555 return InterlockedDecrement(&sys->refs);
558 static STDMETHODIMP
559 vlc_MMNotificationClient_OnDefaultDeviceChange(IMMNotificationClient *this,
560 EDataFlow flow, ERole role,
561 LPCWSTR wid)
563 aout_sys_t *sys = container_of(this, aout_sys_t, device_events);
564 audio_output_t *aout = sys->aout;
566 if (flow != eRender)
567 return S_OK;
568 if (role != eConsole)
569 return S_OK;
571 msg_Dbg(aout, "default device changed: %ls", wid); /* TODO? migrate */
572 return S_OK;
575 static STDMETHODIMP
576 vlc_MMNotificationClient_OnDeviceAdded(IMMNotificationClient *this,
577 LPCWSTR wid)
579 aout_sys_t *sys = container_of(this, aout_sys_t, device_events);
580 audio_output_t *aout = sys->aout;
582 msg_Dbg(aout, "device %ls added", wid);
583 return DeviceUpdated(aout, wid);
586 static STDMETHODIMP
587 vlc_MMNotificationClient_OnDeviceRemoved(IMMNotificationClient *this,
588 LPCWSTR wid)
590 aout_sys_t *sys = container_of(this, aout_sys_t, device_events);
591 audio_output_t *aout = sys->aout;
592 char *id = FromWide(wid);
594 msg_Dbg(aout, "device %ls removed", wid);
595 if (unlikely(id == NULL))
596 return E_OUTOFMEMORY;
598 aout_HotplugReport(aout, id, NULL);
599 free(id);
600 return S_OK;
603 static STDMETHODIMP
604 vlc_MMNotificationClient_OnDeviceStateChanged(IMMNotificationClient *this,
605 LPCWSTR wid, DWORD state)
607 aout_sys_t *sys = container_of(this, aout_sys_t, device_events);
608 audio_output_t *aout = sys->aout;
610 switch (state) {
611 case DEVICE_STATE_UNPLUGGED:
612 msg_Dbg(aout, "device %ls state changed: unplugged", wid);
613 break;
614 case DEVICE_STATE_ACTIVE:
615 msg_Dbg(aout, "device %ls state changed: active", wid);
616 return DeviceUpdated(aout, wid);
617 case DEVICE_STATE_DISABLED:
618 msg_Dbg(aout, "device %ls state changed: disabled", wid);
619 break;
620 case DEVICE_STATE_NOTPRESENT:
621 msg_Dbg(aout, "device %ls state changed: not present", wid);
622 break;
623 default:
624 msg_Dbg(aout, "device %ls state changed: unknown: %08lx", wid, state);
625 return E_FAIL;
628 /* Unplugged, disabled or notpresent */
629 char *id = FromWide(wid);
630 if (unlikely(id == NULL))
631 return E_OUTOFMEMORY;
632 aout_HotplugReport(aout, id, NULL);
633 free(id);
635 return S_OK;
638 static STDMETHODIMP
639 vlc_MMNotificationClient_OnPropertyValueChanged(IMMNotificationClient *this,
640 LPCWSTR wid,
641 const PROPERTYKEY key)
643 aout_sys_t *sys = container_of(this, aout_sys_t, device_events);
644 audio_output_t *aout = sys->aout;
646 if (key.pid == PKEY_Device_FriendlyName.pid)
648 msg_Dbg(aout, "device %ls name changed", wid);
649 return DeviceUpdated(aout, wid);
651 return S_OK;
654 static const struct IMMNotificationClientVtbl vlc_MMNotificationClient =
656 vlc_MMNotificationClient_QueryInterface,
657 vlc_MMNotificationClient_AddRef,
658 vlc_MMNotificationClient_Release,
660 vlc_MMNotificationClient_OnDeviceStateChanged,
661 vlc_MMNotificationClient_OnDeviceAdded,
662 vlc_MMNotificationClient_OnDeviceRemoved,
663 vlc_MMNotificationClient_OnDefaultDeviceChange,
664 vlc_MMNotificationClient_OnPropertyValueChanged,
667 static int DevicesEnum(audio_output_t *aout, IMMDeviceEnumerator *it)
669 HRESULT hr;
670 IMMDeviceCollection *devs;
672 hr = IMMDeviceEnumerator_EnumAudioEndpoints(it, eRender,
673 DEVICE_STATE_ACTIVE, &devs);
674 if (FAILED(hr))
676 msg_Warn(aout, "cannot enumerate audio endpoints (error 0x%lx)", hr);
677 return -1;
680 UINT count;
681 hr = IMMDeviceCollection_GetCount(devs, &count);
682 if (FAILED(hr))
684 msg_Warn(aout, "cannot count audio endpoints (error 0x%lx)", hr);
685 count = 0;
688 unsigned n = 0;
690 for (UINT i = 0; i < count; i++)
692 IMMDevice *dev;
694 hr = IMMDeviceCollection_Item(devs, i, &dev);
695 if (FAILED(hr) || !DeviceIsRender(dev))
696 continue;
698 /* Unique device ID */
699 LPWSTR devid;
700 hr = IMMDevice_GetId(dev, &devid);
701 if (FAILED(hr))
703 IMMDevice_Release(dev);
704 continue;
707 DeviceHotplugReport(aout, devid, dev);
708 IMMDevice_Release(dev);
709 CoTaskMemFree(devid);
710 n++;
712 IMMDeviceCollection_Release(devs);
713 return n;
716 static int DeviceRequestLocked(audio_output_t *aout)
718 aout_sys_t *sys = aout->sys;
719 assert(sys->requested_device);
721 WakeConditionVariable(&sys->work);
722 while (sys->requested_device != NULL)
723 SleepConditionVariableCS(&sys->ready, &sys->lock, INFINITE);
725 if (sys->stream != NULL && sys->dev != NULL)
726 /* Request restart of stream with the new device */
727 aout_RestartRequest(aout, AOUT_RESTART_OUTPUT);
728 return (sys->dev != NULL) ? 0 : -1;
731 static int DeviceSelectLocked(audio_output_t *aout, const char *id)
733 aout_sys_t *sys = aout->sys;
734 assert(sys->requested_device == NULL);
736 if (id != NULL)
738 sys->requested_device = ToWide(id);
739 if (unlikely(sys->requested_device == NULL))
740 return -1;
742 else
743 sys->requested_device = default_device;
745 return DeviceRequestLocked(aout);
748 static int DeviceRestartLocked(audio_output_t *aout)
750 aout_sys_t *sys = aout->sys;
751 assert(sys->requested_device == NULL);
752 assert(sys->acquired_device != NULL);
753 sys->requested_device = sys->acquired_device;
754 return DeviceRequestLocked(aout);
757 static int DeviceSelect(audio_output_t *aout, const char *id)
759 EnterCriticalSection(&aout->sys->lock);
760 int ret = DeviceSelectLocked(aout, id);
761 LeaveCriticalSection(&aout->sys->lock);
762 return ret;
765 /*** Initialization / deinitialization **/
766 static wchar_t *var_InheritWide(vlc_object_t *obj, const char *name)
768 char *v8 = var_InheritString(obj, name);
769 if (v8 == NULL)
770 return NULL;
772 wchar_t *v16 = ToWide(v8);
773 free(v8);
774 return v16;
776 #define var_InheritWide(o,n) var_InheritWide(VLC_OBJECT(o),n)
778 /** MMDevice audio output thread.
779 * This thread takes cares of the audio session control. Inconveniently enough,
780 * the audio session control interface must:
781 * - be created and destroyed from the same thread, and
782 * - survive across VLC audio output calls.
783 * The only way to reconcile both requirements is a custom thread.
784 * The thread also ensure that the COM Multi-Thread Apartment is continuously
785 * referenced so that MMDevice objects are not destroyed early.
786 * Furthermore, VolumeSet() and MuteSet() may be called from a thread with a
787 * COM STA, so that it cannot access the COM MTA for audio controls.
789 static HRESULT MMSession(audio_output_t *aout, IMMDeviceEnumerator *it)
791 aout_sys_t *sys = aout->sys;
792 IAudioSessionManager *manager;
793 IAudioSessionControl *control;
794 ISimpleAudioVolume *volume;
795 IAudioEndpointVolume *endpoint;
796 void *pv;
797 HRESULT hr;
799 assert(sys->requested_device != NULL);
800 assert(sys->dev == NULL);
802 /* Yes, it's perfectly valid to request the same device, see Start()
803 * comments. */
804 if (sys->acquired_device != sys->requested_device
805 && sys->acquired_device != default_device)
806 free(sys->acquired_device);
807 if (sys->requested_device != default_device) /* Device selected explicitly */
809 msg_Dbg(aout, "using selected device %ls", sys->requested_device);
810 hr = IMMDeviceEnumerator_GetDevice(it, sys->requested_device, &sys->dev);
811 if (FAILED(hr))
812 msg_Err(aout, "cannot get selected device %ls (error 0x%lx)",
813 sys->requested_device, hr);
814 sys->acquired_device = sys->requested_device;
816 else
817 hr = AUDCLNT_E_DEVICE_INVALIDATED;
819 while (hr == AUDCLNT_E_DEVICE_INVALIDATED)
820 { /* Default device selected by policy and with stream routing.
821 * "Do not use eMultimedia" says MSDN. */
822 msg_Dbg(aout, "using default device");
823 hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(it, eRender,
824 eConsole, &sys->dev);
825 if (FAILED(hr))
826 msg_Err(aout, "cannot get default device (error 0x%lx)", hr);
827 else
828 sys->acquired_device = default_device;
831 sys->requested_device = NULL;
832 WakeConditionVariable(&sys->ready);
834 if (SUCCEEDED(hr))
835 { /* Report actual device */
836 LPWSTR wdevid;
838 hr = IMMDevice_GetId(sys->dev, &wdevid);
839 if (SUCCEEDED(hr))
841 char *id = FromWide(wdevid);
842 CoTaskMemFree(wdevid);
843 if (likely(id != NULL))
845 aout_DeviceReport(aout, id);
846 free(id);
850 else
852 msg_Err(aout, "cannot get device identifier (error 0x%lx)", hr);
853 return hr;
856 /* Create session manager (for controls even w/o active audio client) */
857 hr = IMMDevice_Activate(sys->dev, &IID_IAudioSessionManager,
858 CLSCTX_ALL, NULL, &pv);
859 manager = pv;
860 if (SUCCEEDED(hr))
862 LPCGUID guid = &GUID_VLC_AUD_OUT;
864 /* Register session control */
865 hr = IAudioSessionManager_GetAudioSessionControl(manager, guid, 0,
866 &control);
867 if (SUCCEEDED(hr))
869 wchar_t *ua = var_InheritWide(aout, "user-agent");
870 IAudioSessionControl_SetDisplayName(control, ua, NULL);
871 free(ua);
873 IAudioSessionControl_RegisterAudioSessionNotification(control,
874 &sys->session_events);
876 else
877 msg_Err(aout, "cannot get session control (error 0x%lx)", hr);
879 hr = IAudioSessionManager_GetSimpleAudioVolume(manager, guid, FALSE,
880 &volume);
881 if (FAILED(hr))
882 msg_Err(aout, "cannot get simple volume (error 0x%lx)", hr);
884 /* Try to get version 2 (Windows 7) of the manager & control */
885 wchar_t *siid = NULL;
887 hr = IAudioSessionManager_QueryInterface(manager,
888 &IID_IAudioSessionControl2, &pv);
889 if (SUCCEEDED(hr))
891 IAudioSessionControl2 *c2 = pv;
893 IAudioSessionControl2_SetDuckingPreference(c2, FALSE);
894 hr = IAudioSessionControl2_GetSessionInstanceIdentifier(c2, &siid);
895 if (FAILED(hr))
896 siid = NULL;
897 IAudioSessionControl2_Release(c2);
899 else
900 msg_Dbg(aout, "version 2 session control unavailable");
902 hr = IAudioSessionManager_QueryInterface(manager,
903 &IID_IAudioSessionManager2, &pv);
904 if (SUCCEEDED(hr))
906 IAudioSessionManager2 *m2 = pv;
908 IAudioSessionManager2_RegisterDuckNotification(m2, siid,
909 &sys->duck);
910 IAudioSessionManager2_Release(m2);
912 else
913 msg_Dbg(aout, "version 2 session management unavailable");
915 CoTaskMemFree(siid);
917 else
919 msg_Err(aout, "cannot activate session manager (error 0x%lx)", hr);
920 control = NULL;
921 volume = NULL;
924 hr = IMMDevice_Activate(sys->dev, &IID_IAudioEndpointVolume,
925 CLSCTX_ALL, NULL, &pv);
926 endpoint = pv;
927 if (SUCCEEDED(hr))
929 float min, max, inc;
931 hr = IAudioEndpointVolume_GetVolumeRange(endpoint, &min, &max, &inc);
932 if (SUCCEEDED(hr))
933 msg_Dbg(aout, "volume from %+f dB to %+f dB with %f dB increments",
934 min, max, inc);
935 else
936 msg_Err(aout, "cannot get volume range (error 0x%lx)", hr);
938 else
939 msg_Err(aout, "cannot activate endpoint volume (error %lx)", hr);
941 /* Main loop (adjust volume as long as device is unchanged) */
942 while (sys->requested_device == NULL)
944 if (volume != NULL)
946 float level;
948 hr = ISimpleAudioVolume_GetMasterVolume(volume, &level);
949 if (SUCCEEDED(hr))
950 aout_VolumeReport(aout, cbrtf(level * sys->gain));
951 else
952 msg_Err(aout, "cannot get master volume (error 0x%lx)", hr);
954 level = sys->requested_volume;
955 if (level >= 0.f)
957 hr = ISimpleAudioVolume_SetMasterVolume(volume, level, NULL);
958 if (FAILED(hr))
959 msg_Err(aout, "cannot set master volume (error 0x%lx)",
960 hr);
962 sys->requested_volume = -1.f;
964 BOOL mute;
966 hr = ISimpleAudioVolume_GetMute(volume, &mute);
967 if (SUCCEEDED(hr))
968 aout_MuteReport(aout, mute != FALSE);
969 else
970 msg_Err(aout, "cannot get mute (error 0x%lx)", hr);
972 if (sys->requested_mute >= 0)
974 mute = sys->requested_mute ? TRUE : FALSE;
976 hr = ISimpleAudioVolume_SetMute(volume, mute, NULL);
977 if (FAILED(hr))
978 msg_Err(aout, "cannot set mute (error 0x%lx)", hr);
980 sys->requested_mute = -1;
983 SleepConditionVariableCS(&sys->work, &sys->lock, INFINITE);
985 LeaveCriticalSection(&sys->lock);
987 if (endpoint != NULL)
988 IAudioEndpointVolume_Release(endpoint);
990 if (manager != NULL)
991 { /* Deregister callbacks *without* the lock */
992 hr = IAudioSessionManager_QueryInterface(manager,
993 &IID_IAudioSessionManager2, &pv);
994 if (SUCCEEDED(hr))
996 IAudioSessionManager2 *m2 = pv;
998 IAudioSessionManager2_UnregisterDuckNotification(m2, &sys->duck);
999 IAudioSessionManager2_Release(m2);
1002 if (volume != NULL)
1003 ISimpleAudioVolume_Release(volume);
1005 if (control != NULL)
1007 IAudioSessionControl_UnregisterAudioSessionNotification(control,
1008 &sys->session_events);
1009 IAudioSessionControl_Release(control);
1012 IAudioSessionManager_Release(manager);
1015 EnterCriticalSection(&sys->lock);
1016 IMMDevice_Release(sys->dev);
1017 sys->dev = NULL;
1018 return S_OK;
1021 static void *MMThread(void *data)
1023 audio_output_t *aout = data;
1024 aout_sys_t *sys = aout->sys;
1025 IMMDeviceEnumerator *it = sys->it;
1027 EnterMTA();
1028 IMMDeviceEnumerator_RegisterEndpointNotificationCallback(it,
1029 &sys->device_events);
1030 DevicesEnum(aout, it);
1032 EnterCriticalSection(&sys->lock);
1035 if (sys->requested_device == NULL || FAILED(MMSession(aout, it)))
1036 SleepConditionVariableCS(&sys->work, &sys->lock, INFINITE);
1037 while (sys->it != NULL);
1039 LeaveCriticalSection(&sys->lock);
1041 IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(it,
1042 &sys->device_events);
1043 IMMDeviceEnumerator_Release(it);
1044 LeaveMTA();
1045 return NULL;
1049 * Callback for aout_stream_t to create a stream on the device.
1050 * This can instantiate an IAudioClient or IDirectSound(8) object.
1052 static HRESULT ActivateDevice(void *opaque, REFIID iid, PROPVARIANT *actparms,
1053 void **restrict pv)
1055 IMMDevice *dev = opaque;
1056 return IMMDevice_Activate(dev, iid, CLSCTX_ALL, actparms, pv);
1059 static int aout_stream_Start(void *func, va_list ap)
1061 aout_stream_start_t start = func;
1062 aout_stream_t *s = va_arg(ap, aout_stream_t *);
1063 audio_sample_format_t *fmt = va_arg(ap, audio_sample_format_t *);
1064 HRESULT *hr = va_arg(ap, HRESULT *);
1066 *hr = start(s, fmt, &GUID_VLC_AUD_OUT);
1067 if (*hr == AUDCLNT_E_DEVICE_INVALIDATED)
1068 return VLC_ETIMEOUT;
1069 return SUCCEEDED(*hr) ? VLC_SUCCESS : VLC_EGENERIC;
1072 static void aout_stream_Stop(void *func, va_list ap)
1074 aout_stream_stop_t stop = func;
1075 aout_stream_t *s = va_arg(ap, aout_stream_t *);
1077 stop(s);
1080 static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
1082 aout_sys_t *sys = aout->sys;
1084 if (sys->dev == NULL)
1085 return -1;
1087 aout_stream_t *s = vlc_object_create(aout, sizeof (*s));
1088 if (unlikely(s == NULL))
1089 return -1;
1091 s->owner.activate = ActivateDevice;
1093 EnterMTA();
1094 EnterCriticalSection(&sys->lock);
1095 for (;;)
1097 HRESULT hr;
1098 s->owner.device = sys->dev;
1100 sys->module = vlc_module_load(s, "aout stream", "$mmdevice-backend",
1101 false, aout_stream_Start, s, fmt, &hr);
1103 int ret = -1;
1104 if (hr == AUDCLNT_E_ALREADY_INITIALIZED)
1106 /* From MSDN: "If the initial call to Initialize fails, subsequent
1107 * Initialize calls might fail and return error code
1108 * E_ALREADY_INITIALIZED, even though the interface has not been
1109 * initialized. If this occurs, release the IAudioClient interface
1110 * and obtain a new IAudioClient interface from the MMDevice API
1111 * before calling Initialize again."
1113 * Therefore, request to MMThread the same device and try again. */
1115 ret = DeviceRestartLocked(aout);
1117 else if (hr == AUDCLNT_E_DEVICE_INVALIDATED)
1119 /* The audio endpoint device has been unplugged, request to
1120 * MMThread the default device and try again. */
1122 ret = DeviceSelectLocked(aout, NULL);
1124 if (ret != 0)
1125 break;
1128 IPropertyStore *props;
1129 HRESULT hr = IMMDevice_OpenPropertyStore(sys->dev, STGM_READ, &props);
1130 if (SUCCEEDED(hr))
1132 PROPVARIANT v;
1133 PropVariantInit(&v);
1134 hr = IPropertyStore_GetValue(props, &PKEY_AudioEndpoint_FormFactor, &v);
1135 if (SUCCEEDED(hr))
1137 switch (v.uintVal)
1139 case Headphones:
1140 case Headset:
1141 aout->current_sink_info.headphones = true;
1142 break;
1144 PropVariantClear(&v);
1146 IPropertyStore_Release(props);
1149 LeaveCriticalSection(&sys->lock);
1150 LeaveMTA();
1152 if (sys->module == NULL)
1154 vlc_object_release(s);
1155 return -1;
1158 assert (sys->stream == NULL);
1159 sys->stream = s;
1160 aout_GainRequest(aout, sys->gain);
1161 return 0;
1164 static void Stop(audio_output_t *aout)
1166 aout_sys_t *sys = aout->sys;
1168 assert(sys->stream != NULL);
1170 EnterMTA();
1171 vlc_module_unload(sys->stream, sys->module, aout_stream_Stop, sys->stream);
1172 LeaveMTA();
1174 vlc_object_release(sys->stream);
1175 sys->stream = NULL;
1178 static int Open(vlc_object_t *obj)
1180 audio_output_t *aout = (audio_output_t *)obj;
1182 aout_sys_t *sys = malloc(sizeof (*sys));
1183 if (unlikely(sys == NULL))
1184 return VLC_ENOMEM;
1186 aout->sys = sys;
1187 sys->stream = NULL;
1188 sys->aout = aout;
1189 sys->it = NULL;
1190 sys->dev = NULL;
1191 sys->device_events.lpVtbl = &vlc_MMNotificationClient;
1192 sys->session_events.lpVtbl = &vlc_AudioSessionEvents;
1193 sys->duck.lpVtbl = &vlc_AudioVolumeDuckNotification;
1194 sys->refs = 1;
1195 sys->ducks = 0;
1197 sys->gain = 1.f;
1198 sys->requested_device = default_device;
1199 sys->requested_volume = -1.f;
1200 sys->requested_mute = -1;
1201 sys->acquired_device = NULL;
1202 InitializeCriticalSection(&sys->lock);
1203 InitializeConditionVariable(&sys->work);
1204 InitializeConditionVariable(&sys->ready);
1206 /* Initialize MMDevice API */
1207 if (TryEnterMTA(aout))
1208 goto error;
1210 void *pv;
1211 HRESULT hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
1212 &IID_IMMDeviceEnumerator, &pv);
1213 if (FAILED(hr))
1215 LeaveMTA();
1216 msg_Dbg(aout, "cannot create device enumerator (error 0x%lx)", hr);
1217 goto error;
1219 sys->it = pv;
1221 if (vlc_clone(&sys->thread, MMThread, aout, VLC_THREAD_PRIORITY_LOW))
1223 IMMDeviceEnumerator_Release(sys->it);
1224 LeaveMTA();
1225 goto error;
1228 EnterCriticalSection(&sys->lock);
1229 while (sys->requested_device != NULL)
1230 SleepConditionVariableCS(&sys->ready, &sys->lock, INFINITE);
1231 LeaveCriticalSection(&sys->lock);
1232 LeaveMTA(); /* Leave MTA after thread has entered MTA */
1234 aout->start = Start;
1235 aout->stop = Stop;
1236 aout->time_get = TimeGet;
1237 aout->play = Play;
1238 aout->pause = Pause;
1239 aout->flush = Flush;
1240 aout->volume_set = VolumeSet;
1241 aout->mute_set = MuteSet;
1242 aout->device_select = DeviceSelect;
1243 return VLC_SUCCESS;
1245 error:
1246 DeleteCriticalSection(&sys->lock);
1247 free(sys);
1248 return VLC_EGENERIC;
1251 static void Close(vlc_object_t *obj)
1253 audio_output_t *aout = (audio_output_t *)obj;
1254 aout_sys_t *sys = aout->sys;
1256 EnterCriticalSection(&sys->lock);
1257 sys->requested_device = default_device; /* break out of MMSession() loop */
1258 sys->it = NULL; /* break out of MMThread() loop */
1259 WakeConditionVariable(&sys->work);
1260 LeaveCriticalSection(&sys->lock);
1262 vlc_join(sys->thread, NULL);
1263 DeleteCriticalSection(&sys->lock);
1264 free(sys);
1267 vlc_module_begin()
1268 set_shortname("MMDevice")
1269 set_description(N_("Windows Multimedia Device output"))
1270 set_capability("audio output", 150)
1271 set_category(CAT_AUDIO)
1272 set_subcategory(SUBCAT_AUDIO_AOUT)
1273 set_callbacks(Open, Close)
1274 add_module("mmdevice-backend", "aout stream", "any",
1275 N_("Output back-end"), N_("Audio output back-end interface."),
1276 true)
1277 vlc_module_end()