1 /*****************************************************************************
2 * mmdevice.c : Windows Multimedia Device API audio output plugin for VLC
3 *****************************************************************************
4 * Copyright (C) 2012-2014 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 #if (_WIN32_WINNT < 0x600)
47 static VOID WINAPI (*InitializeConditionVariable)(PCONDITION_VARIABLE);
48 static BOOL WINAPI (*SleepConditionVariableCS)(PCONDITION_VARIABLE,
49 PCRITICAL_SECTION, DWORD);
50 static VOID WINAPI (*WakeConditionVariable)(PCONDITION_VARIABLE);
52 if (((s) = (void *)GetProcAddress(h, #s)) == NULL) return FALSE
54 BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID); /* avoid warning */
55 BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, LPVOID reserved)
62 case DLL_PROCESS_ATTACH:
64 HANDLE h = GetModuleHandle(TEXT("kernel32.dll"));
65 if (unlikely(h == NULL))
67 LOOKUP(InitializeConditionVariable);
68 LOOKUP(SleepConditionVariableCS);
69 LOOKUP(WakeConditionVariable);
77 DEFINE_GUID (GUID_VLC_AUD_OUT, 0x4533f59d, 0x59ee, 0x00c6,
78 0xad, 0xb2, 0xc6, 0x8b, 0x50, 0x1a, 0x66, 0x55);
80 static int TryEnterMTA(vlc_object_t *obj)
82 HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
83 if (unlikely(FAILED(hr)))
85 msg_Err (obj, "cannot initialize COM (error 0x%lx)", hr);
90 #define TryEnterMTA(o) TryEnterMTA(VLC_OBJECT(o))
92 static void EnterMTA(void)
94 HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
95 if (unlikely(FAILED(hr)))
99 static void LeaveMTA(void)
104 static wchar_t default_device[1] = L"";
108 aout_stream_t *stream; /**< Underlying audio output stream */
110 audio_output_t *aout;
111 IMMDeviceEnumerator *it; /**< Device enumerator, NULL when exiting */
112 IMMDevice *dev; /**< Selected output device, NULL if none */
114 struct IMMNotificationClient device_events;
115 struct IAudioSessionEvents session_events;
116 struct IAudioVolumeDuckNotification duck;
120 float gain; /**< Current software gain volume */
122 wchar_t *device; /**< Requested device identifier, NULL if none */
123 float volume; /**< Requested volume, negative if none */
124 signed char mute; /**< Requested mute, negative if none */
125 CRITICAL_SECTION lock;
126 CONDITION_VARIABLE work;
127 CONDITION_VARIABLE ready;
128 vlc_thread_t thread; /**< Thread for audio session control */
131 /* NOTE: The Core Audio API documentation totally fails to specify the thread
132 * safety (or lack thereof) of the interfaces. This code takes the most
133 * restrictive assumption: no thread safety. The background thread (MMThread)
134 * only runs at specified times, namely between the device_ready and
135 * device_changed events (effectively a thread synchronization barrier, but
136 * only Windows 8 natively provides such a primitive).
138 * The audio output owner (i.e. the audio output core) is responsible for
139 * serializing callbacks. This code only needs to be concerned with
140 * synchronization between the set of audio output callbacks, MMThread()
141 * and (trivially) the device and session notifications. */
143 static int vlc_FromHR(audio_output_t *aout, HRESULT hr)
145 /* Restart on unplug */
146 if (unlikely(hr == AUDCLNT_E_DEVICE_INVALIDATED))
147 aout_RestartRequest(aout, AOUT_RESTART_OUTPUT);
148 return SUCCEEDED(hr) ? 0 : -1;
151 /*** VLC audio output callbacks ***/
152 static int TimeGet(audio_output_t *aout, mtime_t *restrict delay)
154 aout_sys_t *sys = aout->sys;
158 hr = aout_stream_TimeGet(sys->stream, delay);
161 return SUCCEEDED(hr) ? 0 : -1;
164 static void Play(audio_output_t *aout, block_t *block)
166 aout_sys_t *sys = aout->sys;
170 hr = aout_stream_Play(sys->stream, block);
173 vlc_FromHR(aout, hr);
176 static void Pause(audio_output_t *aout, bool paused, mtime_t date)
178 aout_sys_t *sys = aout->sys;
182 hr = aout_stream_Pause(sys->stream, paused);
185 vlc_FromHR(aout, hr);
189 static void Flush(audio_output_t *aout, bool wait)
191 aout_sys_t *sys = aout->sys;
194 aout_stream_Flush(sys->stream, wait);
198 static int VolumeSet(audio_output_t *aout, float vol)
200 aout_sys_t *sys = aout->sys;
203 vol = vol * vol * vol; /* ISimpleAudioVolume is tapered linearly. */
211 aout_GainRequest(aout, gain);
213 EnterCriticalSection(&sys->lock);
216 WakeConditionVariable(&sys->work);
217 LeaveCriticalSection(&sys->lock);
221 static int MuteSet(audio_output_t *aout, bool mute)
223 aout_sys_t *sys = aout->sys;
225 EnterCriticalSection(&sys->lock);
227 WakeConditionVariable(&sys->work);
228 LeaveCriticalSection(&sys->lock);
232 /*** Audio session events ***/
233 static inline aout_sys_t *vlc_AudioSessionEvents_sys(IAudioSessionEvents *this)
235 return (void *)(((char *)this) - offsetof(aout_sys_t, session_events));
239 vlc_AudioSessionEvents_QueryInterface(IAudioSessionEvents *this, REFIID riid,
242 if (IsEqualIID(riid, &IID_IUnknown)
243 || IsEqualIID(riid, &IID_IAudioSessionEvents))
246 IUnknown_AddRef(this);
252 return E_NOINTERFACE;
256 static STDMETHODIMP_(ULONG)
257 vlc_AudioSessionEvents_AddRef(IAudioSessionEvents *this)
259 aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
260 return InterlockedIncrement(&sys->refs);
263 static STDMETHODIMP_(ULONG)
264 vlc_AudioSessionEvents_Release(IAudioSessionEvents *this)
266 aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
267 return InterlockedDecrement(&sys->refs);
271 vlc_AudioSessionEvents_OnDisplayNameChanged(IAudioSessionEvents *this,
272 LPCWSTR wname, LPCGUID ctx)
274 aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
275 audio_output_t *aout = sys->aout;
277 msg_Dbg(aout, "display name changed: %ls", wname);
283 vlc_AudioSessionEvents_OnIconPathChanged(IAudioSessionEvents *this,
284 LPCWSTR wpath, LPCGUID ctx)
286 aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
287 audio_output_t *aout = sys->aout;
289 msg_Dbg(aout, "icon path changed: %ls", wpath);
295 vlc_AudioSessionEvents_OnSimpleVolumeChanged(IAudioSessionEvents *this,
296 float vol, BOOL mute,
299 aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
300 audio_output_t *aout = sys->aout;
302 msg_Dbg(aout, "simple volume changed: %f, muting %sabled", vol,
303 mute ? "en" : "dis");
304 EnterCriticalSection(&sys->lock);
305 WakeConditionVariable(&sys->work); /* implicit state: vol & mute */
306 LeaveCriticalSection(&sys->lock);
312 vlc_AudioSessionEvents_OnChannelVolumeChanged(IAudioSessionEvents *this,
313 DWORD count, float *vols,
314 DWORD changed, LPCGUID ctx)
316 aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
317 audio_output_t *aout = sys->aout;
319 if (changed != (DWORD)-1)
320 msg_Dbg(aout, "channel volume %lu of %lu changed: %f", changed, count,
323 msg_Dbg(aout, "%lu channels volume changed", count);
330 vlc_AudioSessionEvents_OnGroupingParamChanged(IAudioSessionEvents *this,
331 LPCGUID param, LPCGUID ctx)
334 aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
335 audio_output_t *aout = sys->aout;
337 msg_Dbg(aout, "grouping parameter changed");
344 vlc_AudioSessionEvents_OnStateChanged(IAudioSessionEvents *this,
345 AudioSessionState state)
347 aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
348 audio_output_t *aout = sys->aout;
350 msg_Dbg(aout, "state changed: %d", state);
355 vlc_AudioSessionEvents_OnSessionDisconnected(IAudioSessionEvents *this,
356 AudioSessionDisconnectReason reason)
358 aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
359 audio_output_t *aout = sys->aout;
363 case DisconnectReasonDeviceRemoval:
364 msg_Warn(aout, "session disconnected: %s", "device removed");
366 case DisconnectReasonServerShutdown:
367 msg_Err(aout, "session disconnected: %s", "service stopped");
369 case DisconnectReasonFormatChanged:
370 msg_Warn(aout, "session disconnected: %s", "format changed");
372 case DisconnectReasonSessionLogoff:
373 msg_Err(aout, "session disconnected: %s", "user logged off");
375 case DisconnectReasonSessionDisconnected:
376 msg_Err(aout, "session disconnected: %s", "session disconnected");
378 case DisconnectReasonExclusiveModeOverride:
379 msg_Err(aout, "session disconnected: %s", "stream overriden");
382 msg_Warn(aout, "session disconnected: unknown reason %d", reason);
385 /* NOTE: audio decoder thread should get invalidated device and restart */
389 static const struct IAudioSessionEventsVtbl vlc_AudioSessionEvents =
391 vlc_AudioSessionEvents_QueryInterface,
392 vlc_AudioSessionEvents_AddRef,
393 vlc_AudioSessionEvents_Release,
395 vlc_AudioSessionEvents_OnDisplayNameChanged,
396 vlc_AudioSessionEvents_OnIconPathChanged,
397 vlc_AudioSessionEvents_OnSimpleVolumeChanged,
398 vlc_AudioSessionEvents_OnChannelVolumeChanged,
399 vlc_AudioSessionEvents_OnGroupingParamChanged,
400 vlc_AudioSessionEvents_OnStateChanged,
401 vlc_AudioSessionEvents_OnSessionDisconnected,
404 static inline aout_sys_t *vlc_AudioVolumeDuckNotification_sys(IAudioVolumeDuckNotification *this)
406 return (void *)(((char *)this) - offsetof(aout_sys_t, duck));
410 vlc_AudioVolumeDuckNotification_QueryInterface(
411 IAudioVolumeDuckNotification *this, REFIID riid, void **ppv)
413 if (IsEqualIID(riid, &IID_IUnknown)
414 || IsEqualIID(riid, &IID_IAudioVolumeDuckNotification))
417 IUnknown_AddRef(this);
423 return E_NOINTERFACE;
427 static STDMETHODIMP_(ULONG)
428 vlc_AudioVolumeDuckNotification_AddRef(IAudioVolumeDuckNotification *this)
430 aout_sys_t *sys = vlc_AudioVolumeDuckNotification_sys(this);
431 return InterlockedIncrement(&sys->refs);
434 static STDMETHODIMP_(ULONG)
435 vlc_AudioVolumeDuckNotification_Release(IAudioVolumeDuckNotification *this)
437 aout_sys_t *sys = vlc_AudioVolumeDuckNotification_sys(this);
438 return InterlockedDecrement(&sys->refs);
442 vlc_AudioVolumeDuckNotification_OnVolumeDuckNotification(
443 IAudioVolumeDuckNotification *this, LPCWSTR sid, UINT32 count)
445 aout_sys_t *sys = vlc_AudioVolumeDuckNotification_sys(this);
446 audio_output_t *aout = sys->aout;
448 msg_Dbg(aout, "volume ducked by %ls of %u sessions", sid, count);
450 aout_PolicyReport(aout, true);
455 vlc_AudioVolumeDuckNotification_OnVolumeUnduckNotification(
456 IAudioVolumeDuckNotification *this, LPCWSTR sid)
458 aout_sys_t *sys = vlc_AudioVolumeDuckNotification_sys(this);
459 audio_output_t *aout = sys->aout;
461 msg_Dbg(aout, "volume unducked by %ls", sid);
463 aout_PolicyReport(aout, sys->ducks != 0);
467 static const struct IAudioVolumeDuckNotificationVtbl vlc_AudioVolumeDuckNotification =
469 vlc_AudioVolumeDuckNotification_QueryInterface,
470 vlc_AudioVolumeDuckNotification_AddRef,
471 vlc_AudioVolumeDuckNotification_Release,
473 vlc_AudioVolumeDuckNotification_OnVolumeDuckNotification,
474 vlc_AudioVolumeDuckNotification_OnVolumeUnduckNotification,
478 /*** Audio devices ***/
480 /** Gets the user-readable device name */
481 static char *DeviceName(IMMDevice *dev)
483 IPropertyStore *props;
488 hr = IMMDevice_OpenPropertyStore(dev, STGM_READ, &props);
493 hr = IPropertyStore_GetValue(props, &PKEY_Device_FriendlyName, &v);
496 name = FromWide(v.pwszVal);
497 PropVariantClear(&v);
499 IPropertyStore_Release(props);
503 /** Checks that a device is an output device */
504 static bool DeviceIsRender(IMMDevice *dev)
508 if (FAILED(IMMDevice_QueryInterface(dev, &IID_IMMEndpoint, &pv)))
511 IMMEndpoint *ep = pv;
514 if (FAILED(IMMEndpoint_GetDataFlow(ep, &flow)))
517 IMMEndpoint_Release(ep);
518 return flow == eRender;
521 static HRESULT DeviceUpdated(audio_output_t *aout, LPCWSTR wid)
523 aout_sys_t *sys = aout->sys;
527 hr = IMMDeviceEnumerator_GetDevice(sys->it, wid, &dev);
531 if (!DeviceIsRender(dev))
533 IMMDevice_Release(dev);
537 char *id = FromWide(wid);
538 if (unlikely(id == NULL))
540 IMMDevice_Release(dev);
541 return E_OUTOFMEMORY;
544 char *name = DeviceName(dev);
545 IMMDevice_Release(dev);
547 aout_HotplugReport(aout, id, (name != NULL) ? name : id);
553 static inline aout_sys_t *vlc_MMNotificationClient_sys(IMMNotificationClient *this)
555 return (void *)(((char *)this) - offsetof(aout_sys_t, device_events));
559 vlc_MMNotificationClient_QueryInterface(IMMNotificationClient *this,
560 REFIID riid, void **ppv)
562 if (IsEqualIID(riid, &IID_IUnknown)
563 || IsEqualIID(riid, &IID_IMMNotificationClient))
566 IUnknown_AddRef(this);
572 return E_NOINTERFACE;
576 static STDMETHODIMP_(ULONG)
577 vlc_MMNotificationClient_AddRef(IMMNotificationClient *this)
579 aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
580 return InterlockedIncrement(&sys->refs);
583 static STDMETHODIMP_(ULONG)
584 vlc_MMNotificationClient_Release(IMMNotificationClient *this)
586 aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
587 return InterlockedDecrement(&sys->refs);
591 vlc_MMNotificationClient_OnDefaultDeviceChange(IMMNotificationClient *this,
592 EDataFlow flow, ERole role,
595 aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
596 audio_output_t *aout = sys->aout;
600 if (role != eConsole)
603 msg_Dbg(aout, "default device changed: %ls", wid); /* TODO? migrate */
608 vlc_MMNotificationClient_OnDeviceAdded(IMMNotificationClient *this,
611 aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
612 audio_output_t *aout = sys->aout;
614 msg_Dbg(aout, "device %ls added", wid);
615 return DeviceUpdated(aout, wid);
619 vlc_MMNotificationClient_OnDeviceRemoved(IMMNotificationClient *this,
622 aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
623 audio_output_t *aout = sys->aout;
624 char *id = FromWide(wid);
626 msg_Dbg(aout, "device %ls removed", wid);
627 if (unlikely(id == NULL))
628 return E_OUTOFMEMORY;
630 aout_HotplugReport(aout, id, NULL);
636 vlc_MMNotificationClient_OnDeviceStateChanged(IMMNotificationClient *this,
637 LPCWSTR wid, DWORD state)
639 aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
640 audio_output_t *aout = sys->aout;
642 /* TODO: show device state / ignore missing devices */
643 msg_Dbg(aout, "device %ls state changed %08lx", wid, state);
648 vlc_MMNotificationClient_OnPropertyValueChanged(IMMNotificationClient *this,
650 const PROPERTYKEY key)
652 aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
653 audio_output_t *aout = sys->aout;
655 if (key.pid == PKEY_Device_FriendlyName.pid)
657 msg_Dbg(aout, "device %ls name changed", wid);
658 return DeviceUpdated(aout, wid);
663 static const struct IMMNotificationClientVtbl vlc_MMNotificationClient =
665 vlc_MMNotificationClient_QueryInterface,
666 vlc_MMNotificationClient_AddRef,
667 vlc_MMNotificationClient_Release,
669 vlc_MMNotificationClient_OnDeviceStateChanged,
670 vlc_MMNotificationClient_OnDeviceAdded,
671 vlc_MMNotificationClient_OnDeviceRemoved,
672 vlc_MMNotificationClient_OnDefaultDeviceChange,
673 vlc_MMNotificationClient_OnPropertyValueChanged,
676 static int DevicesEnum(audio_output_t *aout, IMMDeviceEnumerator *it)
679 IMMDeviceCollection *devs;
681 hr = IMMDeviceEnumerator_EnumAudioEndpoints(it, eRender,
682 DEVICE_STATE_ACTIVE, &devs);
685 msg_Warn(aout, "cannot enumerate audio endpoints (error 0x%lx)", hr);
690 hr = IMMDeviceCollection_GetCount(devs, &count);
693 msg_Warn(aout, "cannot count audio endpoints (error 0x%lx)", hr);
699 for (UINT i = 0; i < count; i++)
704 hr = IMMDeviceCollection_Item(devs, i, &dev);
708 /* Unique device ID */
710 hr = IMMDevice_GetId(dev, &devid);
713 IMMDevice_Release(dev);
716 id = FromWide(devid);
717 CoTaskMemFree(devid);
719 name = DeviceName(dev);
720 IMMDevice_Release(dev);
722 aout_HotplugReport(aout, id, (name != NULL) ? name : id);
727 IMMDeviceCollection_Release(devs);
731 static int DeviceSelect(audio_output_t *aout, const char *id)
733 aout_sys_t *sys = aout->sys;
739 if (unlikely(device == NULL))
743 device = default_device;
745 EnterCriticalSection(&sys->lock);
746 assert(sys->device == NULL);
747 sys->device = device;
749 WakeConditionVariable(&sys->work);
750 while (sys->device != NULL)
751 SleepConditionVariableCS(&sys->ready, &sys->lock, INFINITE);
752 LeaveCriticalSection(&sys->lock);
754 if (sys->stream != NULL && sys->dev != NULL)
755 /* Request restart of stream with the new device */
756 aout_RestartRequest(aout, AOUT_RESTART_OUTPUT);
757 return (sys->dev != NULL) ? 0 : -1;
760 /*** Initialization / deinitialization **/
761 static wchar_t *var_InheritWide(vlc_object_t *obj, const char *name)
763 char *v8 = var_InheritString(obj, name);
767 wchar_t *v16 = ToWide(v8);
771 #define var_InheritWide(o,n) var_InheritWide(VLC_OBJECT(o),n)
773 /** MMDevice audio output thread.
774 * This thread takes cares of the audio session control. Inconveniently enough,
775 * the audio session control interface must:
776 * - be created and destroyed from the same thread, and
777 * - survive across VLC audio output calls.
778 * The only way to reconcile both requirements is a custom thread.
779 * The thread also ensure that the COM Multi-Thread Apartment is continuously
780 * referenced so that MMDevice objects are not destroyed early.
781 * Furthermore, VolumeSet() and MuteSet() may be called from a thread with a
782 * COM STA, so that it cannot access the COM MTA for audio controls.
784 static HRESULT MMSession(audio_output_t *aout, IMMDeviceEnumerator *it)
786 aout_sys_t *sys = aout->sys;
787 IAudioSessionManager *manager;
788 IAudioSessionControl *control;
789 ISimpleAudioVolume *volume;
790 IAudioEndpointVolume *endpoint;
794 assert(sys->device != NULL);
795 assert(sys->dev == NULL);
797 if (sys->device != default_device) /* Device selected explicitly */
799 msg_Dbg(aout, "using selected device %ls", sys->device);
800 hr = IMMDeviceEnumerator_GetDevice(it, sys->device, &sys->dev);
802 msg_Err(aout, "cannot get selected device %ls (error 0x%lx)",
807 hr = AUDCLNT_E_DEVICE_INVALIDATED;
809 while (hr == AUDCLNT_E_DEVICE_INVALIDATED)
810 { /* Default device selected by policy and with stream routing.
811 * "Do not use eMultimedia" says MSDN. */
812 msg_Dbg(aout, "using default device");
813 hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(it, eRender,
814 eConsole, &sys->dev);
816 msg_Err(aout, "cannot get default device (error 0x%lx)", hr);
820 WakeConditionVariable(&sys->ready);
823 { /* Report actual device */
826 hr = IMMDevice_GetId(sys->dev, &wdevid);
829 char *id = FromWide(wdevid);
830 CoTaskMemFree(wdevid);
831 if (likely(id != NULL))
833 aout_DeviceReport(aout, id);
840 msg_Err(aout, "cannot get device identifier (error 0x%lx)", hr);
844 /* Create session manager (for controls even w/o active audio client) */
845 hr = IMMDevice_Activate(sys->dev, &IID_IAudioSessionManager,
846 CLSCTX_ALL, NULL, &pv);
850 LPCGUID guid = &GUID_VLC_AUD_OUT;
852 /* Register session control */
853 hr = IAudioSessionManager_GetAudioSessionControl(manager, guid, 0,
857 wchar_t *ua = var_InheritWide(aout, "user-agent");
858 IAudioSessionControl_SetDisplayName(control, ua, NULL);
861 IAudioSessionControl_RegisterAudioSessionNotification(control,
862 &sys->session_events);
865 msg_Err(aout, "cannot get session control (error 0x%lx)", hr);
867 hr = IAudioSessionManager_GetSimpleAudioVolume(manager, guid, FALSE,
870 msg_Err(aout, "cannot get simple volume (error 0x%lx)", hr);
872 /* Try to get version 2 (Windows 7) of the manager & control */
873 wchar_t *siid = NULL;
875 hr = IAudioSessionManager_QueryInterface(manager,
876 &IID_IAudioSessionControl2, &pv);
879 IAudioSessionControl2 *c2 = pv;
881 IAudioSessionControl2_SetDuckingPreference(c2, FALSE);
882 hr = IAudioSessionControl2_GetSessionInstanceIdentifier(c2, &siid);
885 IAudioSessionControl2_Release(c2);
888 msg_Dbg(aout, "version 2 session control unavailable");
890 hr = IAudioSessionManager_QueryInterface(manager,
891 &IID_IAudioSessionManager2, &pv);
894 IAudioSessionManager2 *m2 = pv;
896 IAudioSessionManager2_RegisterDuckNotification(m2, siid,
898 IAudioSessionManager2_Release(m2);
901 msg_Dbg(aout, "version 2 session management unavailable");
907 msg_Err(aout, "cannot activate session manager (error 0x%lx)", hr);
912 hr = IMMDevice_Activate(sys->dev, &IID_IAudioEndpointVolume,
913 CLSCTX_ALL, NULL, &pv);
919 hr = IAudioEndpointVolume_GetVolumeRange(endpoint, &min, &max, &inc);
921 msg_Dbg(aout, "volume from %+f dB to %+f dB with %f dB increments",
924 msg_Err(aout, "cannot get volume range (error 0x%lx)", hr);
927 msg_Err(aout, "cannot activate endpoint volume (error %lx)", hr);
929 /* Main loop (adjust volume as long as device is unchanged) */
930 while (sys->device == NULL)
936 hr = ISimpleAudioVolume_GetMasterVolume(volume, &level);
938 aout_VolumeReport(aout, cbrtf(level * sys->gain));
940 msg_Err(aout, "cannot get master volume (error 0x%lx)", hr);
945 hr = ISimpleAudioVolume_SetMasterVolume(volume, level, NULL);
947 msg_Err(aout, "cannot set master volume (error 0x%lx)",
954 hr = ISimpleAudioVolume_GetMute(volume, &mute);
956 aout_MuteReport(aout, mute != FALSE);
958 msg_Err(aout, "cannot get mute (error 0x%lx)", hr);
962 mute = sys->mute ? TRUE : FALSE;
964 hr = ISimpleAudioVolume_SetMute(volume, mute, NULL);
966 msg_Err(aout, "cannot set mute (error 0x%lx)", hr);
971 SleepConditionVariableCS(&sys->work, &sys->lock, INFINITE);
973 LeaveCriticalSection(&sys->lock);
975 if (endpoint != NULL)
976 IAudioEndpointVolume_Release(endpoint);
979 { /* Deregister callbacks *without* the lock */
980 hr = IAudioSessionManager_QueryInterface(manager,
981 &IID_IAudioSessionManager2, &pv);
984 IAudioSessionManager2 *m2 = pv;
986 IAudioSessionManager2_UnregisterDuckNotification(m2, &sys->duck);
987 IAudioSessionManager2_Release(m2);
991 ISimpleAudioVolume_Release(volume);
995 IAudioSessionControl_UnregisterAudioSessionNotification(control,
996 &sys->session_events);
997 IAudioSessionControl_Release(control);
1000 IAudioSessionManager_Release(manager);
1003 EnterCriticalSection(&sys->lock);
1004 IMMDevice_Release(sys->dev);
1009 static void *MMThread(void *data)
1011 audio_output_t *aout = data;
1012 aout_sys_t *sys = aout->sys;
1013 IMMDeviceEnumerator *it = sys->it;
1016 IMMDeviceEnumerator_RegisterEndpointNotificationCallback(it,
1017 &sys->device_events);
1018 DevicesEnum(aout, it);
1020 EnterCriticalSection(&sys->lock);
1023 if (FAILED(MMSession(aout, it)))
1024 SleepConditionVariableCS(&sys->work, &sys->lock, INFINITE);
1025 while (sys->it != NULL);
1027 LeaveCriticalSection(&sys->lock);
1029 IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(it,
1030 &sys->device_events);
1031 IMMDeviceEnumerator_Release(it);
1037 * Callback for aout_stream_t to create a stream on the device.
1038 * This can instantiate an IAudioClient or IDirectSound(8) object.
1040 static HRESULT ActivateDevice(void *opaque, REFIID iid, PROPVARIANT *actparms,
1043 IMMDevice *dev = opaque;
1044 return IMMDevice_Activate(dev, iid, CLSCTX_ALL, actparms, pv);
1047 static int aout_stream_Start(void *func, va_list ap)
1049 aout_stream_start_t start = func;
1050 aout_stream_t *s = va_arg(ap, aout_stream_t *);
1051 audio_sample_format_t *fmt = va_arg(ap, audio_sample_format_t *);
1052 HRESULT *hr = va_arg(ap, HRESULT *);
1054 *hr = start(s, fmt, &GUID_VLC_AUD_OUT);
1055 if (*hr == AUDCLNT_E_DEVICE_INVALIDATED)
1056 return VLC_ETIMEOUT;
1057 return SUCCEEDED(*hr) ? VLC_SUCCESS : VLC_EGENERIC;
1060 static void aout_stream_Stop(void *func, va_list ap)
1062 aout_stream_stop_t stop = func;
1063 aout_stream_t *s = va_arg(ap, aout_stream_t *);
1068 static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
1070 aout_sys_t *sys = aout->sys;
1072 if (sys->dev == NULL)
1075 aout_stream_t *s = vlc_object_create(aout, sizeof (*s));
1076 if (unlikely(s == NULL))
1079 s->owner.device = sys->dev;
1080 s->owner.activate = ActivateDevice;
1087 /* TODO: Do not overload the "aout" configuration item. */
1088 sys->module = vlc_module_load(s, "aout stream", "$aout", false,
1089 aout_stream_Start, s, fmt, &hr);
1090 if (hr != AUDCLNT_E_DEVICE_INVALIDATED || DeviceSelect(aout, NULL))
1095 if (sys->module == NULL)
1097 vlc_object_release(s);
1101 assert (sys->stream == NULL);
1103 aout_GainRequest(aout, sys->gain);
1107 static void Stop(audio_output_t *aout)
1109 aout_sys_t *sys = aout->sys;
1111 assert(sys->stream != NULL);
1114 vlc_module_unload(sys->module, aout_stream_Stop, sys->stream);
1117 vlc_object_release(sys->stream);
1121 static int Open(vlc_object_t *obj)
1123 audio_output_t *aout = (audio_output_t *)obj;
1125 aout_sys_t *sys = malloc(sizeof (*sys));
1126 if (unlikely(sys == NULL))
1134 sys->device_events.lpVtbl = &vlc_MMNotificationClient;
1135 sys->session_events.lpVtbl = &vlc_AudioSessionEvents;
1136 sys->duck.lpVtbl = &vlc_AudioVolumeDuckNotification;
1140 sys->device = default_device;
1144 InitializeCriticalSection(&sys->lock);
1145 InitializeConditionVariable(&sys->work);
1146 InitializeConditionVariable(&sys->ready);
1148 /* Initialize MMDevice API */
1149 if (TryEnterMTA(aout))
1153 HRESULT hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
1154 &IID_IMMDeviceEnumerator, &pv);
1158 msg_Dbg(aout, "cannot create device enumerator (error 0x%lx)", hr);
1163 if (vlc_clone(&sys->thread, MMThread, aout, VLC_THREAD_PRIORITY_LOW))
1165 IMMDeviceEnumerator_Release(sys->it);
1170 EnterCriticalSection(&sys->lock);
1171 while (sys->device != NULL)
1172 SleepConditionVariableCS(&sys->ready, &sys->lock, INFINITE);
1173 LeaveCriticalSection(&sys->lock);
1174 LeaveMTA(); /* Leave MTA after thread has entered MTA */
1176 aout->start = Start;
1178 aout->time_get = TimeGet;
1180 aout->pause = Pause;
1181 aout->flush = Flush;
1182 aout->volume_set = VolumeSet;
1183 aout->mute_set = MuteSet;
1184 aout->device_select = DeviceSelect;
1188 DeleteCriticalSection(&sys->lock);
1190 return VLC_EGENERIC;
1193 static void Close(vlc_object_t *obj)
1195 audio_output_t *aout = (audio_output_t *)obj;
1196 aout_sys_t *sys = aout->sys;
1198 EnterCriticalSection(&sys->lock);
1199 sys->device = default_device; /* break out of MMSession() loop */
1200 sys->it = NULL; /* break out of MMThread() loop */
1201 WakeConditionVariable(&sys->work);
1202 LeaveCriticalSection(&sys->lock);
1204 vlc_join(sys->thread, NULL);
1205 DeleteCriticalSection(&sys->lock);
1210 set_shortname("MMDevice")
1211 set_description(N_("Windows Multimedia Device output"))
1212 set_capability("audio output", 150)
1213 set_category(CAT_AUDIO)
1214 set_subcategory(SUBCAT_AUDIO_AOUT)
1215 add_shortcut("wasapi", "directsound")
1216 set_callbacks(Open, Close)