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 *****************************************************************************/
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>
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
);
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
)))
68 static void LeaveMTA(void)
73 static wchar_t default_device
[1] = L
"";
77 aout_stream_t
*stream
; /**< Underlying audio output stream */
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
;
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
;
130 hr
= aout_stream_TimeGet(sys
->stream
, delay
);
133 return SUCCEEDED(hr
) ? 0 : -1;
136 static void Play(audio_output_t
*aout
, block_t
*block
)
138 aout_sys_t
*sys
= aout
->sys
;
142 hr
= aout_stream_Play(sys
->stream
, block
);
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
;
154 hr
= aout_stream_Pause(sys
->stream
, paused
);
157 vlc_FromHR(aout
, hr
);
161 static void Flush(audio_output_t
*aout
, bool wait
)
163 aout_sys_t
*sys
= aout
->sys
;
167 hr
= aout_stream_Flush(sys
->stream
, wait
);
170 vlc_FromHR(aout
, hr
);
173 static int VolumeSet(audio_output_t
*aout
, float vol
)
175 aout_sys_t
*sys
= aout
->sys
;
178 vol
= vol
* vol
* vol
; /* ISimpleAudioVolume is tapered linearly. */
186 aout_GainRequest(aout
, gain
);
188 EnterCriticalSection(&sys
->lock
);
190 sys
->requested_volume
= vol
;
191 WakeConditionVariable(&sys
->work
);
192 LeaveCriticalSection(&sys
->lock
);
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
);
207 /*** Audio session events ***/
209 vlc_AudioSessionEvents_QueryInterface(IAudioSessionEvents
*this, REFIID riid
,
212 if (IsEqualIID(riid
, &IID_IUnknown
)
213 || IsEqualIID(riid
, &IID_IAudioSessionEvents
))
216 IUnknown_AddRef(this);
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
);
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
);
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
);
265 vlc_AudioSessionEvents_OnSimpleVolumeChanged(IAudioSessionEvents
*this,
266 float vol
, BOOL mute
,
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
);
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
,
293 msg_Dbg(aout
, "%lu channels volume changed", count
);
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");
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
);
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
;
333 case DisconnectReasonDeviceRemoval
:
334 msg_Warn(aout
, "session disconnected: %s", "device removed");
336 case DisconnectReasonServerShutdown
:
337 msg_Err(aout
, "session disconnected: %s", "service stopped");
339 case DisconnectReasonFormatChanged
:
340 msg_Warn(aout
, "session disconnected: %s", "format changed");
342 case DisconnectReasonSessionLogoff
:
343 msg_Err(aout
, "session disconnected: %s", "user logged off");
345 case DisconnectReasonSessionDisconnected
:
346 msg_Err(aout
, "session disconnected: %s", "session disconnected");
348 case DisconnectReasonExclusiveModeOverride
:
349 msg_Err(aout
, "session disconnected: %s", "stream overriden");
352 msg_Warn(aout
, "session disconnected: unknown reason %d", reason
);
355 /* NOTE: audio decoder thread should get invalidated device and restart */
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
,
375 vlc_AudioVolumeDuckNotification_QueryInterface(
376 IAudioVolumeDuckNotification
*this, REFIID riid
, void **ppv
)
378 if (IsEqualIID(riid
, &IID_IUnknown
)
379 || IsEqualIID(riid
, &IID_IAudioVolumeDuckNotification
))
382 IUnknown_AddRef(this);
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
);
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
);
415 aout_PolicyReport(aout
, true);
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
);
428 aout_PolicyReport(aout
, sys
->ducks
!= 0);
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
,
449 IPropertyStore
*props
;
454 char *id
= FromWide(wid
);
455 if (unlikely(id
== NULL
))
458 hr
= IMMDevice_OpenPropertyStore(dev
, STGM_READ
, &props
);
466 hr
= IPropertyStore_GetValue(props
, &PKEY_Device_FriendlyName
, &v
);
469 name
= FromWide(v
.pwszVal
);
470 PropVariantClear(&v
);
475 IPropertyStore_Release(props
);
476 aout_HotplugReport(aout
, id
, name
);
484 /** Checks that a device is an output device */
485 static bool DeviceIsRender(IMMDevice
*dev
)
489 if (FAILED(IMMDevice_QueryInterface(dev
, &IID_IMMEndpoint
, &pv
)))
492 IMMEndpoint
*ep
= pv
;
494 HRESULT hr
= IMMEndpoint_GetDataFlow(ep
, &flow
);
496 IMMEndpoint_Release(ep
);
497 if (FAILED(hr
) || flow
!= eRender
)
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
;
511 hr
= IMMDeviceEnumerator_GetDevice(sys
->it
, wid
, &dev
);
515 if (!DeviceIsRender(dev
))
517 IMMDevice_Release(dev
);
521 DeviceHotplugReport(aout
, wid
, dev
);
522 IMMDevice_Release(dev
);
527 vlc_MMNotificationClient_QueryInterface(IMMNotificationClient
*this,
528 REFIID riid
, void **ppv
)
530 if (IsEqualIID(riid
, &IID_IUnknown
)
531 || IsEqualIID(riid
, &IID_IMMNotificationClient
))
534 IUnknown_AddRef(this);
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
);
559 vlc_MMNotificationClient_OnDefaultDeviceChange(IMMNotificationClient
*this,
560 EDataFlow flow
, ERole role
,
563 aout_sys_t
*sys
= container_of(this, aout_sys_t
, device_events
);
564 audio_output_t
*aout
= sys
->aout
;
568 if (role
!= eConsole
)
571 msg_Dbg(aout
, "default device changed: %ls", wid
); /* TODO? migrate */
576 vlc_MMNotificationClient_OnDeviceAdded(IMMNotificationClient
*this,
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
);
587 vlc_MMNotificationClient_OnDeviceRemoved(IMMNotificationClient
*this,
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
);
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
;
611 case DEVICE_STATE_UNPLUGGED
:
612 msg_Dbg(aout
, "device %ls state changed: unplugged", wid
);
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
);
620 case DEVICE_STATE_NOTPRESENT
:
621 msg_Dbg(aout
, "device %ls state changed: not present", wid
);
624 msg_Dbg(aout
, "device %ls state changed: unknown: %08lx", wid
, state
);
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
);
639 vlc_MMNotificationClient_OnPropertyValueChanged(IMMNotificationClient
*this,
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
);
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
)
670 IMMDeviceCollection
*devs
;
672 hr
= IMMDeviceEnumerator_EnumAudioEndpoints(it
, eRender
,
673 DEVICE_STATE_ACTIVE
, &devs
);
676 msg_Warn(aout
, "cannot enumerate audio endpoints (error 0x%lx)", hr
);
681 hr
= IMMDeviceCollection_GetCount(devs
, &count
);
684 msg_Warn(aout
, "cannot count audio endpoints (error 0x%lx)", hr
);
690 for (UINT i
= 0; i
< count
; i
++)
694 hr
= IMMDeviceCollection_Item(devs
, i
, &dev
);
695 if (FAILED(hr
) || !DeviceIsRender(dev
))
698 /* Unique device ID */
700 hr
= IMMDevice_GetId(dev
, &devid
);
703 IMMDevice_Release(dev
);
707 DeviceHotplugReport(aout
, devid
, dev
);
708 IMMDevice_Release(dev
);
709 CoTaskMemFree(devid
);
712 IMMDeviceCollection_Release(devs
);
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
);
738 sys
->requested_device
= ToWide(id
);
739 if (unlikely(sys
->requested_device
== NULL
))
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
);
765 /*** Initialization / deinitialization **/
766 static wchar_t *var_InheritWide(vlc_object_t
*obj
, const char *name
)
768 char *v8
= var_InheritString(obj
, name
);
772 wchar_t *v16
= ToWide(v8
);
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
;
799 assert(sys
->requested_device
!= NULL
);
800 assert(sys
->dev
== NULL
);
802 /* Yes, it's perfectly valid to request the same device, see Start()
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
);
812 msg_Err(aout
, "cannot get selected device %ls (error 0x%lx)",
813 sys
->requested_device
, hr
);
814 sys
->acquired_device
= sys
->requested_device
;
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
);
826 msg_Err(aout
, "cannot get default device (error 0x%lx)", hr
);
828 sys
->acquired_device
= default_device
;
831 sys
->requested_device
= NULL
;
832 WakeConditionVariable(&sys
->ready
);
835 { /* Report actual device */
838 hr
= IMMDevice_GetId(sys
->dev
, &wdevid
);
841 char *id
= FromWide(wdevid
);
842 CoTaskMemFree(wdevid
);
843 if (likely(id
!= NULL
))
845 aout_DeviceReport(aout
, id
);
852 msg_Err(aout
, "cannot get device identifier (error 0x%lx)", 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
);
862 LPCGUID guid
= &GUID_VLC_AUD_OUT
;
864 /* Register session control */
865 hr
= IAudioSessionManager_GetAudioSessionControl(manager
, guid
, 0,
869 wchar_t *ua
= var_InheritWide(aout
, "user-agent");
870 IAudioSessionControl_SetDisplayName(control
, ua
, NULL
);
873 IAudioSessionControl_RegisterAudioSessionNotification(control
,
874 &sys
->session_events
);
877 msg_Err(aout
, "cannot get session control (error 0x%lx)", hr
);
879 hr
= IAudioSessionManager_GetSimpleAudioVolume(manager
, guid
, FALSE
,
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
);
891 IAudioSessionControl2
*c2
= pv
;
893 IAudioSessionControl2_SetDuckingPreference(c2
, FALSE
);
894 hr
= IAudioSessionControl2_GetSessionInstanceIdentifier(c2
, &siid
);
897 IAudioSessionControl2_Release(c2
);
900 msg_Dbg(aout
, "version 2 session control unavailable");
902 hr
= IAudioSessionManager_QueryInterface(manager
,
903 &IID_IAudioSessionManager2
, &pv
);
906 IAudioSessionManager2
*m2
= pv
;
908 IAudioSessionManager2_RegisterDuckNotification(m2
, siid
,
910 IAudioSessionManager2_Release(m2
);
913 msg_Dbg(aout
, "version 2 session management unavailable");
919 msg_Err(aout
, "cannot activate session manager (error 0x%lx)", hr
);
924 hr
= IMMDevice_Activate(sys
->dev
, &IID_IAudioEndpointVolume
,
925 CLSCTX_ALL
, NULL
, &pv
);
931 hr
= IAudioEndpointVolume_GetVolumeRange(endpoint
, &min
, &max
, &inc
);
933 msg_Dbg(aout
, "volume from %+f dB to %+f dB with %f dB increments",
936 msg_Err(aout
, "cannot get volume range (error 0x%lx)", hr
);
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
)
948 hr
= ISimpleAudioVolume_GetMasterVolume(volume
, &level
);
950 aout_VolumeReport(aout
, cbrtf(level
* sys
->gain
));
952 msg_Err(aout
, "cannot get master volume (error 0x%lx)", hr
);
954 level
= sys
->requested_volume
;
957 hr
= ISimpleAudioVolume_SetMasterVolume(volume
, level
, NULL
);
959 msg_Err(aout
, "cannot set master volume (error 0x%lx)",
962 sys
->requested_volume
= -1.f
;
966 hr
= ISimpleAudioVolume_GetMute(volume
, &mute
);
968 aout_MuteReport(aout
, mute
!= FALSE
);
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
);
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
);
991 { /* Deregister callbacks *without* the lock */
992 hr
= IAudioSessionManager_QueryInterface(manager
,
993 &IID_IAudioSessionManager2
, &pv
);
996 IAudioSessionManager2
*m2
= pv
;
998 IAudioSessionManager2_UnregisterDuckNotification(m2
, &sys
->duck
);
999 IAudioSessionManager2_Release(m2
);
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
);
1021 static void *MMThread(void *data
)
1023 audio_output_t
*aout
= data
;
1024 aout_sys_t
*sys
= aout
->sys
;
1025 IMMDeviceEnumerator
*it
= sys
->it
;
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
);
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
,
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
*);
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
)
1087 aout_stream_t
*s
= vlc_object_create(aout
, sizeof (*s
));
1088 if (unlikely(s
== NULL
))
1091 s
->owner
.activate
= ActivateDevice
;
1094 EnterCriticalSection(&sys
->lock
);
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
);
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
);
1128 IPropertyStore
*props
;
1129 HRESULT hr
= IMMDevice_OpenPropertyStore(sys
->dev
, STGM_READ
, &props
);
1133 PropVariantInit(&v
);
1134 hr
= IPropertyStore_GetValue(props
, &PKEY_AudioEndpoint_FormFactor
, &v
);
1141 aout
->current_sink_info
.headphones
= true;
1144 PropVariantClear(&v
);
1146 IPropertyStore_Release(props
);
1149 LeaveCriticalSection(&sys
->lock
);
1152 if (sys
->module
== NULL
)
1154 vlc_object_release(s
);
1158 assert (sys
->stream
== NULL
);
1160 aout_GainRequest(aout
, sys
->gain
);
1164 static void Stop(audio_output_t
*aout
)
1166 aout_sys_t
*sys
= aout
->sys
;
1168 assert(sys
->stream
!= NULL
);
1171 vlc_module_unload(sys
->stream
, sys
->module
, aout_stream_Stop
, sys
->stream
);
1174 vlc_object_release(sys
->stream
);
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
))
1191 sys
->device_events
.lpVtbl
= &vlc_MMNotificationClient
;
1192 sys
->session_events
.lpVtbl
= &vlc_AudioSessionEvents
;
1193 sys
->duck
.lpVtbl
= &vlc_AudioVolumeDuckNotification
;
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
))
1211 HRESULT hr
= CoCreateInstance(&CLSID_MMDeviceEnumerator
, NULL
, CLSCTX_ALL
,
1212 &IID_IMMDeviceEnumerator
, &pv
);
1216 msg_Dbg(aout
, "cannot create device enumerator (error 0x%lx)", hr
);
1221 if (vlc_clone(&sys
->thread
, MMThread
, aout
, VLC_THREAD_PRIORITY_LOW
))
1223 IMMDeviceEnumerator_Release(sys
->it
);
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
;
1236 aout
->time_get
= TimeGet
;
1238 aout
->pause
= Pause
;
1239 aout
->flush
= Flush
;
1240 aout
->volume_set
= VolumeSet
;
1241 aout
->mute_set
= MuteSet
;
1242 aout
->device_select
= DeviceSelect
;
1246 DeleteCriticalSection(&sys
->lock
);
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
);
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."),