]> git.sesse.net Git - vlc/blobdiff - modules/audio_output/mmdevice.c
mmdevice: avoid incorrect assertion
[vlc] / modules / audio_output / mmdevice.c
index dea9cd6de4f616ccabd2d88496e65cea52cd66e6..2f41567334a62f07168dacf1f4eed1ce1d6c153b 100644 (file)
 # include <config.h>
 #endif
 
-#undef _WIN32_WINNT
-#define _WIN32_WINNT 0x600 /* Windows Vista */
 #define INITGUID
 #define COBJMACROS
 #define CONST_VTABLE
 
 #include <stdlib.h>
+#include <math.h>
 #include <assert.h>
 #include <audiopolicy.h>
 #include <mmdeviceapi.h>
@@ -40,11 +39,44 @@ DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd,
 #include <vlc_plugin.h>
 #include <vlc_aout.h>
 #include <vlc_charset.h>
+#include <vlc_modules.h>
 #include "audio_output/mmdevice.h"
 
+#if (_WIN32_WINNT < 0x600)
+static VOID WINAPI (*InitializeConditionVariable)(PCONDITION_VARIABLE);
+static BOOL WINAPI (*SleepConditionVariableCS)(PCONDITION_VARIABLE,
+                                               PCRITICAL_SECTION, DWORD);
+static VOID WINAPI (*WakeConditionVariable)(PCONDITION_VARIABLE);
+#define LOOKUP(s) \
+    if (((s) = (void *)GetProcAddress(h, #s)) == NULL) return FALSE
+
+BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID); /* avoid warning */
+BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, LPVOID reserved)
+{
+    (void) dll;
+    (void) reserved;
+
+    switch (reason)
+    {
+        case DLL_PROCESS_ATTACH:
+        {
+            HANDLE h = GetModuleHandle(TEXT("kernel32.dll"));
+            if (unlikely(h == NULL))
+                return FALSE;
+            LOOKUP(InitializeConditionVariable);
+            LOOKUP(SleepConditionVariableCS);
+            LOOKUP(WakeConditionVariable);
+            break;
+        }
+    }
+    return TRUE;
+}
+#endif
+
 DEFINE_GUID (GUID_VLC_AUD_OUT, 0x4533f59d, 0x59ee, 0x00c6,
    0xad, 0xb2, 0xc6, 0x8b, 0x50, 0x1a, 0x66, 0x55);
 
+#if !VLC_WINSTORE_APP
 static int TryEnterMTA(vlc_object_t *obj)
 {
     HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
@@ -56,6 +88,7 @@ static int TryEnterMTA(vlc_object_t *obj)
     return 0;
 }
 #define TryEnterMTA(o) TryEnterMTA(VLC_OBJECT(o))
+#endif
 
 static void EnterMTA(void)
 {
@@ -69,17 +102,20 @@ static void LeaveMTA(void)
     CoUninitialize();
 }
 
+#if !VLC_WINSTORE_APP
 static wchar_t default_device[1] = L"";
+#endif
 
 struct aout_sys_t
 {
     aout_stream_t *stream; /**< Underlying audio output stream */
+    module_t *module;
 #if !VLC_WINSTORE_APP
     audio_output_t *aout;
     IMMDeviceEnumerator *it; /**< Device enumerator, NULL when exiting */
-    /*TODO: IMMNotificationClient*/
     IMMDevice *dev; /**< Selected output device, NULL if none */
 
+    struct IMMNotificationClient device_events;
     struct IAudioSessionEvents session_events;
 
     LONG refs;
@@ -179,6 +215,7 @@ static int VolumeSet(audio_output_t *aout, float vol)
 {
     aout_sys_t *sys = aout->sys;
 
+    vol = vol * vol * vol; /* ISimpleAudioVolume is tapered linearly. */
     EnterCriticalSection(&sys->lock);
     sys->volume = vol;
     WakeConditionVariable(&sys->work);
@@ -269,7 +306,7 @@ vlc_AudioSessionEvents_OnSimpleVolumeChanged(IAudioSessionEvents *this,
 
     msg_Dbg(aout, "simple volume changed: %f, muting %sabled", vol,
             mute ? "en" : "dis");
-    aout_VolumeReport(aout, vol);
+    aout_VolumeReport(aout, cbrtf(vol));
     aout_MuteReport(aout, mute == TRUE);
     (void) ctx;
     return S_OK;
@@ -365,6 +402,203 @@ static const struct IAudioSessionEventsVtbl vlc_AudioSessionEvents =
 };
 
 /*** Audio devices ***/
+
+/** Gets the user-readable device name */
+static char *DeviceName(IMMDevice *dev)
+{
+    IPropertyStore *props;
+    char *name = NULL;
+    PROPVARIANT v;
+    HRESULT hr;
+
+    hr = IMMDevice_OpenPropertyStore(dev, STGM_READ, &props);
+    if (FAILED(hr))
+        return NULL;
+
+    PropVariantInit(&v);
+    hr = IPropertyStore_GetValue(props, &PKEY_Device_FriendlyName, &v);
+    if (SUCCEEDED(hr))
+    {
+        name = FromWide(v.pwszVal);
+        PropVariantClear(&v);
+    }
+    IPropertyStore_Release(props);
+    return name;
+}
+
+/** Checks that a device is an output device */
+static bool DeviceIsRender(IMMDevice *dev)
+{
+    void *pv;
+
+    if (FAILED(IMMDevice_QueryInterface(dev, &IID_IMMEndpoint, &pv)))
+        return false;
+
+    IMMEndpoint *ep = pv;
+    EDataFlow flow;
+
+    if (FAILED(IMMEndpoint_GetDataFlow(ep, &flow)))
+        flow = eCapture;
+
+    IMMEndpoint_Release(ep);
+    return flow == eRender;
+}
+
+static HRESULT DeviceUpdated(audio_output_t *aout, LPCWSTR wid)
+{
+    aout_sys_t *sys = aout->sys;
+    HRESULT hr;
+
+    IMMDevice *dev;
+    hr = IMMDeviceEnumerator_GetDevice(sys->it, wid, &dev);
+    if (FAILED(hr))
+        return hr;
+
+    if (!DeviceIsRender(dev))
+    {
+        IMMDevice_Release(dev);
+        return S_OK;
+    }
+
+    char *id = FromWide(wid);
+    if (unlikely(id == NULL))
+    {
+        IMMDevice_Release(dev);
+        return E_OUTOFMEMORY;
+    }
+
+    char *name = DeviceName(dev);
+    IMMDevice_Release(dev);
+
+    aout_HotplugReport(aout, id, (name != NULL) ? name : id);
+    free(name);
+    free(id);
+    return S_OK;
+}
+
+static inline aout_sys_t *vlc_MMNotificationClient_sys(IMMNotificationClient *this)
+{
+    return (void *)(((char *)this) - offsetof(aout_sys_t, device_events));
+}
+
+static STDMETHODIMP
+vlc_MMNotificationClient_QueryInterface(IMMNotificationClient *this,
+                                        REFIID riid, void **ppv)
+{
+    if (IsEqualIID(riid, &IID_IUnknown)
+     || IsEqualIID(riid, &IID_IMMNotificationClient))
+    {
+        *ppv = this;
+        IUnknown_AddRef(this);
+        return S_OK;
+    }
+    else
+    {
+       *ppv = NULL;
+        return E_NOINTERFACE;
+    }
+}
+
+static STDMETHODIMP_(ULONG)
+vlc_MMNotificationClient_AddRef(IMMNotificationClient *this)
+{
+    aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
+    return InterlockedIncrement(&sys->refs);
+}
+
+static STDMETHODIMP_(ULONG)
+vlc_MMNotificationClient_Release(IMMNotificationClient *this)
+{
+    aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
+    return InterlockedDecrement(&sys->refs);
+}
+
+static STDMETHODIMP
+vlc_MMNotificationClient_OnDefaultDeviceChange(IMMNotificationClient *this,
+                                               EDataFlow flow, ERole role,
+                                               LPCWSTR wid)
+{
+    aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
+    audio_output_t *aout = sys->aout;
+
+    if (flow != eRender)
+        return S_OK;
+    if (role != eConsole)
+        return S_OK;
+
+    msg_Dbg(aout, "default device changed: %ls", wid); /* TODO? migrate */
+    return S_OK;
+}
+
+static STDMETHODIMP
+vlc_MMNotificationClient_OnDeviceAdded(IMMNotificationClient *this,
+                                       LPCWSTR wid)
+{
+    aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
+    audio_output_t *aout = sys->aout;
+
+    msg_Dbg(aout, "device %ls added", wid);
+    return DeviceUpdated(aout, wid);
+}
+
+static STDMETHODIMP
+vlc_MMNotificationClient_OnDeviceRemoved(IMMNotificationClient *this,
+                                         LPCWSTR wid)
+{
+    aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
+    audio_output_t *aout = sys->aout;
+    char *id = FromWide(wid);
+
+    msg_Dbg(aout, "device %ls removed", wid);
+    if (unlikely(id == NULL))
+        return E_OUTOFMEMORY;
+
+    aout_HotplugReport(aout, id, NULL);
+    free(id);
+    return S_OK;
+}
+
+static STDMETHODIMP
+vlc_MMNotificationClient_OnDeviceStateChanged(IMMNotificationClient *this,
+                                              LPCWSTR wid, DWORD state)
+{
+    aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
+    audio_output_t *aout = sys->aout;
+
+    /* TODO: show device state / ignore missing devices */
+    msg_Dbg(aout, "device %ls state changed %08lx", wid, state);
+    return S_OK;
+}
+
+static STDMETHODIMP
+vlc_MMNotificationClient_OnDevicePropertyChanged(IMMNotificationClient *this,
+                                                 LPCWSTR wid,
+                                                 const PROPERTYKEY key)
+{
+    aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
+    audio_output_t *aout = sys->aout;
+
+    if (key.pid == PKEY_Device_FriendlyName.pid)
+    {
+        msg_Dbg(aout, "device %ls name changed", wid);
+        return DeviceUpdated(aout, wid);
+    }
+    return S_OK;
+}
+
+static const struct IMMNotificationClientVtbl vlc_MMNotificationClient =
+{
+    vlc_MMNotificationClient_QueryInterface,
+    vlc_MMNotificationClient_AddRef,
+    vlc_MMNotificationClient_Release,
+
+    vlc_MMNotificationClient_OnDeviceStateChanged,
+    vlc_MMNotificationClient_OnDeviceAdded,
+    vlc_MMNotificationClient_OnDeviceRemoved,
+    vlc_MMNotificationClient_OnDefaultDeviceChange,
+    vlc_MMNotificationClient_OnDevicePropertyChanged,
+};
+
 static int DevicesEnum(audio_output_t *aout, IMMDeviceEnumerator *it)
 {
     HRESULT hr;
@@ -391,7 +625,7 @@ static int DevicesEnum(audio_output_t *aout, IMMDeviceEnumerator *it)
     for (UINT i = 0; i < count; i++)
     {
         IMMDevice *dev;
-        char *id, *name = NULL;
+        char *id, *name;
 
         hr = IMMDeviceCollection_Item(devs, i, &dev);
         if (FAILED(hr))
@@ -408,20 +642,7 @@ static int DevicesEnum(audio_output_t *aout, IMMDeviceEnumerator *it)
         id = FromWide(devid);
         CoTaskMemFree(devid);
 
-        /* User-readable device name */
-        IPropertyStore *props;
-        hr = IMMDevice_OpenPropertyStore(dev, STGM_READ, &props);
-        if (SUCCEEDED(hr))
-        {
-            PROPVARIANT v;
-
-            PropVariantInit(&v);
-            hr = IPropertyStore_GetValue(props, &PKEY_Device_FriendlyName, &v);
-            if (SUCCEEDED(hr))
-                name = FromWide(v.pwszVal);
-            PropVariantClear(&v);
-            IPropertyStore_Release(props);
-        }
+        name = DeviceName(dev);
         IMMDevice_Release(dev);
 
         aout_HotplugReport(aout, id, (name != NULL) ? name : id);
@@ -456,7 +677,7 @@ static int DeviceSelect(audio_output_t *aout, const char *id)
         SleepConditionVariableCS(&sys->ready, &sys->lock, INFINITE);
     LeaveCriticalSection(&sys->lock);
 
-    if (sys->stream != NULL)
+    if (sys->stream != NULL && sys->dev != NULL)
         /* Request restart of stream with the new device */
         aout_RestartRequest(aout, AOUT_RESTART_OUTPUT);
     return (sys->dev != NULL) ? 0 : -1;
@@ -508,7 +729,8 @@ static HRESULT MMSession(audio_output_t *aout, IMMDeviceEnumerator *it)
         hr = AUDCLNT_E_DEVICE_INVALIDATED;
 
     while (hr == AUDCLNT_E_DEVICE_INVALIDATED)
-    {   /* Default device selected by policy */
+    {   /* Default device selected by policy and with stream routing.
+         * "Do not use eMultimedia" says MSDN. */
         msg_Dbg(aout, "using default device");
         hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(it, eRender,
                                                          eConsole, &sys->dev);
@@ -639,15 +861,25 @@ static void *MMThread(void *data)
 {
     audio_output_t *aout = data;
     aout_sys_t *sys = aout->sys;
+    IMMDeviceEnumerator *it = sys->it;
 
     EnterMTA();
+    IMMDeviceEnumerator_RegisterEndpointNotificationCallback(it,
+                                                          &sys->device_events);
+    DevicesEnum(aout, it);
+
     EnterCriticalSection(&sys->lock);
 
-    while (sys->it != NULL)
-        if (FAILED(MMSession(aout, sys->it)))
+    do
+        if (FAILED(MMSession(aout, it)))
             SleepConditionVariableCS(&sys->work, &sys->lock, INFINITE);
+    while (sys->it != NULL);
 
     LeaveCriticalSection(&sys->lock);
+
+    IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(it,
+                                                          &sys->device_events);
+    IMMDeviceEnumerator_Release(it);
     LeaveMTA();
     return NULL;
 }
@@ -674,15 +906,32 @@ static HRESULT ActivateDevice(void *opaque, REFIID iid, PROPVARIANT *actparms,
 }
 #endif /* VLC_WINSTORE_APP */
 
+static int aout_stream_Start(void *func, va_list ap)
+{
+    aout_stream_start_t start = func;
+    aout_stream_t *s = va_arg(ap, aout_stream_t *);
+    audio_sample_format_t *fmt = va_arg(ap, audio_sample_format_t *);
+    HRESULT *hr = va_arg(ap, HRESULT *);
+
+    *hr = start(s, fmt, &GUID_VLC_AUD_OUT);
+    if (*hr == AUDCLNT_E_DEVICE_INVALIDATED)
+        return VLC_ETIMEOUT;
+    return SUCCEEDED(*hr) ? VLC_SUCCESS : VLC_EGENERIC;
+}
+
+static void aout_stream_Stop(void *func, va_list ap)
+{
+    aout_stream_stop_t stop = func;
+    aout_stream_t *s = va_arg(ap, aout_stream_t *);
+
+    stop(s);
+}
+
 static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
 {
     aout_sys_t *sys = aout->sys;
-    HRESULT hr;
-
-    assert (sys->stream == NULL);
 #if !VLC_WINSTORE_APP
-    /* Open the default device if required (to deal with restarts) */
-    if (sys->dev == NULL && FAILED(DeviceSelect(aout, NULL)))
+    if (sys->dev == NULL)
         return -1;
 #endif
 
@@ -690,18 +939,38 @@ static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
     if (unlikely(s == NULL))
         return -1;
 
+#if !VLC_WINSTORE_APP
     s->owner.device = sys->dev;
+#else
+    s->owner.device = sys->client;
+#endif
     s->owner.activate = ActivateDevice;
 
     EnterMTA();
-    hr = aout_stream_Start(s, fmt, &GUID_VLC_AUD_OUT);
-    if (SUCCEEDED(hr))
-        sys->stream = s;
-    else
-        vlc_object_release(s);
+    for (;;)
+    {
+        HRESULT hr;
+
+        sys->module = vlc_module_load(s, "aout stream", NULL, false,
+                                      aout_stream_Start, s, fmt, &hr);
+        if (hr != AUDCLNT_E_DEVICE_INVALIDATED
+#if !VLC_WINSTORE_APP
+                || DeviceSelect(aout, NULL)
+#endif
+           )
+            break;
+    }
     LeaveMTA();
 
-    return vlc_FromHR(aout, hr);
+    if (sys->module == NULL)
+    {
+        vlc_object_release(s);
+        return -1;
+    }
+
+    assert (sys->stream == NULL);
+    sys->stream = s;
+    return 0;
 }
 
 static void Stop(audio_output_t *aout)
@@ -711,7 +980,7 @@ static void Stop(audio_output_t *aout)
     assert (sys->stream != NULL);
 
     EnterMTA();
-    aout_stream_Stop(sys->stream);
+    vlc_module_unload(sys->module, aout_stream_Stop, sys->stream);
     LeaveMTA();
 
     vlc_object_release(sys->stream);
@@ -722,10 +991,6 @@ static int Open(vlc_object_t *obj)
 {
     audio_output_t *aout = (audio_output_t *)obj;
 
-    if (!aout->b_force && var_InheritBool(aout, "spdif"))
-        /* Fallback to other plugin until pass-through is implemented */
-        return VLC_EGENERIC;
-
     aout_sys_t *sys = malloc(sizeof (*sys));
     if (unlikely(sys == NULL))
         return VLC_ENOMEM;
@@ -768,7 +1033,10 @@ static int Open(vlc_object_t *obj)
         goto error;
     }
 
-    DeviceSelect(aout, NULL);
+    EnterCriticalSection(&sys->lock);
+    while (sys->device != NULL)
+        SleepConditionVariableCS(&sys->ready, &sys->lock, INFINITE);
+    LeaveCriticalSection(&sys->lock);
     LeaveMTA(); /* Leave MTA after thread has entered MTA */
 #else
     sys->client = var_InheritAddress(aout, "mmdevice-audioclient");
@@ -784,9 +1052,6 @@ static int Open(vlc_object_t *obj)
     aout->volume_set = VolumeSet;
     aout->mute_set = MuteSet;
     aout->device_select = DeviceSelect;
-    EnterMTA();
-    DevicesEnum(aout, sys->it);
-    LeaveMTA();
     return VLC_SUCCESS;
 
 error:
@@ -803,18 +1068,12 @@ static void Close(vlc_object_t *obj)
     audio_output_t *aout = (audio_output_t *)obj;
     aout_sys_t *sys = aout->sys;
 #if !VLC_WINSTORE_APP
-    IMMDeviceEnumerator *it = sys->it;
-
-    EnterMTA(); /* Enter MTA before thread leaves MTA */
     EnterCriticalSection(&sys->lock);
     sys->device = default_device; /* break out of MMSession() loop */
     sys->it = NULL; /* break out of MMThread() loop */
     WakeConditionVariable(&sys->work);
     LeaveCriticalSection(&sys->lock);
 
-    IMMDeviceEnumerator_Release(it);
-    LeaveMTA();
-
     vlc_join(sys->thread, NULL);
     DeleteCriticalSection(&sys->lock);
 #else
@@ -826,7 +1085,7 @@ static void Close(vlc_object_t *obj)
 vlc_module_begin()
     set_shortname("MMDevice")
     set_description(N_("Windows Multimedia Device output"))
-    set_capability("audio output", /*150*/0)
+    set_capability("audio output", 150)
 #if VLC_WINSTORE_APP
     /* Pointer to the activated AudioClient* */
     add_integer("mmdevice-audioclient", 0x0, NULL, NULL, true);