]> git.sesse.net Git - vlc/blobdiff - modules/audio_output/mmdevice.c
skins2: improve fullscreen behaviour
[vlc] / modules / audio_output / mmdevice.c
index 8b8e9e2275b05e4728d094256cb3aaee0c8f38c8..912247caa4e6516a6230e4f2e18f5ead0e460e1f 100644 (file)
@@ -80,6 +80,7 @@ struct aout_sys_t
     IMMDevice *dev; /**< Selected output device, NULL if none */
     IAudioSessionManager *manager; /**< Session for the output device */
     struct IAudioSessionEvents session_events;
+    ISimpleAudioVolume *volume; /**< Volume setter */
 
     LONG refs;
     HANDLE device_changed; /**< Event to reset thread */
@@ -103,7 +104,7 @@ static int vlc_FromHR(audio_output_t *aout, HRESULT hr)
 {
     /* Restart on unplug */
     if (unlikely(hr == AUDCLNT_E_DEVICE_INVALIDATED))
-        var_TriggerCallback(aout, "audio-device");
+        aout_RestartRequest(aout, AOUT_RESTART_OUTPUT);
     return SUCCEEDED(hr) ? 0 : -1;
 }
 
@@ -165,150 +166,40 @@ static void Flush(audio_output_t *aout, bool wait)
 
 }
 
-static ISimpleAudioVolume *GetSimpleVolume(audio_output_t *aout)
-{
-    aout_sys_t *sys = aout->sys;
-    ISimpleAudioVolume *volume;
-    HRESULT hr;
-
-    if (sys->manager == NULL)
-        return NULL;
-
-    if (TryEnterMTA(aout))
-        return NULL;
-    hr = IAudioSessionManager_GetSimpleAudioVolume(sys->manager,
-                                                   &GUID_VLC_AUD_OUT,
-                                                   FALSE, &volume);
-    if (FAILED(hr))
-    {
-        LeaveMTA();
-        msg_Err(aout, "cannot get simple volume (error 0x%lx)", hr);
-        assert(volume == NULL);
-    }
-    return volume;
-}
-
-static void PutSimpleVolume(ISimpleAudioVolume *volume)
-{
-    ISimpleAudioVolume_Release(volume);
-    LeaveMTA();
-}
-
 static int VolumeSet(audio_output_t *aout, float vol)
 {
-    ISimpleAudioVolume *volume = GetSimpleVolume(aout);
+    ISimpleAudioVolume *volume = aout->sys->volume;
     if (volume == NULL)
         return -1;
 
+    if (TryEnterMTA(aout))
+        return -1;
+
     HRESULT hr = ISimpleAudioVolume_SetMasterVolume(volume, vol, NULL);
     if (FAILED(hr))
         msg_Err(aout, "cannot set volume (error 0x%lx)", hr);
-    PutSimpleVolume(volume);
+    LeaveMTA();
 
     return FAILED(hr) ? -1 : 0;
 }
 
 static int MuteSet(audio_output_t *aout, bool mute)
 {
-    ISimpleAudioVolume *volume = GetSimpleVolume(aout);
+    ISimpleAudioVolume *volume = aout->sys->volume;
     if (volume == NULL)
         return -1;
 
+    if (TryEnterMTA(aout))
+        return -1;
+
     HRESULT hr = ISimpleAudioVolume_SetMute(volume, mute ? TRUE : FALSE, NULL);
     if (FAILED(hr))
         msg_Err(aout, "cannot set volume (error 0x%lx)", hr);
-    PutSimpleVolume(volume);
+    LeaveMTA();
 
     return FAILED(hr) ? -1 : 0;
 }
 
-/*** Audio devices ***/
-static int DeviceChanged(vlc_object_t *obj, const char *varname,
-                         vlc_value_t prev, vlc_value_t cur, void *data)
-{
-    /* FIXME: This does not work. sys->dev, ->manager and ->stream must be
-     * recreated. Those pointers are protected by the aout lock, which
-     * serializes accesses to the audio_output_t. Unfortunately,
-     * aout lock cannot be taken from a variable callback.
-     * Solution: add device_change callback to audio_output_t. */
-    aout_ChannelsRestart(obj, varname, prev, cur, data);
-    return VLC_SUCCESS;
-}
-
-static void GetDevices(vlc_object_t *obj, IMMDeviceEnumerator *it)
-{
-    HRESULT hr;
-    vlc_value_t val, text;
-
-    var_Create (obj, "audio-device", VLC_VAR_STRING | VLC_VAR_HASCHOICE);
-    text.psz_string = _("Audio Device");
-    var_Change (obj, "audio-device", VLC_VAR_SETTEXT, &text, NULL);
-
-    /* TODO: implement IMMNotificationClient for hotplug devices */
-    IMMDeviceCollection *devs;
-    hr = IMMDeviceEnumerator_EnumAudioEndpoints(it, eRender,
-                                                DEVICE_STATE_ACTIVE, &devs);
-    if (FAILED(hr))
-    {
-        msg_Warn (obj, "cannot enumerate audio endpoints (error 0x%lx)", hr);
-        return;
-    }
-
-    UINT n;
-    hr = IMMDeviceCollection_GetCount(devs, &n);
-    if (FAILED(hr))
-    {
-        msg_Warn (obj, "cannot count audio endpoints (error 0x%lx)", hr);
-        n = 0;
-    }
-    else
-        msg_Dbg(obj, "Available Windows Audio devices:");
-
-    while (n > 0)
-    {
-        IMMDevice *dev;
-
-        hr = IMMDeviceCollection_Item(devs, --n, &dev);
-        if (FAILED(hr))
-            continue;
-
-        /* Unique device ID */
-        LPWSTR devid;
-        hr = IMMDevice_GetId(dev, &devid);
-        if (FAILED(hr))
-        {
-            IMMDevice_Release(dev);
-            continue;
-        }
-        val.psz_string = FromWide(devid);
-        CoTaskMemFree(devid);
-        text.psz_string = val.psz_string;
-
-        /* 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))
-                text.psz_string = FromWide(v.pwszVal);
-            PropVariantClear(&v);
-            IPropertyStore_Release(props);
-        }
-        IMMDevice_Release(dev);
-
-        msg_Dbg(obj, "%s (%s)", val.psz_string, text.psz_string);
-        var_Change(obj, "audio-device", VLC_VAR_ADDCHOICE, &val, &text);
-        if (likely(text.psz_string != val.psz_string))
-            free(text.psz_string);
-        free(val.psz_string);
-    }
-    IMMDeviceCollection_Release(devs);
-}
-
 /*** Audio session events ***/
 static inline aout_sys_t *vlc_AudioSessionEvents_sys(IAudioSessionEvents *this)
 {
@@ -457,7 +348,7 @@ vlc_AudioSessionEvents_OnSessionDisconnected(IAudioSessionEvents *this,
             msg_Warn(aout, "session disconnected: unknown reason %d", reason);
             return S_OK;
     }
-    var_TriggerCallback(aout, "audio-device");
+    /* NOTE: audio decoder thread should get invalidated device and restart */
     return S_OK;
 }
 
@@ -498,6 +389,12 @@ static void MMSession(audio_output_t *aout, aout_sys_t *sys)
     /* Register session control */
     if (sys->manager != NULL)
     {
+        hr = IAudioSessionManager_GetSimpleAudioVolume(sys->manager,
+                                                       &GUID_VLC_AUD_OUT,
+                                                       FALSE, &sys->volume);
+        if (FAILED(hr))
+            msg_Err(aout, "cannot get simple volume (error 0x%lx)", hr);
+
         hr = IAudioSessionManager_GetAudioSessionControl(sys->manager,
                                                          &GUID_VLC_AUD_OUT, 0,
                                                          &control);
@@ -505,7 +402,10 @@ static void MMSession(audio_output_t *aout, aout_sys_t *sys)
             msg_Err(aout, "cannot get session control (error 0x%lx)", hr);
     }
     else
+    {
+        sys->volume = NULL;
         control = NULL;
+    }
 
     if (control != NULL)
     {
@@ -517,7 +417,26 @@ static void MMSession(audio_output_t *aout, aout_sys_t *sys)
                                                          &sys->session_events);
     }
 
+    if (sys->volume != NULL)
+    {   /* Get current values (_after_ changes notification registration) */
+        BOOL mute;
+        float level;
+
+        hr = ISimpleAudioVolume_GetMute(sys->volume, &mute);
+        if (FAILED(hr))
+            msg_Err(aout, "cannot get mute (error 0x%lx)", hr);
+        else
+            aout_MuteReport(aout, mute != FALSE);
+
+        hr = ISimpleAudioVolume_GetMasterVolume(sys->volume, &level);
+        if (FAILED(hr))
+            msg_Err(aout, "cannot get mute (error 0x%lx)", hr);
+        else
+            aout_VolumeReport(aout, level);
+    }
+
     SetEvent(sys->device_ready);
+    /* Wait until device change or exit */
     WaitForSingleObject(sys->device_changed, INFINITE);
 
     /* Deregister session control */
@@ -527,6 +446,9 @@ static void MMSession(audio_output_t *aout, aout_sys_t *sys)
                                                          &sys->session_events);
         IAudioSessionControl_Release(control);
     }
+
+    if (sys->volume != NULL)
+        ISimpleAudioVolume_Release(sys->volume);
 }
 
 /** MMDevice audio output thread.
@@ -550,6 +472,76 @@ static void *MMThread(void *data)
     return NULL;
 }
 
+/*** Audio devices ***/
+static int DevicesEnum(audio_output_t *aout)
+{
+    aout_sys_t *sys = aout->sys;
+    HRESULT hr;
+    IMMDeviceCollection *devs;
+
+    hr = IMMDeviceEnumerator_EnumAudioEndpoints(sys->it, eRender,
+                                                DEVICE_STATE_ACTIVE, &devs);
+    if (FAILED(hr))
+    {
+        msg_Warn(aout, "cannot enumerate audio endpoints (error 0x%lx)", hr);
+        return -1;
+    }
+
+    UINT count;
+    hr = IMMDeviceCollection_GetCount(devs, &count);
+    if (FAILED(hr))
+    {
+        msg_Warn(aout, "cannot count audio endpoints (error 0x%lx)", hr);
+        count = 0;
+    }
+
+    unsigned n = 0;
+
+    for (UINT i = 0; i < count; i++)
+    {
+        IMMDevice *dev;
+        char *id, *name = NULL;
+
+        hr = IMMDeviceCollection_Item(devs, i, &dev);
+        if (FAILED(hr))
+            continue;
+
+        /* Unique device ID */
+        LPWSTR devid;
+        hr = IMMDevice_GetId(dev, &devid);
+        if (FAILED(hr))
+        {
+            IMMDevice_Release(dev);
+            continue;
+        }
+        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);
+        }
+        IMMDevice_Release(dev);
+
+        aout_HotplugReport(aout, id, (name != NULL) ? name : id);
+        free(name);
+        free(id);
+        n++;
+    }
+    IMMDeviceCollection_Release(devs);
+    return n;
+}
+
 /**
  * Opens the selected audio output device.
  */
@@ -579,21 +571,36 @@ static HRESULT OpenDevice(audio_output_t *aout, const char *devid)
         hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(sys->it, eRender,
                                                          eConsole, &sys->dev);
     }
+    assert(sys->manager == NULL);
+    if (FAILED(hr))
+    {
+        msg_Err(aout, "cannot get device (error 0x%lx)", hr);
+        goto out;
+    }
 
     /* Create session manager (for controls even w/o active audio client) */
+    void *pv;
+    hr = IMMDevice_Activate(sys->dev, &IID_IAudioSessionManager,
+                            CLSCTX_ALL, NULL, &pv);
     if (FAILED(hr))
-        msg_Err(aout, "cannot get device (error 0x%lx)", hr);
+        msg_Err(aout, "cannot activate session manager (error 0x%lx)", hr);
     else
+        sys->manager = pv;
+
+    /* Report actual device */
+    LPWSTR wdevid;
+    hr = IMMDevice_GetId(sys->dev, &wdevid);
+    if (SUCCEEDED(hr))
     {
-        void *pv;
-        hr = IMMDevice_Activate(sys->dev, &IID_IAudioSessionManager,
-                                CLSCTX_ALL, NULL, &pv);
-        if (FAILED(hr))
-            msg_Err(aout, "cannot activate session manager (error 0x%lx)", hr);
-        else
-            sys->manager = pv;
+        char *id = FromWide(wdevid);
+        CoTaskMemFree(wdevid);
+        if (likely(id != NULL))
+        {
+            aout_DeviceReport(aout, id);
+            free(id);
+        }
     }
-
+out:
     SetEvent(sys->device_changed);
     WaitForSingleObject(sys->device_ready, INFINITE);
     return hr;
@@ -618,21 +625,59 @@ static void CloseDevice(audio_output_t *aout)
     sys->dev = NULL;
 }
 
+static int DeviceSelect(audio_output_t *aout, const char *id)
+{
+    aout_sys_t *sys = aout->sys;
+    HRESULT hr;
+
+    if (TryEnterMTA(aout))
+        return -1;
+
+    if (sys->dev != NULL)
+        CloseDevice(aout);
+
+    hr = OpenDevice(aout, id);
+    while (hr == AUDCLNT_E_DEVICE_INVALIDATED)
+        hr = OpenDevice(aout, NULL); /* Fallback to default device */
+    LeaveMTA();
+
+    if (sys->stream != NULL)
+        /* Request restart of stream with the new device */
+        aout_RestartRequest(aout, AOUT_RESTART_OUTPUT);
+    return FAILED(hr) ? -1 : 0;
+}
+
+/**
+ * Callback for aout_stream_t to create a stream on the device.
+ * This can instantiate an IAudioClient or IDirectSound(8) object.
+ */
+static HRESULT ActivateDevice(void *opaque, REFIID iid, PROPVARIANT *actparms,
+                              void **restrict pv)
+{
+    IMMDevice *dev = opaque;
+
+    return IMMDevice_Activate(dev, iid, CLSCTX_ALL, actparms, pv);
+}
+
 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 (sys->dev == NULL)
+    /* Open the default device if required (to deal with restarts) */
+    if (sys->dev == NULL && FAILED(DeviceSelect(aout, NULL)))
         return -1;
 
     aout_stream_t *s = vlc_object_create(aout, sizeof (*s));
     if (unlikely(s == NULL))
         return -1;
 
+    s->owner.device = sys->dev;
+    s->owner.activate = ActivateDevice;
+
     EnterMTA();
-    hr = aout_stream_Start(s, fmt, sys->dev, &GUID_VLC_AUD_OUT);
+    hr = aout_stream_Start(s, fmt, &GUID_VLC_AUD_OUT);
     if (SUCCEEDED(hr))
         sys->stream = s;
     else
@@ -697,16 +742,12 @@ static int Open(vlc_object_t *obj)
         goto error;
     }
     sys->it = pv;
-    GetDevices(obj, sys->it);
 
     if (vlc_clone(&sys->thread, MMThread, aout, VLC_THREAD_PRIORITY_LOW))
         goto error;
     WaitForSingleObject(sys->device_ready, INFINITE);
 
-    /* Get a device to start with */
-    do
-        hr = OpenDevice(aout, NULL);
-    while (hr == AUDCLNT_E_DEVICE_INVALIDATED);
+    DeviceSelect(aout, NULL); /* Get a device to start with */
     LeaveMTA(); /* leave MTA after thread has entered MTA */
 
     aout->start = Start;
@@ -717,7 +758,8 @@ static int Open(vlc_object_t *obj)
     aout->flush = Flush;
     aout->volume_set = VolumeSet;
     aout->mute_set = MuteSet;
-    var_AddCallback (aout, "audio-device", DeviceChanged, NULL);
+    aout->device_select = DeviceSelect;
+    DevicesEnum(aout);
     return VLC_SUCCESS;
 
 error:
@@ -739,9 +781,6 @@ static void Close(vlc_object_t *obj)
     audio_output_t *aout = (audio_output_t *)obj;
     aout_sys_t *sys = aout->sys;
 
-    var_DelCallback (aout, "audio-device", DeviceChanged, NULL);
-    var_Destroy (aout, "audio-device");
-
     EnterMTA(); /* enter MTA before thread leaves MTA */
     if (sys->dev != NULL)
         CloseDevice(aout);
@@ -761,7 +800,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)
+    set_capability("audio output", /*150*/0)
     set_category(CAT_AUDIO)
     set_subcategory(SUBCAT_AUDIO_AOUT)
     add_shortcut("wasapi")