[vlc-commits] mmdevice: override endpoint volume to allow amplification

Rémi Denis-Courmont git at videolan.org
Sat Mar 1 23:20:34 CET 2014


vlc | branch: master | Rémi Denis-Courmont <remi at remlab.net> | Sun Mar  2 00:17:38 2014 +0200| [c4d74ccfbb496eabf384d99272513664be924601] | committer: Rémi Denis-Courmont

mmdevice: override endpoint volume to allow amplification

This works more or less like SNDVOL (and like PulseAudio with flat
volumes not disabled): the VLC volume represents the product of the
endpoint volume by the simple volume.

> http://git.videolan.org/gitweb.cgi/vlc.git/?a=commit;h=c4d74ccfbb496eabf384d99272513664be924601
---

 modules/audio_output/mmdevice.c |  139 ++++++++++++++++++++++++++++++++++++---
 1 file changed, 130 insertions(+), 9 deletions(-)

diff --git a/modules/audio_output/mmdevice.c b/modules/audio_output/mmdevice.c
index e6a8b22..ec44c5c 100644
--- a/modules/audio_output/mmdevice.c
+++ b/modules/audio_output/mmdevice.c
@@ -112,6 +112,7 @@ struct aout_sys_t
     IMMDevice *dev; /**< Selected output device, NULL if none */
 
     struct IMMNotificationClient device_events;
+    struct IAudioEndpointVolumeCallback endpoint_callback;
     struct IAudioSessionEvents session_events;
 
     LONG refs;
@@ -384,6 +385,70 @@ static const struct IAudioSessionEventsVtbl vlc_AudioSessionEvents =
     vlc_AudioSessionEvents_OnSessionDisconnected,
 };
 
+
+/*** Audio endpoint volume ***/
+static inline aout_sys_t *vlc_AudioEndpointVolumeCallback_sys(IAudioEndpointVolumeCallback *this)
+{
+    return (void *)(((char *)this) - offsetof(aout_sys_t, endpoint_callback));
+}
+
+static STDMETHODIMP
+vlc_AudioEndpointVolumeCallback_QueryInterface(IAudioEndpointVolumeCallback *this,
+                                               REFIID riid, void **ppv)
+{
+    if (IsEqualIID(riid, &IID_IUnknown)
+     || IsEqualIID(riid, &IID_IAudioEndpointVolumeCallback))
+    {
+        *ppv = this;
+        IUnknown_AddRef(this);
+        return S_OK;
+    }
+    else
+    {
+       *ppv = NULL;
+        return E_NOINTERFACE;
+    }
+}
+
+static STDMETHODIMP_(ULONG)
+vlc_AudioEndpointVolumeCallback_AddRef(IAudioEndpointVolumeCallback *this)
+{
+    aout_sys_t *sys = vlc_AudioEndpointVolumeCallback_sys(this);
+    return InterlockedIncrement(&sys->refs);
+}
+
+static STDMETHODIMP_(ULONG)
+vlc_AudioEndpointVolumeCallback_Release(IAudioEndpointVolumeCallback *this)
+{
+    aout_sys_t *sys = vlc_AudioEndpointVolumeCallback_sys(this);
+    return InterlockedDecrement(&sys->refs);
+}
+
+static STDMETHODIMP
+vlc_AudioEndpointVolumeCallback_OnNotify(IAudioEndpointVolumeCallback *this,
+                                  const PAUDIO_VOLUME_NOTIFICATION_DATA notify)
+{
+    aout_sys_t *sys = vlc_AudioEndpointVolumeCallback_sys(this);
+    audio_output_t *aout = sys->aout;
+
+    msg_Dbg(aout, "endpoint volume changed");
+    EnterCriticalSection(&sys->lock);
+    WakeConditionVariable(&sys->work); /* implicit state: endpoint volume */
+    LeaveCriticalSection(&sys->lock);
+    (void) notify;
+    return S_OK;
+}
+
+static const struct IAudioEndpointVolumeCallbackVtbl vlc_AudioEndpointVolumeCallback =
+{
+    vlc_AudioEndpointVolumeCallback_QueryInterface,
+    vlc_AudioEndpointVolumeCallback_AddRef,
+    vlc_AudioEndpointVolumeCallback_Release,
+
+    vlc_AudioEndpointVolumeCallback_OnNotify,
+};
+
+
 /*** Audio devices ***/
 
 /** Gets the user-readable device name */
@@ -699,6 +764,7 @@ static HRESULT MMSession(audio_output_t *aout, IMMDeviceEnumerator *it)
     IAudioEndpointVolume *endpoint;
     void *pv;
     HRESULT hr;
+    float base_volume = 1.f;
 
     assert(sys->device != NULL);
     assert(sys->dev == NULL);
@@ -794,10 +860,16 @@ static HRESULT MMSession(audio_output_t *aout, IMMDeviceEnumerator *it)
 
         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);
+            base_volume = powf(10.f, max / 20.f + .3f);
+        }
         else
             msg_Err(aout, "cannot get volume range (error 0x%lx)", hr);
+
+        IAudioEndpointVolume_RegisterControlChangeNotify(endpoint,
+                                                      &sys->endpoint_callback);
     }
     else
         msg_Err(aout, "cannot activate endpoint volume (error %lx)", hr);
@@ -805,28 +877,60 @@ static HRESULT MMSession(audio_output_t *aout, IMMDeviceEnumerator *it)
     /* Main loop (adjust volume as long as device is unchanged) */
     while (sys->device == NULL)
     {
+        float level = 1.f, master = 1.f;
+
         if (volume != NULL)
         {
-            float level;
-
             hr = ISimpleAudioVolume_GetMasterVolume(volume, &level);
+            if (FAILED(hr))
+                msg_Err(aout, "cannot get master volume (error 0x%lx)", hr);
+        }
+
+        if (endpoint != NULL)
+        {
+            float db;
+
+            hr = IAudioEndpointVolume_GetMasterVolumeLevel(endpoint, &db);
             if (SUCCEEDED(hr))
-                aout_VolumeReport(aout, cbrtf(level));
+                master = powf(10.f, db / 20.f);
             else
-                msg_Err(aout, "cannot get master volume (error 0x%lx)", hr);
+                msg_Err(aout, "cannot get endpoint volume (error 0x%lx)", hr);
+        }
+
+        aout_VolumeReport(aout, cbrtf(level * master * base_volume));
 
-            level = sys->volume;
+    /* The WASAPI simple volume is relative to the endpoint volume, and it
+     * cannot exceed 100%. Therefore the endpoint master volume must be
+     * increased to reach an overall volume above the current endpoint master
+     * volume. Unfortunately, that means the volume of other applications will
+     * also be changed (which may or may not be what the user wants) and
+     * introduces race conditions between updates. */
+
+        level = sys->volume / base_volume;
+        sys->volume = -1.f;
+
+        if (level > master)
+        {
+            master = level;
+            level = 1.f;
+        }
+        else
+        {
+            if (master > 0.f)
+                level /= master;
+            master = -1.f;
+        }
+
+        if (volume != NULL)
+        {
             if (level >= 0.f)
             {
-                if (level > 1.f)
-                    level = 1.f;
-
+                assert(level <= 1.f);
                 hr = ISimpleAudioVolume_SetMasterVolume(volume, level, NULL);
                 if (FAILED(hr))
                     msg_Err(aout, "cannot set master volume (error 0x%lx)",
                             hr);
             }
-            sys->volume = -1.f;
 
             BOOL mute;
 
@@ -847,12 +951,28 @@ static HRESULT MMSession(audio_output_t *aout, IMMDeviceEnumerator *it)
             sys->mute = -1;
         }
 
+        if (endpoint != NULL && master >= 0.f)
+        {
+            float v = 20.f * log10f(master);
+
+            msg_Warn(aout, "overriding endpoint volume: %+f dB", v);
+            hr = IAudioEndpointVolume_SetMasterVolumeLevel(endpoint, v, NULL);
+            if (FAILED(hr))
+                msg_Err(aout, "cannot set endpoint volume (error 0x%lx)", hr);
+        }
+
+        sys->volume = -1.f;
+
         SleepConditionVariableCS(&sys->work, &sys->lock, INFINITE);
     }
     LeaveCriticalSection(&sys->lock);
 
     if (endpoint != NULL)
+    {
+        IAudioEndpointVolume_UnregisterControlChangeNotify(endpoint,
+                                                      &sys->endpoint_callback);
         IAudioEndpointVolume_Release(endpoint);
+    }
 
     if (manager != NULL)
     {   /* Deregister callbacks *without* the lock */
@@ -999,6 +1119,7 @@ static int Open(vlc_object_t *obj)
     sys->it = NULL;
     sys->dev = NULL;
     sys->device_events.lpVtbl = &vlc_MMNotificationClient;
+    sys->endpoint_callback.lpVtbl = &vlc_AudioEndpointVolumeCallback;
     sys->session_events.lpVtbl = &vlc_AudioSessionEvents;
     sys->refs = 1;
 



More information about the vlc-commits mailing list