1 /*****************************************************************************
2 * mmdevice.c : Windows Multimedia Device API audio output plugin for VLC
3 *****************************************************************************
4 * Copyright (C) 2012-2013 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>
35 DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd,
36 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
38 #include <vlc_common.h>
39 #include <vlc_plugin.h>
41 #include <vlc_charset.h>
42 #include <vlc_modules.h>
43 #include "audio_output/mmdevice.h"
45 #if (_WIN32_WINNT < 0x600)
46 static VOID WINAPI (*InitializeConditionVariable)(PCONDITION_VARIABLE);
47 static BOOL WINAPI (*SleepConditionVariableCS)(PCONDITION_VARIABLE,
48 PCRITICAL_SECTION, DWORD);
49 static VOID WINAPI (*WakeConditionVariable)(PCONDITION_VARIABLE);
51 if (((s) = (void *)GetProcAddress(h, #s)) == NULL) return FALSE
53 BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID); /* avoid warning */
54 BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, LPVOID reserved)
61 case DLL_PROCESS_ATTACH:
63 HANDLE h = GetModuleHandle(TEXT("kernel32.dll"));
64 if (unlikely(h == NULL))
66 LOOKUP(InitializeConditionVariable);
67 LOOKUP(SleepConditionVariableCS);
68 LOOKUP(WakeConditionVariable);
76 DEFINE_GUID (GUID_VLC_AUD_OUT, 0x4533f59d, 0x59ee, 0x00c6,
77 0xad, 0xb2, 0xc6, 0x8b, 0x50, 0x1a, 0x66, 0x55);
79 static int TryEnterMTA(vlc_object_t *obj)
81 HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
82 if (unlikely(FAILED(hr)))
84 msg_Err (obj, "cannot initialize COM (error 0x%lx)", hr);
89 #define TryEnterMTA(o) TryEnterMTA(VLC_OBJECT(o))
91 static void EnterMTA(void)
93 HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
94 if (unlikely(FAILED(hr)))
98 static void LeaveMTA(void)
103 static wchar_t default_device[1] = L"";
107 aout_stream_t *stream; /**< Underlying audio output stream */
109 audio_output_t *aout;
110 IMMDeviceEnumerator *it; /**< Device enumerator, NULL when exiting */
111 IMMDevice *dev; /**< Selected output device, NULL if none */
113 struct IMMNotificationClient device_events;
114 struct IAudioSessionEvents session_events;
118 wchar_t *device; /**< Requested device identifier, NULL if none */
119 float volume; /**< Requested volume, negative if none */
120 signed char mute; /**< Requested mute, negative if none */
121 CRITICAL_SECTION lock;
122 CONDITION_VARIABLE work;
123 CONDITION_VARIABLE ready;
124 vlc_thread_t thread; /**< Thread for audio session control */
127 /* NOTE: The Core Audio API documentation totally fails to specify the thread
128 * safety (or lack thereof) of the interfaces. This code takes the most
129 * restrictive assumption: no thread safety. The background thread (MMThread)
130 * only runs at specified times, namely between the device_ready and
131 * device_changed events (effectively a thread synchronization barrier, but
132 * only Windows 8 natively provides such a primitive).
134 * The audio output owner (i.e. the audio output core) is responsible for
135 * serializing callbacks. This code only needs to be concerned with
136 * synchronization between the set of audio output callbacks, MMThread()
137 * and (trivially) the device and session notifications. */
139 static int vlc_FromHR(audio_output_t *aout, HRESULT hr)
141 /* Restart on unplug */
142 if (unlikely(hr == AUDCLNT_E_DEVICE_INVALIDATED))
143 aout_RestartRequest(aout, AOUT_RESTART_OUTPUT);
144 return SUCCEEDED(hr) ? 0 : -1;
147 /*** VLC audio output callbacks ***/
148 static int TimeGet(audio_output_t *aout, mtime_t *restrict delay)
150 aout_sys_t *sys = aout->sys;
154 hr = aout_stream_TimeGet(sys->stream, delay);
157 return SUCCEEDED(hr) ? 0 : -1;
160 static void Play(audio_output_t *aout, block_t *block)
162 aout_sys_t *sys = aout->sys;
166 hr = aout_stream_Play(sys->stream, block);
169 vlc_FromHR(aout, hr);
172 static void Pause(audio_output_t *aout, bool paused, mtime_t date)
174 aout_sys_t *sys = aout->sys;
178 hr = aout_stream_Pause(sys->stream, paused);
181 vlc_FromHR(aout, hr);
185 static void Flush(audio_output_t *aout, bool wait)
187 aout_sys_t *sys = aout->sys;
190 aout_stream_Flush(sys->stream, wait);
195 static int VolumeSet(audio_output_t *aout, float vol)
197 aout_sys_t *sys = aout->sys;
199 vol = vol * vol * vol; /* ISimpleAudioVolume is tapered linearly. */
200 EnterCriticalSection(&sys->lock);
202 WakeConditionVariable(&sys->work);
203 LeaveCriticalSection(&sys->lock);
207 static int MuteSet(audio_output_t *aout, bool mute)
209 aout_sys_t *sys = aout->sys;
211 EnterCriticalSection(&sys->lock);
213 WakeConditionVariable(&sys->work);
214 LeaveCriticalSection(&sys->lock);
218 /*** Audio session events ***/
219 static inline aout_sys_t *vlc_AudioSessionEvents_sys(IAudioSessionEvents *this)
221 return (void *)(((char *)this) - offsetof(aout_sys_t, session_events));
225 vlc_AudioSessionEvents_QueryInterface(IAudioSessionEvents *this, REFIID riid,
228 if (IsEqualIID(riid, &IID_IUnknown)
229 || IsEqualIID(riid, &IID_IAudioSessionEvents))
232 IUnknown_AddRef(this);
238 return E_NOINTERFACE;
242 static STDMETHODIMP_(ULONG)
243 vlc_AudioSessionEvents_AddRef(IAudioSessionEvents *this)
245 aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
246 return InterlockedIncrement(&sys->refs);
249 static STDMETHODIMP_(ULONG)
250 vlc_AudioSessionEvents_Release(IAudioSessionEvents *this)
252 aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
253 return InterlockedDecrement(&sys->refs);
257 vlc_AudioSessionEvents_OnDisplayNameChanged(IAudioSessionEvents *this,
258 LPCWSTR wname, LPCGUID ctx)
260 aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
261 audio_output_t *aout = sys->aout;
263 msg_Dbg(aout, "display name changed: %ls", wname);
269 vlc_AudioSessionEvents_OnIconPathChanged(IAudioSessionEvents *this,
270 LPCWSTR wpath, LPCGUID ctx)
272 aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
273 audio_output_t *aout = sys->aout;
275 msg_Dbg(aout, "icon path changed: %ls", wpath);
281 vlc_AudioSessionEvents_OnSimpleVolumeChanged(IAudioSessionEvents *this,
282 float vol, WINBOOL mute,
285 aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
286 audio_output_t *aout = sys->aout;
288 msg_Dbg(aout, "simple volume changed: %f, muting %sabled", vol,
289 mute ? "en" : "dis");
290 aout_VolumeReport(aout, cbrtf(vol));
291 aout_MuteReport(aout, mute == TRUE);
297 vlc_AudioSessionEvents_OnChannelVolumeChanged(IAudioSessionEvents *this,
298 DWORD count, float *vols,
299 DWORD changed, LPCGUID ctx)
301 aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
302 audio_output_t *aout = sys->aout;
304 msg_Dbg(aout, "channel volume %lu of %lu changed: %f", changed, count,
311 vlc_AudioSessionEvents_OnGroupingParamChanged(IAudioSessionEvents *this,
312 LPCGUID param, LPCGUID ctx)
315 aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
316 audio_output_t *aout = sys->aout;
318 msg_Dbg(aout, "grouping parameter changed");
325 vlc_AudioSessionEvents_OnStateChanged(IAudioSessionEvents *this,
326 AudioSessionState state)
328 aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
329 audio_output_t *aout = sys->aout;
331 msg_Dbg(aout, "state changed: %d", state);
336 vlc_AudioSessionEvents_OnSessionDisconnected(IAudioSessionEvents *this,
337 AudioSessionDisconnectReason reason)
339 aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
340 audio_output_t *aout = sys->aout;
344 case DisconnectReasonDeviceRemoval:
345 msg_Warn(aout, "session disconnected: %s", "device removed");
347 case DisconnectReasonServerShutdown:
348 msg_Err(aout, "session disconnected: %s", "service stopped");
350 case DisconnectReasonFormatChanged:
351 msg_Warn(aout, "session disconnected: %s", "format changed");
353 case DisconnectReasonSessionLogoff:
354 msg_Err(aout, "session disconnected: %s", "user logged off");
356 case DisconnectReasonSessionDisconnected:
357 msg_Err(aout, "session disconnected: %s", "session disconnected");
359 case DisconnectReasonExclusiveModeOverride:
360 msg_Err(aout, "session disconnected: %s", "stream overriden");
363 msg_Warn(aout, "session disconnected: unknown reason %d", reason);
366 /* NOTE: audio decoder thread should get invalidated device and restart */
370 static const struct IAudioSessionEventsVtbl vlc_AudioSessionEvents =
372 vlc_AudioSessionEvents_QueryInterface,
373 vlc_AudioSessionEvents_AddRef,
374 vlc_AudioSessionEvents_Release,
376 vlc_AudioSessionEvents_OnDisplayNameChanged,
377 vlc_AudioSessionEvents_OnIconPathChanged,
378 vlc_AudioSessionEvents_OnSimpleVolumeChanged,
379 vlc_AudioSessionEvents_OnChannelVolumeChanged,
380 vlc_AudioSessionEvents_OnGroupingParamChanged,
381 vlc_AudioSessionEvents_OnStateChanged,
382 vlc_AudioSessionEvents_OnSessionDisconnected,
385 /*** Audio devices ***/
387 /** Gets the user-readable device name */
388 static char *DeviceName(IMMDevice *dev)
390 IPropertyStore *props;
395 hr = IMMDevice_OpenPropertyStore(dev, STGM_READ, &props);
400 hr = IPropertyStore_GetValue(props, &PKEY_Device_FriendlyName, &v);
403 name = FromWide(v.pwszVal);
404 PropVariantClear(&v);
406 IPropertyStore_Release(props);
410 /** Checks that a device is an output device */
411 static bool DeviceIsRender(IMMDevice *dev)
415 if (FAILED(IMMDevice_QueryInterface(dev, &IID_IMMEndpoint, &pv)))
418 IMMEndpoint *ep = pv;
421 if (FAILED(IMMEndpoint_GetDataFlow(ep, &flow)))
424 IMMEndpoint_Release(ep);
425 return flow == eRender;
428 static HRESULT DeviceUpdated(audio_output_t *aout, LPCWSTR wid)
430 aout_sys_t *sys = aout->sys;
434 hr = IMMDeviceEnumerator_GetDevice(sys->it, wid, &dev);
438 if (!DeviceIsRender(dev))
440 IMMDevice_Release(dev);
444 char *id = FromWide(wid);
445 if (unlikely(id == NULL))
447 IMMDevice_Release(dev);
448 return E_OUTOFMEMORY;
451 char *name = DeviceName(dev);
452 IMMDevice_Release(dev);
454 aout_HotplugReport(aout, id, (name != NULL) ? name : id);
460 static inline aout_sys_t *vlc_MMNotificationClient_sys(IMMNotificationClient *this)
462 return (void *)(((char *)this) - offsetof(aout_sys_t, device_events));
466 vlc_MMNotificationClient_QueryInterface(IMMNotificationClient *this,
467 REFIID riid, void **ppv)
469 if (IsEqualIID(riid, &IID_IUnknown)
470 || IsEqualIID(riid, &IID_IMMNotificationClient))
473 IUnknown_AddRef(this);
479 return E_NOINTERFACE;
483 static STDMETHODIMP_(ULONG)
484 vlc_MMNotificationClient_AddRef(IMMNotificationClient *this)
486 aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
487 return InterlockedIncrement(&sys->refs);
490 static STDMETHODIMP_(ULONG)
491 vlc_MMNotificationClient_Release(IMMNotificationClient *this)
493 aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
494 return InterlockedDecrement(&sys->refs);
498 vlc_MMNotificationClient_OnDefaultDeviceChange(IMMNotificationClient *this,
499 EDataFlow flow, ERole role,
502 aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
503 audio_output_t *aout = sys->aout;
507 if (role != eConsole)
510 msg_Dbg(aout, "default device changed: %ls", wid); /* TODO? migrate */
515 vlc_MMNotificationClient_OnDeviceAdded(IMMNotificationClient *this,
518 aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
519 audio_output_t *aout = sys->aout;
521 msg_Dbg(aout, "device %ls added", wid);
522 return DeviceUpdated(aout, wid);
526 vlc_MMNotificationClient_OnDeviceRemoved(IMMNotificationClient *this,
529 aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
530 audio_output_t *aout = sys->aout;
531 char *id = FromWide(wid);
533 msg_Dbg(aout, "device %ls removed", wid);
534 if (unlikely(id == NULL))
535 return E_OUTOFMEMORY;
537 aout_HotplugReport(aout, id, NULL);
543 vlc_MMNotificationClient_OnDeviceStateChanged(IMMNotificationClient *this,
544 LPCWSTR wid, DWORD state)
546 aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
547 audio_output_t *aout = sys->aout;
549 /* TODO: show device state / ignore missing devices */
550 msg_Dbg(aout, "device %ls state changed %08lx", wid, state);
555 vlc_MMNotificationClient_OnPropertyValueChanged(IMMNotificationClient *this,
557 const PROPERTYKEY key)
559 aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
560 audio_output_t *aout = sys->aout;
562 if (key.pid == PKEY_Device_FriendlyName.pid)
564 msg_Dbg(aout, "device %ls name changed", wid);
565 return DeviceUpdated(aout, wid);
570 static const struct IMMNotificationClientVtbl vlc_MMNotificationClient =
572 vlc_MMNotificationClient_QueryInterface,
573 vlc_MMNotificationClient_AddRef,
574 vlc_MMNotificationClient_Release,
576 vlc_MMNotificationClient_OnDeviceStateChanged,
577 vlc_MMNotificationClient_OnDeviceAdded,
578 vlc_MMNotificationClient_OnDeviceRemoved,
579 vlc_MMNotificationClient_OnDefaultDeviceChange,
580 vlc_MMNotificationClient_OnPropertyValueChanged,
583 static int DevicesEnum(audio_output_t *aout, IMMDeviceEnumerator *it)
586 IMMDeviceCollection *devs;
588 hr = IMMDeviceEnumerator_EnumAudioEndpoints(it, eRender,
589 DEVICE_STATE_ACTIVE, &devs);
592 msg_Warn(aout, "cannot enumerate audio endpoints (error 0x%lx)", hr);
597 hr = IMMDeviceCollection_GetCount(devs, &count);
600 msg_Warn(aout, "cannot count audio endpoints (error 0x%lx)", hr);
606 for (UINT i = 0; i < count; i++)
611 hr = IMMDeviceCollection_Item(devs, i, &dev);
615 /* Unique device ID */
617 hr = IMMDevice_GetId(dev, &devid);
620 IMMDevice_Release(dev);
623 id = FromWide(devid);
624 CoTaskMemFree(devid);
626 name = DeviceName(dev);
627 IMMDevice_Release(dev);
629 aout_HotplugReport(aout, id, (name != NULL) ? name : id);
634 IMMDeviceCollection_Release(devs);
638 static int DeviceSelect(audio_output_t *aout, const char *id)
640 aout_sys_t *sys = aout->sys;
646 if (unlikely(device == NULL))
650 device = default_device;
652 EnterCriticalSection(&sys->lock);
653 assert(sys->device == NULL);
654 sys->device = device;
656 WakeConditionVariable(&sys->work);
657 while (sys->device != NULL)
658 SleepConditionVariableCS(&sys->ready, &sys->lock, INFINITE);
659 LeaveCriticalSection(&sys->lock);
661 if (sys->stream != NULL && sys->dev != NULL)
662 /* Request restart of stream with the new device */
663 aout_RestartRequest(aout, AOUT_RESTART_OUTPUT);
664 return (sys->dev != NULL) ? 0 : -1;
667 /*** Initialization / deinitialization **/
668 static wchar_t *var_InheritWide(vlc_object_t *obj, const char *name)
670 char *v8 = var_InheritString(obj, name);
674 wchar_t *v16 = ToWide(v8);
678 #define var_InheritWide(o,n) var_InheritWide(VLC_OBJECT(o),n)
680 /** MMDevice audio output thread.
681 * This thread takes cares of the audio session control. Inconveniently enough,
682 * the audio session control interface must:
683 * - be created and destroyed from the same thread, and
684 * - survive across VLC audio output calls.
685 * The only way to reconcile both requirements is a custom thread.
686 * The thread also ensure that the COM Multi-Thread Apartment is continuously
687 * referenced so that MMDevice objects are not destroyed early.
688 * Furthermore, VolumeSet() and MuteSet() may be called from a thread with a
689 * COM STA, so that it cannot access the COM MTA for audio controls.
691 static HRESULT MMSession(audio_output_t *aout, IMMDeviceEnumerator *it)
693 aout_sys_t *sys = aout->sys;
694 IAudioSessionManager *manager;
695 IAudioSessionControl *control;
696 ISimpleAudioVolume *volume;
700 assert(sys->device != NULL);
701 assert(sys->dev == NULL);
703 if (sys->device != default_device) /* Device selected explicitly */
705 msg_Dbg(aout, "using selected device %ls", sys->device);
706 hr = IMMDeviceEnumerator_GetDevice(it, sys->device, &sys->dev);
708 msg_Err(aout, "cannot get selected device %ls (error 0x%lx)",
713 hr = AUDCLNT_E_DEVICE_INVALIDATED;
715 while (hr == AUDCLNT_E_DEVICE_INVALIDATED)
716 { /* Default device selected by policy and with stream routing.
717 * "Do not use eMultimedia" says MSDN. */
718 msg_Dbg(aout, "using default device");
719 hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(it, eRender,
720 eConsole, &sys->dev);
722 msg_Err(aout, "cannot get default device (error 0x%lx)", hr);
726 WakeConditionVariable(&sys->ready);
729 { /* Report actual device */
732 hr = IMMDevice_GetId(sys->dev, &wdevid);
735 char *id = FromWide(wdevid);
736 CoTaskMemFree(wdevid);
737 if (likely(id != NULL))
739 aout_DeviceReport(aout, id);
746 msg_Err(aout, "cannot get device identifier (error 0x%lx)", hr);
750 /* Create session manager (for controls even w/o active audio client) */
751 hr = IMMDevice_Activate(sys->dev, &IID_IAudioSessionManager,
752 CLSCTX_ALL, NULL, &pv);
756 LPCGUID guid = &GUID_VLC_AUD_OUT;
758 /* Register session control */
759 hr = IAudioSessionManager_GetAudioSessionControl(manager, guid, 0,
763 wchar_t *ua = var_InheritWide(aout, "user-agent");
764 IAudioSessionControl_SetDisplayName(control, ua, NULL);
767 IAudioSessionControl_RegisterAudioSessionNotification(control,
768 &sys->session_events);
771 msg_Err(aout, "cannot get session control (error 0x%lx)", hr);
773 hr = IAudioSessionManager_GetSimpleAudioVolume(manager, guid, FALSE,
776 { /* Get current values _after_ registering for notification */
780 hr = ISimpleAudioVolume_GetMute(volume, &mute);
782 aout_MuteReport(aout, mute != FALSE);
784 msg_Err(aout, "cannot get mute (error 0x%lx)", hr);
786 hr = ISimpleAudioVolume_GetMasterVolume(volume, &level);
788 aout_VolumeReport(aout, level);
790 msg_Err(aout, "cannot get mute (error 0x%lx)", hr);
793 msg_Err(aout, "cannot get simple volume (error 0x%lx)", hr);
797 msg_Err(aout, "cannot activate session manager (error 0x%lx)", hr);
802 /* Main loop (adjust volume as long as device is unchanged) */
803 while (sys->device == NULL)
805 if (volume != NULL && sys->volume >= 0.f)
807 if (sys->volume > 1.f)
810 hr = ISimpleAudioVolume_SetMasterVolume(volume, sys->volume, NULL);
812 msg_Err(aout, "cannot set master volume (error 0x%lx)", hr);
816 if (volume != NULL && sys->mute >= 0)
818 hr = ISimpleAudioVolume_SetMute(volume,
819 sys->mute ? TRUE : FALSE, NULL);
821 msg_Err(aout, "cannot set mute (error 0x%lx)", hr);
825 SleepConditionVariableCS(&sys->work, &sys->lock, INFINITE);
830 /* Deregister session control */
832 ISimpleAudioVolume_Release(volume);
836 IAudioSessionControl_UnregisterAudioSessionNotification(control,
837 &sys->session_events);
838 IAudioSessionControl_Release(control);
841 IAudioSessionManager_Release(manager);
844 IMMDevice_Release(sys->dev);
849 static void *MMThread(void *data)
851 audio_output_t *aout = data;
852 aout_sys_t *sys = aout->sys;
853 IMMDeviceEnumerator *it = sys->it;
856 IMMDeviceEnumerator_RegisterEndpointNotificationCallback(it,
857 &sys->device_events);
858 DevicesEnum(aout, it);
860 EnterCriticalSection(&sys->lock);
863 if (FAILED(MMSession(aout, it)))
864 SleepConditionVariableCS(&sys->work, &sys->lock, INFINITE);
865 while (sys->it != NULL);
867 LeaveCriticalSection(&sys->lock);
869 IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(it,
870 &sys->device_events);
871 IMMDeviceEnumerator_Release(it);
877 * Callback for aout_stream_t to create a stream on the device.
878 * This can instantiate an IAudioClient or IDirectSound(8) object.
880 static HRESULT ActivateDevice(void *opaque, REFIID iid, PROPVARIANT *actparms,
883 IMMDevice *dev = opaque;
884 return IMMDevice_Activate(dev, iid, CLSCTX_ALL, actparms, pv);
887 static int aout_stream_Start(void *func, va_list ap)
889 aout_stream_start_t start = func;
890 aout_stream_t *s = va_arg(ap, aout_stream_t *);
891 audio_sample_format_t *fmt = va_arg(ap, audio_sample_format_t *);
892 HRESULT *hr = va_arg(ap, HRESULT *);
894 *hr = start(s, fmt, &GUID_VLC_AUD_OUT);
895 if (*hr == AUDCLNT_E_DEVICE_INVALIDATED)
897 return SUCCEEDED(*hr) ? VLC_SUCCESS : VLC_EGENERIC;
900 static void aout_stream_Stop(void *func, va_list ap)
902 aout_stream_stop_t stop = func;
903 aout_stream_t *s = va_arg(ap, aout_stream_t *);
908 static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
910 aout_sys_t *sys = aout->sys;
912 if (sys->dev == NULL)
915 aout_stream_t *s = vlc_object_create(aout, sizeof (*s));
916 if (unlikely(s == NULL))
919 s->owner.device = sys->dev;
920 s->owner.activate = ActivateDevice;
927 sys->module = vlc_module_load(s, "aout stream", NULL, false,
928 aout_stream_Start, s, fmt, &hr);
929 if (hr != AUDCLNT_E_DEVICE_INVALIDATED || DeviceSelect(aout, NULL))
934 if (sys->module == NULL)
936 vlc_object_release(s);
940 assert (sys->stream == NULL);
945 static void Stop(audio_output_t *aout)
947 aout_sys_t *sys = aout->sys;
949 assert (sys->stream != NULL);
952 vlc_module_unload(sys->module, aout_stream_Stop, sys->stream);
955 vlc_object_release(sys->stream);
959 static int Open(vlc_object_t *obj)
961 audio_output_t *aout = (audio_output_t *)obj;
963 aout_sys_t *sys = malloc(sizeof (*sys));
964 if (unlikely(sys == NULL))
972 sys->device_events.lpVtbl = &vlc_MMNotificationClient;
973 sys->session_events.lpVtbl = &vlc_AudioSessionEvents;
976 sys->device = default_device;
979 InitializeCriticalSection(&sys->lock);
980 InitializeConditionVariable(&sys->work);
981 InitializeConditionVariable(&sys->ready);
983 /* Initialize MMDevice API */
984 if (TryEnterMTA(aout))
988 HRESULT hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
989 &IID_IMMDeviceEnumerator, &pv);
993 msg_Dbg(aout, "cannot create device enumerator (error 0x%lx)", hr);
998 if (vlc_clone(&sys->thread, MMThread, aout, VLC_THREAD_PRIORITY_LOW))
1000 IMMDeviceEnumerator_Release(sys->it);
1005 EnterCriticalSection(&sys->lock);
1006 while (sys->device != NULL)
1007 SleepConditionVariableCS(&sys->ready, &sys->lock, INFINITE);
1008 LeaveCriticalSection(&sys->lock);
1009 LeaveMTA(); /* Leave MTA after thread has entered MTA */
1011 aout->start = Start;
1013 aout->time_get = TimeGet;
1015 aout->pause = Pause;
1016 aout->flush = Flush;
1017 aout->volume_set = VolumeSet;
1018 aout->mute_set = MuteSet;
1019 aout->device_select = DeviceSelect;
1023 DeleteCriticalSection(&sys->lock);
1025 return VLC_EGENERIC;
1028 static void Close(vlc_object_t *obj)
1030 audio_output_t *aout = (audio_output_t *)obj;
1031 aout_sys_t *sys = aout->sys;
1033 EnterCriticalSection(&sys->lock);
1034 sys->device = default_device; /* break out of MMSession() loop */
1035 sys->it = NULL; /* break out of MMThread() loop */
1036 WakeConditionVariable(&sys->work);
1037 LeaveCriticalSection(&sys->lock);
1039 vlc_join(sys->thread, NULL);
1040 DeleteCriticalSection(&sys->lock);
1045 set_shortname("MMDevice")
1046 set_description(N_("Windows Multimedia Device output"))
1047 set_capability("audio output", 150)
1048 set_category(CAT_AUDIO)
1049 set_subcategory(SUBCAT_AUDIO_AOUT)
1050 add_shortcut("wasapi")
1051 set_callbacks(Open, Close)