[vlc-devel] [PATCH] mmdevice: use IAudioStreamVolume to change volume

Thomas Guillem thomas at gllm.fr
Fri Apr 6 16:39:00 CEST 2018


Since mmdevice doesn't respect the following ISimpleAudioVolume rule, cf [1]:

> As an option, an application window can display a volume slider. The
> application slider should reflect the state of the corresponding Sndvol
> slider at all times.

> To support this behavior, WASAPI implements the ISimpleAudioVolume interface.
> When the user moves the application slider, the application calls the
> ISimpleAudioVolume::SetMasterVolume method to adjust the session volume level
> accordingly.

We should then use the IAudioStreamVolume API, cf. [2]:

> Typical audio applications do not modify the volume levels of sessions.
> Instead, they rely on users to set these volume levels through the Sndvol
> program. "

With this API, volume changes are only made for the current stream (the
IAudioClient * that is Started/Stopped from wasapi) and is not saved across
several sessions. This means that mmdevice should know use the config to save
the last volume. This module doesn't need to handle volume notifications since
the stream volume can only be changed by VLC.

Users will still be able to change VLC volume via the Sndvol slider (the
effective volume will be the product of the Sndvol volume and the VLC's stream
volume).

This patch also allow users to save the volume after 100% (but maxed to 125%).

[1] https://msdn.microsoft.com/fr-fr/library/windows/desktop/dd316769(v=vs.85).aspx
[2] https://msdn.microsoft.com/en-us/library/windows/desktop/dd371023(v=vs.85).aspx
---

2nd version: change volume directly from the volume/mute callback thread.

 modules/audio_output/mmdevice.c | 337 ++++++++--------------------------------
 modules/audio_output/mmdevice.h |   8 +
 modules/audio_output/wasapi.c   |   6 +
 3 files changed, 77 insertions(+), 274 deletions(-)

diff --git a/modules/audio_output/mmdevice.c b/modules/audio_output/mmdevice.c
index c2f190f01f..ccec6bab8a 100644
--- a/modules/audio_output/mmdevice.c
+++ b/modules/audio_output/mmdevice.c
@@ -83,16 +83,14 @@ struct aout_sys_t
     IMMDevice *dev; /**< Selected output device, NULL if none */
 
     struct IMMNotificationClient device_events;
-    struct IAudioSessionEvents session_events;
     struct IAudioVolumeDuckNotification duck;
 
     LONG refs;
     unsigned ducks;
-    float gain; /**< Current software gain volume */
 
     wchar_t *requested_device; /**< Requested device identifier, NULL if none */
-    float requested_volume; /**< Requested volume, negative if none */
-    signed char requested_mute; /**< Requested mute, negative if none */
+    float volume; /**< volume */
+    bool mute; /**< mute */
     wchar_t *acquired_device; /**< Acquired device identifier, NULL if none */
     bool request_device_restart;
     CRITICAL_SECTION lock;
@@ -173,12 +171,14 @@ static void Flush(audio_output_t *aout, bool wait)
     vlc_FromHR(aout, hr);
 }
 
-static int VolumeSetLocked(audio_output_t *aout, float vol)
+static void VolumeUpdateInMTA(audio_output_t *aout)
 {
     aout_sys_t *sys = aout->sys;
-    float gain = 1.f;
 
-    vol = vol * vol * vol; /* ISimpleAudioVolume is tapered linearly. */
+    assert(sys->stream != NULL);
+
+    float gain = 1.f;
+    float vol = sys->mute ? 0.0f : sys->volume * sys->volume * sys->volume;
 
     if (vol > 1.f)
     {
@@ -186,202 +186,65 @@ static int VolumeSetLocked(audio_output_t *aout, float vol)
         vol = 1.f;
     }
 
-    aout_GainRequest(aout, gain);
+    void *pv;
+    HRESULT hr = aout_stream_GetService(sys->stream, &IID_IAudioStreamVolume, &pv);
+    if (SUCCEEDED(hr))
+    {
+        IAudioStreamVolume *stream_volume = pv;
 
-    sys->gain = gain;
-    sys->requested_volume = vol;
-    return 0;
-}
+        UINT32 chan_count;
+        hr = IAudioStreamVolume_GetChannelCount(stream_volume, &chan_count);
+        if (SUCCEEDED(hr))
+        {
+            assert(chan_count <= 64);
+            float chan_volumes[chan_count];
+            for (UINT32 i = 0; i < chan_count; ++i)
+                chan_volumes[i] = vol;
 
-static int VolumeSet(audio_output_t *aout, float vol)
-{
-    aout_sys_t *sys = aout->sys;
+            IAudioStreamVolume_SetAllVolumes(stream_volume, chan_count,
+                                             chan_volumes);
+        }
+        IAudioStreamVolume_Release(stream_volume);
+    }
 
-    EnterCriticalSection(&sys->lock);
-    int ret = VolumeSetLocked(aout, vol);
-    WakeConditionVariable(&sys->work);
-    LeaveCriticalSection(&sys->lock);
-    return ret;
+    aout_GainRequest(aout, gain);
 }
 
-static int MuteSet(audio_output_t *aout, bool mute)
+static void VolumeUpdate(audio_output_t *aout)
 {
     aout_sys_t *sys = aout->sys;
 
-    EnterCriticalSection(&sys->lock);
-    sys->requested_mute = mute;
-    WakeConditionVariable(&sys->work);
-    LeaveCriticalSection(&sys->lock);
-    return 0;
-}
-
-/*** Audio session events ***/
-static STDMETHODIMP
-vlc_AudioSessionEvents_QueryInterface(IAudioSessionEvents *this, REFIID riid,
-                                      void **ppv)
-{
-    if (IsEqualIID(riid, &IID_IUnknown)
-     || IsEqualIID(riid, &IID_IAudioSessionEvents))
+    if (sys->stream)
     {
-        *ppv = this;
-        IUnknown_AddRef(this);
-        return S_OK;
+        bool in_mta = SUCCEEDED(CoInitializeEx(NULL, COINIT_MULTITHREADED));
+        VolumeUpdateInMTA(aout);
+        if (in_mta)
+            CoUninitialize();
     }
-    else
-    {
-       *ppv = NULL;
-        return E_NOINTERFACE;
-    }
-}
-
-static STDMETHODIMP_(ULONG)
-vlc_AudioSessionEvents_AddRef(IAudioSessionEvents *this)
-{
-    aout_sys_t *sys = container_of(this, aout_sys_t, session_events);
-    return InterlockedIncrement(&sys->refs);
-}
-
-static STDMETHODIMP_(ULONG)
-vlc_AudioSessionEvents_Release(IAudioSessionEvents *this)
-{
-    aout_sys_t *sys = container_of(this, aout_sys_t, session_events);
-    return InterlockedDecrement(&sys->refs);
-}
-
-static STDMETHODIMP
-vlc_AudioSessionEvents_OnDisplayNameChanged(IAudioSessionEvents *this,
-                                            LPCWSTR wname, LPCGUID ctx)
-{
-    aout_sys_t *sys = container_of(this, aout_sys_t, session_events);
-    audio_output_t *aout = sys->aout;
-
-    msg_Dbg(aout, "display name changed: %ls", wname);
-    (void) ctx;
-    return S_OK;
-}
-
-static STDMETHODIMP
-vlc_AudioSessionEvents_OnIconPathChanged(IAudioSessionEvents *this,
-                                         LPCWSTR wpath, LPCGUID ctx)
-{
-    aout_sys_t *sys = container_of(this, aout_sys_t, session_events);
-    audio_output_t *aout = sys->aout;
-
-    msg_Dbg(aout, "icon path changed: %ls", wpath);
-    (void) ctx;
-    return S_OK;
 }
 
-static STDMETHODIMP
-vlc_AudioSessionEvents_OnSimpleVolumeChanged(IAudioSessionEvents *this,
-                                             float vol, BOOL mute,
-                                             LPCGUID ctx)
-{
-    aout_sys_t *sys = container_of(this, aout_sys_t, session_events);
-    audio_output_t *aout = sys->aout;
-
-    msg_Dbg(aout, "simple volume changed: %f, muting %sabled", vol,
-            mute ? "en" : "dis");
-    EnterCriticalSection(&sys->lock);
-    WakeConditionVariable(&sys->work); /* implicit state: vol & mute */
-    LeaveCriticalSection(&sys->lock);
-    (void) ctx;
-    return S_OK;
-}
-
-static STDMETHODIMP
-vlc_AudioSessionEvents_OnChannelVolumeChanged(IAudioSessionEvents *this,
-                                              DWORD count, float *vols,
-                                              DWORD changed, LPCGUID ctx)
+static int VolumeSet(audio_output_t *aout, float vol)
 {
-    aout_sys_t *sys = container_of(this, aout_sys_t, session_events);
-    audio_output_t *aout = sys->aout;
-
-    if (changed != (DWORD)-1)
-        msg_Dbg(aout, "channel volume %lu of %lu changed: %f", changed, count,
-                vols[changed]);
-    else
-        msg_Dbg(aout, "%lu channels volume changed", count);
-
-    (void) ctx;
-    return S_OK;
-}
-
-static STDMETHODIMP
-vlc_AudioSessionEvents_OnGroupingParamChanged(IAudioSessionEvents *this,
-                                              LPCGUID param, LPCGUID ctx)
+    aout_sys_t *sys = aout->sys;
 
-{
-    aout_sys_t *sys = container_of(this, aout_sys_t, session_events);
-    audio_output_t *aout = sys->aout;
+    sys->volume = vol;
+    VolumeUpdate(aout);
 
-    msg_Dbg(aout, "grouping parameter changed");
-    (void) param;
-    (void) ctx;
-    return S_OK;
+    aout_VolumeReport(aout, sys->volume);
+    return 0;
 }
 
-static STDMETHODIMP
-vlc_AudioSessionEvents_OnStateChanged(IAudioSessionEvents *this,
-                                      AudioSessionState state)
+static int MuteSet(audio_output_t *aout, bool mute)
 {
-    aout_sys_t *sys = container_of(this, aout_sys_t, session_events);
-    audio_output_t *aout = sys->aout;
-
-    msg_Dbg(aout, "state changed: %d", state);
-    return S_OK;
-}
+    aout_sys_t *sys = aout->sys;
 
-static STDMETHODIMP
-vlc_AudioSessionEvents_OnSessionDisconnected(IAudioSessionEvents *this,
-                                           AudioSessionDisconnectReason reason)
-{
-    aout_sys_t *sys = container_of(this, aout_sys_t, session_events);
-    audio_output_t *aout = sys->aout;
+    sys->mute = mute;
+    VolumeUpdate(aout);
 
-    switch (reason)
-    {
-        case DisconnectReasonDeviceRemoval:
-            msg_Warn(aout, "session disconnected: %s", "device removed");
-            break;
-        case DisconnectReasonServerShutdown:
-            msg_Err(aout, "session disconnected: %s", "service stopped");
-            return S_OK;
-        case DisconnectReasonFormatChanged:
-            msg_Warn(aout, "session disconnected: %s", "format changed");
-            break;
-        case DisconnectReasonSessionLogoff:
-            msg_Err(aout, "session disconnected: %s", "user logged off");
-            return S_OK;
-        case DisconnectReasonSessionDisconnected:
-            msg_Err(aout, "session disconnected: %s", "session disconnected");
-            return S_OK;
-        case DisconnectReasonExclusiveModeOverride:
-            msg_Err(aout, "session disconnected: %s", "stream overriden");
-            return S_OK;
-        default:
-            msg_Warn(aout, "session disconnected: unknown reason %d", reason);
-            return S_OK;
-    }
-    /* NOTE: audio decoder thread should get invalidated device and restart */
-    return S_OK;
+    aout_MuteReport(aout, mute);
+    return 0;
 }
 
-static const struct IAudioSessionEventsVtbl vlc_AudioSessionEvents =
-{
-    vlc_AudioSessionEvents_QueryInterface,
-    vlc_AudioSessionEvents_AddRef,
-    vlc_AudioSessionEvents_Release,
-
-    vlc_AudioSessionEvents_OnDisplayNameChanged,
-    vlc_AudioSessionEvents_OnIconPathChanged,
-    vlc_AudioSessionEvents_OnSimpleVolumeChanged,
-    vlc_AudioSessionEvents_OnChannelVolumeChanged,
-    vlc_AudioSessionEvents_OnGroupingParamChanged,
-    vlc_AudioSessionEvents_OnStateChanged,
-    vlc_AudioSessionEvents_OnSessionDisconnected,
-};
-
 static STDMETHODIMP
 vlc_AudioVolumeDuckNotification_QueryInterface(
     IAudioVolumeDuckNotification *this, REFIID riid, void **ppv)
@@ -811,8 +674,6 @@ static HRESULT MMSession(audio_output_t *aout, IMMDeviceEnumerator *it)
     aout_sys_t *sys = aout->sys;
     IAudioSessionManager *manager;
     IAudioSessionControl *control;
-    ISimpleAudioVolume *volume;
-    IAudioEndpointVolume *endpoint;
     void *pv;
     HRESULT hr;
 
@@ -884,28 +745,18 @@ static HRESULT MMSession(audio_output_t *aout, IMMDeviceEnumerator *it)
     manager = pv;
     if (SUCCEEDED(hr))
     {
-        LPCGUID guid = var_GetBool(aout, "volume-save") ? &GUID_VLC_AUD_OUT : NULL;
-
         /* Register session control */
-        hr = IAudioSessionManager_GetAudioSessionControl(manager, guid, 0,
+        hr = IAudioSessionManager_GetAudioSessionControl(manager, NULL, 0,
                                                          &control);
         if (SUCCEEDED(hr))
         {
             wchar_t *ua = var_InheritWide(aout, "user-agent");
             IAudioSessionControl_SetDisplayName(control, ua, NULL);
             free(ua);
-
-            IAudioSessionControl_RegisterAudioSessionNotification(control,
-                                                         &sys->session_events);
         }
         else
             msg_Err(aout, "cannot get session control (error 0x%lx)", hr);
 
-        hr = IAudioSessionManager_GetSimpleAudioVolume(manager, guid, FALSE,
-                                                       &volume);
-        if (FAILED(hr))
-            msg_Err(aout, "cannot get simple volume (error 0x%lx)", hr);
-
         /* Try to get version 2 (Windows 7) of the manager & control */
         wchar_t *siid = NULL;
 
@@ -943,74 +794,16 @@ static HRESULT MMSession(audio_output_t *aout, IMMDeviceEnumerator *it)
     {
         msg_Err(aout, "cannot activate session manager (error 0x%lx)", hr);
         control = NULL;
-        volume = NULL;
-    }
-
-    hr = IMMDevice_Activate(sys->dev, &IID_IAudioEndpointVolume,
-                            CLSCTX_ALL, NULL, &pv);
-    endpoint = pv;
-    if (SUCCEEDED(hr))
-    {
-        float min, max, inc;
-
-        hr = IAudioEndpointVolume_GetVolumeRange(endpoint, &min, &max, &inc);
-        if (SUCCEEDED(hr))
-            msg_Dbg(aout, "volume from %+f dB to %+f dB with %f dB increments",
-                    min, max, inc);
-        else
-            msg_Err(aout, "cannot get volume range (error 0x%lx)", hr);
     }
-    else
-        msg_Err(aout, "cannot activate endpoint volume (error %lx)", hr);
 
     /* Main loop (adjust volume as long as device is unchanged) */
+
     while (sys->requested_device == NULL)
     {
-        if (volume != NULL)
-        {
-            float level;
-
-            level = sys->requested_volume;
-            if (level >= 0.f)
-            {
-                hr = ISimpleAudioVolume_SetMasterVolume(volume, level, NULL);
-                if (FAILED(hr))
-                    msg_Err(aout, "cannot set master volume (error 0x%lx)",
-                            hr);
-            }
-            sys->requested_volume = -1.f;
-
-            hr = ISimpleAudioVolume_GetMasterVolume(volume, &level);
-            if (SUCCEEDED(hr))
-                aout_VolumeReport(aout, cbrtf(level * sys->gain));
-            else
-                msg_Err(aout, "cannot get master volume (error 0x%lx)", hr);
-
-            BOOL mute;
-
-            hr = ISimpleAudioVolume_GetMute(volume, &mute);
-            if (SUCCEEDED(hr))
-                aout_MuteReport(aout, mute != FALSE);
-            else
-                msg_Err(aout, "cannot get mute (error 0x%lx)", hr);
-
-            if (sys->requested_mute >= 0)
-            {
-                mute = sys->requested_mute ? TRUE : FALSE;
-
-                hr = ISimpleAudioVolume_SetMute(volume, mute, NULL);
-                if (FAILED(hr))
-                    msg_Err(aout, "cannot set mute (error 0x%lx)", hr);
-            }
-            sys->requested_mute = -1;
-        }
-
         SleepConditionVariableCS(&sys->work, &sys->lock, INFINITE);
     }
-    LeaveCriticalSection(&sys->lock);
 
-    if (endpoint != NULL)
-        IAudioEndpointVolume_Release(endpoint);
+    LeaveCriticalSection(&sys->lock);
 
     if (manager != NULL)
     {   /* Deregister callbacks *without* the lock */
@@ -1024,16 +817,8 @@ static HRESULT MMSession(audio_output_t *aout, IMMDeviceEnumerator *it)
             IAudioSessionManager2_Release(m2);
         }
 
-        if (volume != NULL)
-            ISimpleAudioVolume_Release(volume);
-
         if (control != NULL)
-        {
-            IAudioSessionControl_UnregisterAudioSessionNotification(control,
-                                                         &sys->session_events);
             IAudioSessionControl_Release(control);
-        }
-
         IAudioSessionManager_Release(manager);
     }
 
@@ -1097,7 +882,7 @@ static int aout_stream_Start(void *func, va_list ap)
     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);
+    *hr = start(s, fmt, NULL);
     if (*hr == AUDCLNT_E_DEVICE_INVALIDATED)
         return VLC_ETIMEOUT;
     return SUCCEEDED(*hr) ? VLC_SUCCESS : VLC_EGENERIC;
@@ -1209,18 +994,23 @@ static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
     }
 
     LeaveCriticalSection(&sys->lock);
-    LeaveMTA();
 
     if (sys->module == NULL)
     {
+        LeaveMTA();
         vlc_object_release(s);
         return -1;
     }
+    else
+    {
+        assert (sys->stream == NULL);
+        sys->stream = s;
 
-    assert (sys->stream == NULL);
-    sys->stream = s;
-    aout_GainRequest(aout, sys->gain);
-    return 0;
+        VolumeUpdateInMTA(aout);
+
+        LeaveMTA();
+        return 0;
+    }
 }
 
 static void Stop(audio_output_t *aout)
@@ -1230,6 +1020,7 @@ static void Stop(audio_output_t *aout)
     assert(sys->stream != NULL);
 
     EnterMTA();
+
     vlc_module_unload(sys->stream, sys->module, aout_stream_Stop, sys->stream);
     LeaveMTA();
 
@@ -1251,19 +1042,15 @@ static int Open(vlc_object_t *obj)
     sys->it = NULL;
     sys->dev = NULL;
     sys->device_events.lpVtbl = &vlc_MMNotificationClient;
-    sys->session_events.lpVtbl = &vlc_AudioSessionEvents;
     sys->duck.lpVtbl = &vlc_AudioVolumeDuckNotification;
     sys->refs = 1;
     sys->ducks = 0;
+    sys->mute = false;
 
-    sys->gain = 1.f;
-    sys->requested_volume = -1.f;
-    sys->requested_mute = -1;
     sys->acquired_device = NULL;
     sys->request_device_restart = false;
 
-    if (!var_CreateGetBool(aout, "volume-save"))
-        VolumeSetLocked(aout, var_InheritFloat(aout, "mmdevice-volume"));
+    sys->volume = var_InheritFloat(aout, "mmdevice-volume");
 
     InitializeCriticalSection(&sys->lock);
     InitializeConditionVariable(&sys->work);
@@ -1324,6 +1111,8 @@ static int Open(vlc_object_t *obj)
     aout->mute_set = MuteSet;
     aout->device_select = DeviceSelect;
 
+    aout_VolumeReport(aout, sys->volume);
+
     return VLC_SUCCESS;
 
 error:
diff --git a/modules/audio_output/mmdevice.h b/modules/audio_output/mmdevice.h
index 64cf730b30..d8d665037d 100644
--- a/modules/audio_output/mmdevice.h
+++ b/modules/audio_output/mmdevice.h
@@ -27,6 +27,7 @@
 #define MM_PASSTHROUGH_DEFAULT MM_PASSTHROUGH_DISABLED
 
 typedef struct aout_stream aout_stream_t;
+struct IAudioClient;
 
 /**
  * Audio output simplified API for Windows
@@ -40,6 +41,7 @@ struct aout_stream
     HRESULT (*play)(aout_stream_t *, block_t *);
     HRESULT (*pause)(aout_stream_t *, bool);
     HRESULT (*flush)(aout_stream_t *);
+    HRESULT (*getservice)(aout_stream_t *, REFIID riid, void **ppv);
 
     struct
     {
@@ -92,6 +94,12 @@ static inline HRESULT aout_stream_Flush(aout_stream_t *s, bool wait)
         return (s->flush)(s);
 }
 
+static inline HRESULT aout_stream_GetService(aout_stream_t *s,
+                                             REFIID riid, void **ppv)
+{
+    return (s->getservice)(s, riid, ppv);
+}
+
 static inline
 HRESULT aout_stream_Activate(aout_stream_t *s, REFIID iid,
                              PROPVARIANT *actparms, void **pv)
diff --git a/modules/audio_output/wasapi.c b/modules/audio_output/wasapi.c
index 0ce85db45d..9b491fcde8 100644
--- a/modules/audio_output/wasapi.c
+++ b/modules/audio_output/wasapi.c
@@ -239,6 +239,11 @@ static HRESULT Flush(aout_stream_t *s)
     return hr;
 }
 
+static HRESULT GetService(aout_stream_t *s, REFIID riid, void **ppv)
+{
+    aout_stream_sys_t *sys = s->sys;
+    return IAudioClient_GetService(sys->client, riid, ppv);
+}
 
 /*** Initialization / deinitialization **/
 static const uint32_t chans_out[] = {
@@ -611,6 +616,7 @@ static HRESULT Start(aout_stream_t *s, audio_sample_format_t *restrict pfmt,
     s->play = Play;
     s->pause = Pause;
     s->flush = Flush;
+    s->getservice = GetService;
     return S_OK;
 error:
     CoTaskMemFree(pwf_mix);
-- 
2.11.0



More information about the vlc-devel mailing list