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_atomic.h>
41 #include <vlc_plugin.h>
43 #include <vlc_charset.h>
44 #include <vlc_modules.h>
45 #include "audio_output/mmdevice.h"
47 DEFINE_GUID (GUID_VLC_AUD_OUT
, 0x4533f59d, 0x59ee, 0x00c6,
48 0xad, 0xb2, 0xc6, 0x8b, 0x50, 0x1a, 0x66, 0x55);
50 static int TryEnterMTA(vlc_object_t
*obj
)
52 HRESULT hr
= CoInitializeEx(NULL
, COINIT_MULTITHREADED
);
53 if (unlikely(FAILED(hr
)))
55 msg_Err (obj
, "cannot initialize COM (error 0x%lx)", hr
);
60 #define TryEnterMTA(o) TryEnterMTA(VLC_OBJECT(o))
62 static void EnterMTA(void)
64 HRESULT hr
= CoInitializeEx(NULL
, COINIT_MULTITHREADED
);
65 if (unlikely(FAILED(hr
)))
69 static void LeaveMTA(void)
74 static wchar_t default_device
[1] = L
"";
75 static char default_device_b
[1] = "";
79 aout_stream_t
*stream
; /**< Underlying audio output stream */
82 IMMDeviceEnumerator
*it
; /**< Device enumerator, NULL when exiting */
83 IMMDevice
*dev
; /**< Selected output device, NULL if none */
85 struct IMMNotificationClient device_events
;
86 struct IAudioSessionEvents session_events
;
87 struct IAudioVolumeDuckNotification duck
;
91 float gain
; /**< Current software gain volume */
93 wchar_t *requested_device
; /**< Requested device identifier, NULL if none */
94 float requested_volume
; /**< Requested volume, negative if none */
95 signed char requested_mute
; /**< Requested mute, negative if none */
96 wchar_t *acquired_device
; /**< Acquired device identifier, NULL if none */
97 bool request_device_restart
;
98 CRITICAL_SECTION lock
;
99 CONDITION_VARIABLE work
;
100 CONDITION_VARIABLE ready
;
101 vlc_thread_t thread
; /**< Thread for audio session control */
104 /* NOTE: The Core Audio API documentation totally fails to specify the thread
105 * safety (or lack thereof) of the interfaces. This code takes the most
106 * restrictive assumption: no thread safety. The background thread (MMThread)
107 * only runs at specified times, namely between the device_ready and
108 * device_changed events (effectively a thread synchronization barrier, but
109 * only Windows 8 natively provides such a primitive).
111 * The audio output owner (i.e. the audio output core) is responsible for
112 * serializing callbacks. This code only needs to be concerned with
113 * synchronization between the set of audio output callbacks, MMThread()
114 * and (trivially) the device and session notifications. */
116 static int DeviceSelect(audio_output_t
*, const char *);
117 static int vlc_FromHR(audio_output_t
*aout
, HRESULT hr
)
119 /* Select the default device (and restart) on unplug */
120 if (unlikely(hr
== AUDCLNT_E_DEVICE_INVALIDATED
||
121 hr
== AUDCLNT_E_RESOURCES_INVALIDATED
))
122 DeviceSelect(aout
, NULL
);
123 return SUCCEEDED(hr
) ? 0 : -1;
126 /*** VLC audio output callbacks ***/
127 static int TimeGet(audio_output_t
*aout
, vlc_tick_t
*restrict delay
)
129 aout_sys_t
*sys
= aout
->sys
;
133 hr
= aout_stream_TimeGet(sys
->stream
, delay
);
136 return SUCCEEDED(hr
) ? 0 : -1;
139 static void Play(audio_output_t
*aout
, block_t
*block
, vlc_tick_t date
)
141 aout_sys_t
*sys
= aout
->sys
;
145 hr
= aout_stream_Play(sys
->stream
, block
);
148 vlc_FromHR(aout
, hr
);
152 static void Pause(audio_output_t
*aout
, bool paused
, vlc_tick_t date
)
154 aout_sys_t
*sys
= aout
->sys
;
158 hr
= aout_stream_Pause(sys
->stream
, paused
);
161 vlc_FromHR(aout
, hr
);
165 static void Flush(audio_output_t
*aout
, bool wait
)
167 aout_sys_t
*sys
= aout
->sys
;
171 hr
= aout_stream_Flush(sys
->stream
, wait
);
174 vlc_FromHR(aout
, hr
);
177 static int VolumeSetLocked(audio_output_t
*aout
, float vol
)
179 aout_sys_t
*sys
= aout
->sys
;
182 vol
= vol
* vol
* vol
; /* ISimpleAudioVolume is tapered linearly. */
190 aout_GainRequest(aout
, gain
);
193 sys
->requested_volume
= vol
;
197 static int VolumeSet(audio_output_t
*aout
, float vol
)
199 aout_sys_t
*sys
= aout
->sys
;
201 EnterCriticalSection(&sys
->lock
);
202 int ret
= VolumeSetLocked(aout
, vol
);
203 WakeConditionVariable(&sys
->work
);
204 LeaveCriticalSection(&sys
->lock
);
208 static int MuteSet(audio_output_t
*aout
, bool mute
)
210 aout_sys_t
*sys
= aout
->sys
;
212 EnterCriticalSection(&sys
->lock
);
213 sys
->requested_mute
= mute
;
214 WakeConditionVariable(&sys
->work
);
215 LeaveCriticalSection(&sys
->lock
);
219 /*** Audio session events ***/
221 vlc_AudioSessionEvents_QueryInterface(IAudioSessionEvents
*this, REFIID riid
,
224 if (IsEqualIID(riid
, &IID_IUnknown
)
225 || IsEqualIID(riid
, &IID_IAudioSessionEvents
))
228 IUnknown_AddRef(this);
234 return E_NOINTERFACE
;
238 static STDMETHODIMP_(ULONG
)
239 vlc_AudioSessionEvents_AddRef(IAudioSessionEvents
*this)
241 aout_sys_t
*sys
= container_of(this, aout_sys_t
, session_events
);
242 return InterlockedIncrement(&sys
->refs
);
245 static STDMETHODIMP_(ULONG
)
246 vlc_AudioSessionEvents_Release(IAudioSessionEvents
*this)
248 aout_sys_t
*sys
= container_of(this, aout_sys_t
, session_events
);
249 return InterlockedDecrement(&sys
->refs
);
253 vlc_AudioSessionEvents_OnDisplayNameChanged(IAudioSessionEvents
*this,
254 LPCWSTR wname
, 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
, "display name changed: %ls", wname
);
265 vlc_AudioSessionEvents_OnIconPathChanged(IAudioSessionEvents
*this,
266 LPCWSTR wpath
, LPCGUID ctx
)
268 aout_sys_t
*sys
= container_of(this, aout_sys_t
, session_events
);
269 audio_output_t
*aout
= sys
->aout
;
271 msg_Dbg(aout
, "icon path changed: %ls", wpath
);
277 vlc_AudioSessionEvents_OnSimpleVolumeChanged(IAudioSessionEvents
*this,
278 float vol
, BOOL mute
,
281 aout_sys_t
*sys
= container_of(this, aout_sys_t
, session_events
);
282 audio_output_t
*aout
= sys
->aout
;
284 msg_Dbg(aout
, "simple volume changed: %f, muting %sabled", vol
,
285 mute
? "en" : "dis");
286 EnterCriticalSection(&sys
->lock
);
287 WakeConditionVariable(&sys
->work
); /* implicit state: vol & mute */
288 LeaveCriticalSection(&sys
->lock
);
294 vlc_AudioSessionEvents_OnChannelVolumeChanged(IAudioSessionEvents
*this,
295 DWORD count
, float *vols
,
296 DWORD changed
, LPCGUID ctx
)
298 aout_sys_t
*sys
= container_of(this, aout_sys_t
, session_events
);
299 audio_output_t
*aout
= sys
->aout
;
301 if (changed
!= (DWORD
)-1)
302 msg_Dbg(aout
, "channel volume %lu of %lu changed: %f", changed
, count
,
305 msg_Dbg(aout
, "%lu channels volume changed", count
);
312 vlc_AudioSessionEvents_OnGroupingParamChanged(IAudioSessionEvents
*this,
313 LPCGUID param
, LPCGUID ctx
)
316 aout_sys_t
*sys
= container_of(this, aout_sys_t
, session_events
);
317 audio_output_t
*aout
= sys
->aout
;
319 msg_Dbg(aout
, "grouping parameter changed");
326 vlc_AudioSessionEvents_OnStateChanged(IAudioSessionEvents
*this,
327 AudioSessionState state
)
329 aout_sys_t
*sys
= container_of(this, aout_sys_t
, session_events
);
330 audio_output_t
*aout
= sys
->aout
;
332 msg_Dbg(aout
, "state changed: %d", state
);
337 vlc_AudioSessionEvents_OnSessionDisconnected(IAudioSessionEvents
*this,
338 AudioSessionDisconnectReason reason
)
340 aout_sys_t
*sys
= container_of(this, aout_sys_t
, session_events
);
341 audio_output_t
*aout
= sys
->aout
;
345 case DisconnectReasonDeviceRemoval
:
346 msg_Warn(aout
, "session disconnected: %s", "device removed");
348 case DisconnectReasonServerShutdown
:
349 msg_Err(aout
, "session disconnected: %s", "service stopped");
351 case DisconnectReasonFormatChanged
:
352 msg_Warn(aout
, "session disconnected: %s", "format changed");
354 case DisconnectReasonSessionLogoff
:
355 msg_Err(aout
, "session disconnected: %s", "user logged off");
357 case DisconnectReasonSessionDisconnected
:
358 msg_Err(aout
, "session disconnected: %s", "session disconnected");
360 case DisconnectReasonExclusiveModeOverride
:
361 msg_Err(aout
, "session disconnected: %s", "stream overriden");
364 msg_Warn(aout
, "session disconnected: unknown reason %d", reason
);
367 /* NOTE: audio decoder thread should get invalidated device and restart */
371 static const struct IAudioSessionEventsVtbl vlc_AudioSessionEvents
=
373 vlc_AudioSessionEvents_QueryInterface
,
374 vlc_AudioSessionEvents_AddRef
,
375 vlc_AudioSessionEvents_Release
,
377 vlc_AudioSessionEvents_OnDisplayNameChanged
,
378 vlc_AudioSessionEvents_OnIconPathChanged
,
379 vlc_AudioSessionEvents_OnSimpleVolumeChanged
,
380 vlc_AudioSessionEvents_OnChannelVolumeChanged
,
381 vlc_AudioSessionEvents_OnGroupingParamChanged
,
382 vlc_AudioSessionEvents_OnStateChanged
,
383 vlc_AudioSessionEvents_OnSessionDisconnected
,
387 vlc_AudioVolumeDuckNotification_QueryInterface(
388 IAudioVolumeDuckNotification
*this, REFIID riid
, void **ppv
)
390 if (IsEqualIID(riid
, &IID_IUnknown
)
391 || IsEqualIID(riid
, &IID_IAudioVolumeDuckNotification
))
394 IUnknown_AddRef(this);
400 return E_NOINTERFACE
;
404 static STDMETHODIMP_(ULONG
)
405 vlc_AudioVolumeDuckNotification_AddRef(IAudioVolumeDuckNotification
*this)
407 aout_sys_t
*sys
= container_of(this, aout_sys_t
, duck
);
408 return InterlockedIncrement(&sys
->refs
);
411 static STDMETHODIMP_(ULONG
)
412 vlc_AudioVolumeDuckNotification_Release(IAudioVolumeDuckNotification
*this)
414 aout_sys_t
*sys
= container_of(this, aout_sys_t
, duck
);
415 return InterlockedDecrement(&sys
->refs
);
419 vlc_AudioVolumeDuckNotification_OnVolumeDuckNotification(
420 IAudioVolumeDuckNotification
*this, LPCWSTR sid
, UINT32 count
)
422 aout_sys_t
*sys
= container_of(this, aout_sys_t
, duck
);
423 audio_output_t
*aout
= sys
->aout
;
425 msg_Dbg(aout
, "volume ducked by %ls of %u sessions", sid
, count
);
427 aout_PolicyReport(aout
, true);
432 vlc_AudioVolumeDuckNotification_OnVolumeUnduckNotification(
433 IAudioVolumeDuckNotification
*this, LPCWSTR sid
)
435 aout_sys_t
*sys
= container_of(this, aout_sys_t
, duck
);
436 audio_output_t
*aout
= sys
->aout
;
438 msg_Dbg(aout
, "volume unducked by %ls", sid
);
440 aout_PolicyReport(aout
, sys
->ducks
!= 0);
444 static const struct IAudioVolumeDuckNotificationVtbl vlc_AudioVolumeDuckNotification
=
446 vlc_AudioVolumeDuckNotification_QueryInterface
,
447 vlc_AudioVolumeDuckNotification_AddRef
,
448 vlc_AudioVolumeDuckNotification_Release
,
450 vlc_AudioVolumeDuckNotification_OnVolumeDuckNotification
,
451 vlc_AudioVolumeDuckNotification_OnVolumeUnduckNotification
,
455 /*** Audio devices ***/
457 /** Gets the user-readable device name */
458 static char *DeviceGetFriendlyName(IMMDevice
*dev
)
460 IPropertyStore
*props
;
464 hr
= IMMDevice_OpenPropertyStore(dev
, STGM_READ
, &props
);
470 hr
= IPropertyStore_GetValue(props
, &PKEY_Device_FriendlyName
, &v
);
473 name
= FromWide(v
.pwszVal
);
474 PropVariantClear(&v
);
477 IPropertyStore_Release(props
);
482 static int DeviceHotplugReport(audio_output_t
*aout
, LPCWSTR wid
,
485 char *id
= FromWide(wid
);
489 char *name
= DeviceGetFriendlyName(dev
);
493 aout_HotplugReport(aout
, id
, name
);
501 /** Checks that a device is an output device */
502 static bool DeviceIsRender(IMMDevice
*dev
)
506 if (FAILED(IMMDevice_QueryInterface(dev
, &IID_IMMEndpoint
, &pv
)))
509 IMMEndpoint
*ep
= pv
;
511 HRESULT hr
= IMMEndpoint_GetDataFlow(ep
, &flow
);
513 IMMEndpoint_Release(ep
);
514 if (FAILED(hr
) || flow
!= eRender
)
518 hr
= IMMDevice_GetState(dev
, &pdwState
);
519 return !FAILED(hr
) && pdwState
== DEVICE_STATE_ACTIVE
;
522 static HRESULT
DeviceUpdated(audio_output_t
*aout
, LPCWSTR wid
)
524 aout_sys_t
*sys
= aout
->sys
;
528 hr
= IMMDeviceEnumerator_GetDevice(sys
->it
, wid
, &dev
);
532 if (!DeviceIsRender(dev
))
534 IMMDevice_Release(dev
);
538 DeviceHotplugReport(aout
, wid
, dev
);
539 IMMDevice_Release(dev
);
544 vlc_MMNotificationClient_QueryInterface(IMMNotificationClient
*this,
545 REFIID riid
, void **ppv
)
547 if (IsEqualIID(riid
, &IID_IUnknown
)
548 || IsEqualIID(riid
, &IID_IMMNotificationClient
))
551 IUnknown_AddRef(this);
557 return E_NOINTERFACE
;
561 static STDMETHODIMP_(ULONG
)
562 vlc_MMNotificationClient_AddRef(IMMNotificationClient
*this)
564 aout_sys_t
*sys
= container_of(this, aout_sys_t
, device_events
);
565 return InterlockedIncrement(&sys
->refs
);
568 static STDMETHODIMP_(ULONG
)
569 vlc_MMNotificationClient_Release(IMMNotificationClient
*this)
571 aout_sys_t
*sys
= container_of(this, aout_sys_t
, device_events
);
572 return InterlockedDecrement(&sys
->refs
);
576 vlc_MMNotificationClient_OnDefaultDeviceChange(IMMNotificationClient
*this,
577 EDataFlow flow
, ERole role
,
580 aout_sys_t
*sys
= container_of(this, aout_sys_t
, device_events
);
581 audio_output_t
*aout
= sys
->aout
;
585 if (role
!= eConsole
)
588 EnterCriticalSection(&sys
->lock
);
589 if (sys
->acquired_device
== NULL
|| sys
->acquired_device
== default_device
)
591 msg_Dbg(aout
, "default device changed: %ls", wid
);
592 sys
->request_device_restart
= true;
593 aout_RestartRequest(aout
, AOUT_RESTART_OUTPUT
);
595 LeaveCriticalSection(&sys
->lock
);
601 vlc_MMNotificationClient_OnDeviceAdded(IMMNotificationClient
*this,
604 aout_sys_t
*sys
= container_of(this, aout_sys_t
, device_events
);
605 audio_output_t
*aout
= sys
->aout
;
607 msg_Dbg(aout
, "device %ls added", wid
);
608 return DeviceUpdated(aout
, wid
);
612 vlc_MMNotificationClient_OnDeviceRemoved(IMMNotificationClient
*this,
615 aout_sys_t
*sys
= container_of(this, aout_sys_t
, device_events
);
616 audio_output_t
*aout
= sys
->aout
;
617 char *id
= FromWide(wid
);
619 msg_Dbg(aout
, "device %ls removed", wid
);
620 if (unlikely(id
== NULL
))
621 return E_OUTOFMEMORY
;
623 aout_HotplugReport(aout
, id
, NULL
);
629 vlc_MMNotificationClient_OnDeviceStateChanged(IMMNotificationClient
*this,
630 LPCWSTR wid
, DWORD state
)
632 aout_sys_t
*sys
= container_of(this, aout_sys_t
, device_events
);
633 audio_output_t
*aout
= sys
->aout
;
636 case DEVICE_STATE_UNPLUGGED
:
637 msg_Dbg(aout
, "device %ls state changed: unplugged", wid
);
639 case DEVICE_STATE_ACTIVE
:
640 msg_Dbg(aout
, "device %ls state changed: active", wid
);
641 return DeviceUpdated(aout
, wid
);
642 case DEVICE_STATE_DISABLED
:
643 msg_Dbg(aout
, "device %ls state changed: disabled", wid
);
645 case DEVICE_STATE_NOTPRESENT
:
646 msg_Dbg(aout
, "device %ls state changed: not present", wid
);
649 msg_Dbg(aout
, "device %ls state changed: unknown: %08lx", wid
, state
);
653 /* Unplugged, disabled or notpresent */
654 char *id
= FromWide(wid
);
655 if (unlikely(id
== NULL
))
656 return E_OUTOFMEMORY
;
657 aout_HotplugReport(aout
, id
, NULL
);
664 vlc_MMNotificationClient_OnPropertyValueChanged(IMMNotificationClient
*this,
666 const PROPERTYKEY key
)
668 aout_sys_t
*sys
= container_of(this, aout_sys_t
, device_events
);
669 audio_output_t
*aout
= sys
->aout
;
671 if (key
.pid
== PKEY_Device_FriendlyName
.pid
)
673 msg_Dbg(aout
, "device %ls name changed", wid
);
674 return DeviceUpdated(aout
, wid
);
679 static const struct IMMNotificationClientVtbl vlc_MMNotificationClient
=
681 vlc_MMNotificationClient_QueryInterface
,
682 vlc_MMNotificationClient_AddRef
,
683 vlc_MMNotificationClient_Release
,
685 vlc_MMNotificationClient_OnDeviceStateChanged
,
686 vlc_MMNotificationClient_OnDeviceAdded
,
687 vlc_MMNotificationClient_OnDeviceRemoved
,
688 vlc_MMNotificationClient_OnDefaultDeviceChange
,
689 vlc_MMNotificationClient_OnPropertyValueChanged
,
692 static HRESULT
DevicesEnum(IMMDeviceEnumerator
*it
,
693 void (*added_cb
)(void *data
, LPCWSTR wid
, IMMDevice
*dev
),
697 IMMDeviceCollection
*devs
;
698 assert(added_cb
!= NULL
);
700 hr
= IMMDeviceEnumerator_EnumAudioEndpoints(it
, eRender
,
701 DEVICE_STATE_ACTIVE
, &devs
);
706 hr
= IMMDeviceCollection_GetCount(devs
, &count
);
710 for (UINT i
= 0; i
< count
; i
++)
714 hr
= IMMDeviceCollection_Item(devs
, i
, &dev
);
715 if (FAILED(hr
) || !DeviceIsRender(dev
))
718 /* Unique device ID */
720 hr
= IMMDevice_GetId(dev
, &devid
);
723 IMMDevice_Release(dev
);
727 added_cb(added_cb_data
, devid
, dev
);
728 IMMDevice_Release(dev
);
729 CoTaskMemFree(devid
);
731 IMMDeviceCollection_Release(devs
);
735 static int DeviceRequestLocked(audio_output_t
*aout
)
737 aout_sys_t
*sys
= aout
->sys
;
738 assert(sys
->requested_device
);
740 sys
->request_device_restart
= false;
742 WakeConditionVariable(&sys
->work
);
743 while (sys
->requested_device
!= NULL
)
744 SleepConditionVariableCS(&sys
->ready
, &sys
->lock
, INFINITE
);
746 if (sys
->stream
!= NULL
&& sys
->dev
!= NULL
)
747 /* Request restart of stream with the new device */
748 aout_RestartRequest(aout
, AOUT_RESTART_OUTPUT
);
749 return (sys
->dev
!= NULL
) ? 0 : -1;
752 static int DeviceSelectLocked(audio_output_t
*aout
, const char *id
)
754 aout_sys_t
*sys
= aout
->sys
;
755 assert(sys
->requested_device
== NULL
);
757 if (id
!= NULL
&& strcmp(id
, default_device_b
) != 0)
759 sys
->requested_device
= ToWide(id
);
760 if (unlikely(sys
->requested_device
== NULL
))
764 sys
->requested_device
= default_device
;
766 return DeviceRequestLocked(aout
);
769 static int DeviceRestartLocked(audio_output_t
*aout
)
771 aout_sys_t
*sys
= aout
->sys
;
772 assert(sys
->requested_device
== NULL
);
773 sys
->requested_device
= sys
->acquired_device
? sys
->acquired_device
775 return DeviceRequestLocked(aout
);
778 static int DeviceSelect(audio_output_t
*aout
, const char *id
)
780 aout_sys_t
*sys
= aout
->sys
;
781 EnterCriticalSection(&sys
->lock
);
782 int ret
= DeviceSelectLocked(aout
, id
);
783 LeaveCriticalSection(&sys
->lock
);
787 /*** Initialization / deinitialization **/
788 static wchar_t *var_InheritWide(vlc_object_t
*obj
, const char *name
)
790 char *v8
= var_InheritString(obj
, name
);
794 wchar_t *v16
= ToWide(v8
);
798 #define var_InheritWide(o,n) var_InheritWide(VLC_OBJECT(o),n)
800 /** MMDevice audio output thread.
801 * This thread takes cares of the audio session control. Inconveniently enough,
802 * the audio session control interface must:
803 * - be created and destroyed from the same thread, and
804 * - survive across VLC audio output calls.
805 * The only way to reconcile both requirements is a custom thread.
806 * The thread also ensure that the COM Multi-Thread Apartment is continuously
807 * referenced so that MMDevice objects are not destroyed early.
808 * Furthermore, VolumeSet() and MuteSet() may be called from a thread with a
809 * COM STA, so that it cannot access the COM MTA for audio controls.
811 static HRESULT
MMSession(audio_output_t
*aout
, IMMDeviceEnumerator
*it
)
813 aout_sys_t
*sys
= aout
->sys
;
814 IAudioSessionManager
*manager
;
815 IAudioSessionControl
*control
;
816 ISimpleAudioVolume
*volume
;
817 IAudioEndpointVolume
*endpoint
;
821 assert(sys
->requested_device
!= NULL
);
822 assert(sys
->dev
== NULL
);
824 /* Yes, it's perfectly valid to request the same device, see Start()
826 if (sys
->acquired_device
!= sys
->requested_device
827 && sys
->acquired_device
!= default_device
)
828 free(sys
->acquired_device
);
829 if (sys
->requested_device
!= default_device
) /* Device selected explicitly */
831 msg_Dbg(aout
, "using selected device %ls", sys
->requested_device
);
832 hr
= IMMDeviceEnumerator_GetDevice(it
, sys
->requested_device
, &sys
->dev
);
834 msg_Err(aout
, "cannot get selected device %ls (error 0x%lx)",
835 sys
->requested_device
, hr
);
836 sys
->acquired_device
= sys
->requested_device
;
839 hr
= AUDCLNT_E_DEVICE_INVALIDATED
;
841 while (hr
== AUDCLNT_E_DEVICE_INVALIDATED
)
842 { /* Default device selected by policy and with stream routing.
843 * "Do not use eMultimedia" says MSDN. */
844 msg_Dbg(aout
, "using default device");
845 hr
= IMMDeviceEnumerator_GetDefaultAudioEndpoint(it
, eRender
,
846 eConsole
, &sys
->dev
);
849 msg_Err(aout
, "cannot get default device (error 0x%lx)", hr
);
850 sys
->acquired_device
= NULL
;
853 sys
->acquired_device
= default_device
;
856 sys
->requested_device
= NULL
;
857 WakeConditionVariable(&sys
->ready
);
860 { /* Report actual device */
863 if (sys
->acquired_device
== default_device
)
864 aout_DeviceReport(aout
, default_device_b
);
867 hr
= IMMDevice_GetId(sys
->dev
, &wdevid
);
870 char *id
= FromWide(wdevid
);
871 CoTaskMemFree(wdevid
);
872 if (likely(id
!= NULL
))
874 aout_DeviceReport(aout
, id
);
882 msg_Err(aout
, "cannot get device identifier (error 0x%lx)", hr
);
886 /* Create session manager (for controls even w/o active audio client) */
887 hr
= IMMDevice_Activate(sys
->dev
, &IID_IAudioSessionManager
,
888 CLSCTX_ALL
, NULL
, &pv
);
892 LPCGUID guid
= var_GetBool(aout
, "volume-save") ? &GUID_VLC_AUD_OUT
: NULL
;
894 /* Register session control */
895 hr
= IAudioSessionManager_GetAudioSessionControl(manager
, guid
, 0,
899 wchar_t *ua
= var_InheritWide(aout
, "user-agent");
900 IAudioSessionControl_SetDisplayName(control
, ua
, NULL
);
903 IAudioSessionControl_RegisterAudioSessionNotification(control
,
904 &sys
->session_events
);
907 msg_Err(aout
, "cannot get session control (error 0x%lx)", hr
);
909 hr
= IAudioSessionManager_GetSimpleAudioVolume(manager
, guid
, FALSE
,
912 msg_Err(aout
, "cannot get simple volume (error 0x%lx)", hr
);
914 /* Try to get version 2 (Windows 7) of the manager & control */
915 wchar_t *siid
= NULL
;
917 hr
= IAudioSessionManager_QueryInterface(manager
,
918 &IID_IAudioSessionControl2
, &pv
);
921 IAudioSessionControl2
*c2
= pv
;
923 IAudioSessionControl2_SetDuckingPreference(c2
, FALSE
);
924 hr
= IAudioSessionControl2_GetSessionInstanceIdentifier(c2
, &siid
);
927 IAudioSessionControl2_Release(c2
);
930 msg_Dbg(aout
, "version 2 session control unavailable");
932 hr
= IAudioSessionManager_QueryInterface(manager
,
933 &IID_IAudioSessionManager2
, &pv
);
936 IAudioSessionManager2
*m2
= pv
;
938 IAudioSessionManager2_RegisterDuckNotification(m2
, siid
,
940 IAudioSessionManager2_Release(m2
);
943 msg_Dbg(aout
, "version 2 session management unavailable");
949 msg_Err(aout
, "cannot activate session manager (error 0x%lx)", hr
);
954 hr
= IMMDevice_Activate(sys
->dev
, &IID_IAudioEndpointVolume
,
955 CLSCTX_ALL
, NULL
, &pv
);
961 hr
= IAudioEndpointVolume_GetVolumeRange(endpoint
, &min
, &max
, &inc
);
963 msg_Dbg(aout
, "volume from %+f dB to %+f dB with %f dB increments",
966 msg_Err(aout
, "cannot get volume range (error 0x%lx)", hr
);
969 msg_Err(aout
, "cannot activate endpoint volume (error %lx)", hr
);
971 /* Main loop (adjust volume as long as device is unchanged) */
972 while (sys
->requested_device
== NULL
)
978 level
= sys
->requested_volume
;
981 hr
= ISimpleAudioVolume_SetMasterVolume(volume
, level
, NULL
);
983 msg_Err(aout
, "cannot set master volume (error 0x%lx)",
986 sys
->requested_volume
= -1.f
;
988 hr
= ISimpleAudioVolume_GetMasterVolume(volume
, &level
);
990 aout_VolumeReport(aout
, cbrtf(level
* sys
->gain
));
992 msg_Err(aout
, "cannot get master volume (error 0x%lx)", hr
);
996 hr
= ISimpleAudioVolume_GetMute(volume
, &mute
);
998 aout_MuteReport(aout
, mute
!= FALSE
);
1000 msg_Err(aout
, "cannot get mute (error 0x%lx)", hr
);
1002 if (sys
->requested_mute
>= 0)
1004 mute
= sys
->requested_mute
? TRUE
: FALSE
;
1006 hr
= ISimpleAudioVolume_SetMute(volume
, mute
, NULL
);
1008 msg_Err(aout
, "cannot set mute (error 0x%lx)", hr
);
1010 sys
->requested_mute
= -1;
1013 SleepConditionVariableCS(&sys
->work
, &sys
->lock
, INFINITE
);
1015 LeaveCriticalSection(&sys
->lock
);
1017 if (endpoint
!= NULL
)
1018 IAudioEndpointVolume_Release(endpoint
);
1020 if (manager
!= NULL
)
1021 { /* Deregister callbacks *without* the lock */
1022 hr
= IAudioSessionManager_QueryInterface(manager
,
1023 &IID_IAudioSessionManager2
, &pv
);
1026 IAudioSessionManager2
*m2
= pv
;
1028 IAudioSessionManager2_UnregisterDuckNotification(m2
, &sys
->duck
);
1029 IAudioSessionManager2_Release(m2
);
1033 ISimpleAudioVolume_Release(volume
);
1035 if (control
!= NULL
)
1037 IAudioSessionControl_UnregisterAudioSessionNotification(control
,
1038 &sys
->session_events
);
1039 IAudioSessionControl_Release(control
);
1042 IAudioSessionManager_Release(manager
);
1045 EnterCriticalSection(&sys
->lock
);
1046 IMMDevice_Release(sys
->dev
);
1051 static void MMThread_DevicesEnum_Added(void *data
, LPCWSTR wid
, IMMDevice
*dev
)
1053 audio_output_t
*aout
= data
;
1055 DeviceHotplugReport(aout
, wid
, dev
);
1058 static void *MMThread(void *data
)
1060 audio_output_t
*aout
= data
;
1061 aout_sys_t
*sys
= aout
->sys
;
1062 IMMDeviceEnumerator
*it
= sys
->it
;
1065 IMMDeviceEnumerator_RegisterEndpointNotificationCallback(it
,
1066 &sys
->device_events
);
1067 HRESULT hr
= DevicesEnum(it
, MMThread_DevicesEnum_Added
, aout
);
1069 msg_Warn(aout
, "cannot enumerate audio endpoints (error 0x%lx)", hr
);
1071 EnterCriticalSection(&sys
->lock
);
1074 if (sys
->requested_device
== NULL
|| FAILED(MMSession(aout
, it
)))
1075 SleepConditionVariableCS(&sys
->work
, &sys
->lock
, INFINITE
);
1076 while (sys
->it
!= NULL
);
1078 LeaveCriticalSection(&sys
->lock
);
1080 IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(it
,
1081 &sys
->device_events
);
1082 IMMDeviceEnumerator_Release(it
);
1088 * Callback for aout_stream_t to create a stream on the device.
1089 * This can instantiate an IAudioClient or IDirectSound(8) object.
1091 static HRESULT
ActivateDevice(void *opaque
, REFIID iid
, PROPVARIANT
*actparms
,
1094 IMMDevice
*dev
= opaque
;
1095 return IMMDevice_Activate(dev
, iid
, CLSCTX_ALL
, actparms
, pv
);
1098 static int aout_stream_Start(void *func
, va_list ap
)
1100 aout_stream_start_t start
= func
;
1101 aout_stream_t
*s
= va_arg(ap
, aout_stream_t
*);
1102 audio_sample_format_t
*fmt
= va_arg(ap
, audio_sample_format_t
*);
1103 HRESULT
*hr
= va_arg(ap
, HRESULT
*);
1104 LPCGUID sid
= var_InheritBool(s
, "volume-save") ? &GUID_VLC_AUD_OUT
: NULL
;
1106 *hr
= start(s
, fmt
, sid
);
1107 if (*hr
== AUDCLNT_E_DEVICE_INVALIDATED
)
1108 return VLC_ETIMEOUT
;
1109 return SUCCEEDED(*hr
) ? VLC_SUCCESS
: VLC_EGENERIC
;
1112 static void aout_stream_Stop(void *func
, va_list ap
)
1114 aout_stream_stop_t stop
= func
;
1115 aout_stream_t
*s
= va_arg(ap
, aout_stream_t
*);
1120 static int Start(audio_output_t
*aout
, audio_sample_format_t
*restrict fmt
)
1122 aout_sys_t
*sys
= aout
->sys
;
1124 const bool b_spdif
= AOUT_FMT_SPDIF(fmt
);
1125 const bool b_hdmi
= AOUT_FMT_HDMI(fmt
);
1126 if (b_spdif
|| b_hdmi
)
1128 switch (var_InheritInteger(aout
, "mmdevice-passthrough"))
1130 case MM_PASSTHROUGH_DISABLED
:
1132 case MM_PASSTHROUGH_ENABLED
:
1135 else if (fmt
->i_format
== VLC_CODEC_DTS
)
1136 var_SetBool(aout
, "dtshd", false );
1138 case MM_PASSTHROUGH_ENABLED_HD
:
1143 aout_stream_t
*s
= vlc_object_create(aout
, sizeof (*s
));
1144 if (unlikely(s
== NULL
))
1147 s
->owner
.activate
= ActivateDevice
;
1150 EnterCriticalSection(&sys
->lock
);
1152 if ((sys
->request_device_restart
&& DeviceRestartLocked(aout
) != 0)
1153 || sys
->dev
== NULL
)
1155 /* Error if the device restart failed or if a request previously
1157 LeaveCriticalSection(&sys
->lock
);
1159 vlc_object_release(s
);
1165 char *modlist
= var_InheritString(aout
, "mmdevice-backend");
1167 s
->owner
.device
= sys
->dev
;
1169 sys
->module
= vlc_module_load(s
, "aout stream", modlist
,
1170 false, aout_stream_Start
, s
, fmt
, &hr
);
1174 if (hr
== AUDCLNT_E_ALREADY_INITIALIZED
)
1176 /* From MSDN: "If the initial call to Initialize fails, subsequent
1177 * Initialize calls might fail and return error code
1178 * E_ALREADY_INITIALIZED, even though the interface has not been
1179 * initialized. If this occurs, release the IAudioClient interface
1180 * and obtain a new IAudioClient interface from the MMDevice API
1181 * before calling Initialize again."
1183 * Therefore, request to MMThread the same device and try again. */
1185 ret
= DeviceRestartLocked(aout
);
1187 else if (hr
== AUDCLNT_E_DEVICE_INVALIDATED
)
1189 /* The audio endpoint device has been unplugged, request to
1190 * MMThread the default device and try again. */
1192 ret
= DeviceSelectLocked(aout
, NULL
);
1198 if (sys
->module
!= NULL
)
1200 IPropertyStore
*props
;
1201 HRESULT hr
= IMMDevice_OpenPropertyStore(sys
->dev
, STGM_READ
, &props
);
1205 PropVariantInit(&v
);
1206 hr
= IPropertyStore_GetValue(props
, &PKEY_AudioEndpoint_FormFactor
, &v
);
1213 aout
->current_sink_info
.headphones
= true;
1216 PropVariantClear(&v
);
1218 IPropertyStore_Release(props
);
1222 LeaveCriticalSection(&sys
->lock
);
1225 if (sys
->module
== NULL
)
1227 vlc_object_release(s
);
1231 assert (sys
->stream
== NULL
);
1233 aout_GainRequest(aout
, sys
->gain
);
1237 static void Stop(audio_output_t
*aout
)
1239 aout_sys_t
*sys
= aout
->sys
;
1241 assert(sys
->stream
!= NULL
);
1244 vlc_module_unload(sys
->stream
, sys
->module
, aout_stream_Stop
, sys
->stream
);
1247 vlc_object_release(sys
->stream
);
1251 static int Open(vlc_object_t
*obj
)
1253 audio_output_t
*aout
= (audio_output_t
*)obj
;
1255 aout_sys_t
*sys
= malloc(sizeof (*sys
));
1256 if (unlikely(sys
== NULL
))
1264 sys
->device_events
.lpVtbl
= &vlc_MMNotificationClient
;
1265 sys
->session_events
.lpVtbl
= &vlc_AudioSessionEvents
;
1266 sys
->duck
.lpVtbl
= &vlc_AudioVolumeDuckNotification
;
1271 sys
->requested_volume
= -1.f
;
1272 sys
->requested_mute
= -1;
1273 sys
->acquired_device
= NULL
;
1274 sys
->request_device_restart
= false;
1276 if (!var_CreateGetBool(aout
, "volume-save"))
1277 VolumeSetLocked(aout
, var_InheritFloat(aout
, "mmdevice-volume"));
1279 InitializeCriticalSection(&sys
->lock
);
1280 InitializeConditionVariable(&sys
->work
);
1281 InitializeConditionVariable(&sys
->ready
);
1283 aout_HotplugReport(aout
, default_device_b
, _("Default"));
1285 char *saved_device_b
= var_InheritString(aout
, "mmdevice-audio-device");
1286 if (saved_device_b
!= NULL
&& strcmp(saved_device_b
, default_device_b
) != 0)
1288 sys
->requested_device
= ToWide(saved_device_b
);
1289 free(saved_device_b
);
1291 if (unlikely(sys
->requested_device
== NULL
))
1296 free(saved_device_b
);
1297 sys
->requested_device
= default_device
;
1300 /* Initialize MMDevice API */
1301 if (TryEnterMTA(aout
))
1305 HRESULT hr
= CoCreateInstance(&CLSID_MMDeviceEnumerator
, NULL
, CLSCTX_ALL
,
1306 &IID_IMMDeviceEnumerator
, &pv
);
1310 msg_Dbg(aout
, "cannot create device enumerator (error 0x%lx)", hr
);
1315 if (vlc_clone(&sys
->thread
, MMThread
, aout
, VLC_THREAD_PRIORITY_LOW
))
1317 IMMDeviceEnumerator_Release(sys
->it
);
1322 EnterCriticalSection(&sys
->lock
);
1323 while (sys
->requested_device
!= NULL
)
1324 SleepConditionVariableCS(&sys
->ready
, &sys
->lock
, INFINITE
);
1325 LeaveCriticalSection(&sys
->lock
);
1326 LeaveMTA(); /* Leave MTA after thread has entered MTA */
1328 aout
->start
= Start
;
1330 aout
->time_get
= TimeGet
;
1332 aout
->pause
= Pause
;
1333 aout
->flush
= Flush
;
1334 aout
->volume_set
= VolumeSet
;
1335 aout
->mute_set
= MuteSet
;
1336 aout
->device_select
= DeviceSelect
;
1341 DeleteCriticalSection(&sys
->lock
);
1343 return VLC_EGENERIC
;
1346 static void Close(vlc_object_t
*obj
)
1348 audio_output_t
*aout
= (audio_output_t
*)obj
;
1349 aout_sys_t
*sys
= aout
->sys
;
1351 EnterCriticalSection(&sys
->lock
);
1352 sys
->requested_device
= default_device
; /* break out of MMSession() loop */
1353 sys
->it
= NULL
; /* break out of MMThread() loop */
1354 WakeConditionVariable(&sys
->work
);
1355 LeaveCriticalSection(&sys
->lock
);
1357 vlc_join(sys
->thread
, NULL
);
1358 DeleteCriticalSection(&sys
->lock
);
1370 static void Reload_DevicesEnum_Added(void *data
, LPCWSTR wid
, IMMDevice
*dev
)
1372 struct mm_list
*list
= data
;
1374 size_t new_count
= list
->count
+ 1;
1375 list
->ids
= realloc_or_free(list
->ids
, new_count
* sizeof(char *));
1376 list
->names
= realloc_or_free(list
->names
, new_count
* sizeof(char *));
1377 if (!list
->ids
|| !list
->names
)
1383 char *id
= FromWide(wid
);
1387 char *name
= DeviceGetFriendlyName(dev
);
1388 if (!name
&& !(name
= strdup(id
)))
1393 list
->ids
[list
->count
] = id
;
1394 list
->names
[list
->count
] = name
;
1396 list
->count
= new_count
;
1399 static int ReloadAudioDevices(char const *name
, char ***values
, char ***descs
)
1403 bool in_mta
= SUCCEEDED(CoInitializeEx(NULL
, COINIT_MULTITHREADED
));
1405 struct mm_list list
= { .count
= 0 };
1407 HRESULT hr
= CoCreateInstance(&CLSID_MMDeviceEnumerator
, NULL
, CLSCTX_ALL
,
1408 &IID_IMMDeviceEnumerator
, &it
);
1412 list
.ids
= malloc(sizeof (char *));
1413 list
.names
= malloc(sizeof (char *));
1414 if (!list
.ids
|| !list
.names
)
1420 list
.ids
[0] = strdup("");
1421 list
.names
[0] = strdup(_("Default"));
1422 if (!list
.ids
[0] || !list
.names
[0])
1431 DevicesEnum(it
, Reload_DevicesEnum_Added
, &list
);
1434 IMMDeviceEnumerator_Release((IMMDeviceEnumerator
*)it
);
1441 *descs
= list
.names
;
1447 #define MM_PASSTHROUGH_TEXT N_( \
1448 "HDMI/SPDIF audio passthrough")
1449 #define MM_PASSTHROUGH_LONGTEXT N_( \
1450 "Change this value if you have issue with HD codecs when using a HDMI receiver.")
1451 static const int pi_mmdevice_passthrough_values
[] = {
1452 MM_PASSTHROUGH_DISABLED
,
1453 MM_PASSTHROUGH_ENABLED
,
1454 MM_PASSTHROUGH_ENABLED_HD
,
1456 static const char *const ppsz_mmdevice_passthrough_texts
[] = {
1458 N_("Enabled (AC3/DTS only)"),
1462 #define DEVICE_TEXT N_("Output device")
1463 #define DEVICE_LONGTEXT N_("Select your audio output device")
1465 #define VOLUME_TEXT N_("Audio volume")
1466 #define VOLUME_LONGTEXT N_("Audio volume in hundredths of decibels (dB).")
1469 set_shortname("MMDevice")
1470 set_description(N_("Windows Multimedia Device output"))
1471 set_capability("audio output", 150)
1472 set_category(CAT_AUDIO
)
1473 set_subcategory(SUBCAT_AUDIO_AOUT
)
1474 set_callbacks(Open
, Close
)
1475 add_module("mmdevice-backend", "aout stream", "any",
1476 N_("Output back-end"), N_("Audio output back-end interface."))
1477 add_integer( "mmdevice-passthrough", MM_PASSTHROUGH_DEFAULT
,
1478 MM_PASSTHROUGH_TEXT
, MM_PASSTHROUGH_LONGTEXT
, false )
1479 change_integer_list( pi_mmdevice_passthrough_values
,
1480 ppsz_mmdevice_passthrough_texts
)
1481 add_string("mmdevice-audio-device", NULL
, DEVICE_TEXT
, DEVICE_LONGTEXT
, false)
1482 change_string_cb(ReloadAudioDevices
)
1483 add_float("mmdevice-volume", 1.f
, VOLUME_TEXT
, VOLUME_LONGTEXT
, true)
1484 change_float_range( 0.f
, 1.25f
)