Merge branch 'mmdevice/vol-per-player' into 'master'

mmdevice: allow to set the volume per player rather than per session

See merge request videolan/vlc!3599
This commit is contained in:
Pierre Lamot 2024-04-28 07:10:53 +00:00
commit b4adbe481e
7 changed files with 737 additions and 267 deletions

View File

@ -83,7 +83,11 @@ if HAVE_JACK
aout_LTLIBRARIES += libjack_plugin.la
endif
libmmdevice_plugin_la_SOURCES = audio_output/mmdevice.c audio_output/mmdevice.h
libmmdevice_plugin_la_SOURCES = audio_output/mmdevice.c \
audio_output/mmdevice.h \
audio_output/mmdevice_volume_control.h \
audio_output/mmdevice_session_volume.c \
audio_output/mmdevice_player_volume.c
libmmdevice_plugin_la_LIBADD = $(LIBCOM) $(LIBM)
libwinstore_plugin_la_SOURCES = audio_output/winstore.c audio_output/mmdevice.h
libwinstore_plugin_la_LIBADD = $(LIBCOM) -lmmdevapi

View File

@ -34,6 +34,8 @@
#include <mmdeviceapi.h>
#include <endpointvolume.h>
#include "mmdevice_volume_control.h"
DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd,
0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
@ -89,16 +91,15 @@ typedef struct
IMMDevice *dev; /**< Selected output device, NULL if none */
struct IMMNotificationClient device_events;
struct IAudioSessionEvents session_events;
struct IAudioVolumeDuckNotification duck;
float gain;
mmdevice_volume_controler_t* volume;
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 */
wchar_t *acquired_device; /**< Acquired device identifier, NULL if none */
bool request_device_restart;
HANDLE work_event;
@ -173,30 +174,20 @@ static void Flush(audio_output_t *aout)
static int VolumeSetLocked(audio_output_t *aout, float vol)
{
aout_sys_t *sys = aout->sys;
float gain = 1.f;
vol = vol * vol * vol; /* ISimpleAudioVolume is tapered linearly. */
if (vol > 1.f)
{
gain = vol;
vol = 1.f;
}
sys->gain = gain;
sys->requested_volume = vol;
return 0;
return mmdevice_volume_controler_request_volume(sys->volume, vol, &sys->gain);
}
static int VolumeSet(audio_output_t *aout, float vol)
{
aout_sys_t *sys = aout->sys;
vlc_mutex_lock(&sys->lock);
int ret = VolumeSetLocked(aout, vol);
aout_GainRequest(aout, sys->gain);
vlc_mutex_unlock(&sys->lock);
SetEvent(sys->work_event);
aout_GainRequest(sys->aout, sys->gain);
return ret;
}
@ -205,176 +196,11 @@ static int MuteSet(audio_output_t *aout, bool mute)
aout_sys_t *sys = aout->sys;
vlc_mutex_lock(&sys->lock);
sys->requested_mute = mute;
int ret = mmdevice_volume_controler_request_mute(sys->volume, mute);
vlc_mutex_unlock(&sys->lock);
SetEvent(sys->work_event);
return 0;
return ret;
}
/*** Audio session events ***/
static STDMETHODIMP
vlc_AudioSessionEvents_QueryInterface(IAudioSessionEvents *this, REFIID riid,
void **ppv)
{
if (IsEqualIID(riid, &IID_IUnknown)
|| IsEqualIID(riid, &IID_IAudioSessionEvents))
{
*ppv = this;
IUnknown_AddRef(this);
return S_OK;
}
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");
SetEvent(sys->work_event); /* implicit state: vol & mute */
(void) ctx;
return S_OK;
}
static STDMETHODIMP
vlc_AudioSessionEvents_OnChannelVolumeChanged(IAudioSessionEvents *this,
DWORD count, float *vols,
DWORD changed, LPCGUID ctx)
{
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 = container_of(this, aout_sys_t, session_events);
audio_output_t *aout = sys->aout;
msg_Dbg(aout, "grouping parameter changed");
(void) param;
(void) ctx;
return S_OK;
}
static STDMETHODIMP
vlc_AudioSessionEvents_OnStateChanged(IAudioSessionEvents *this,
AudioSessionState state)
{
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;
}
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;
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 overridden");
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;
}
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(
@ -782,61 +608,15 @@ static int DeviceSelect(audio_output_t *aout, const char *id)
*
* Adjust volume as long as device is unchanged
* */
static void MMSessionMainloop(audio_output_t *aout, ISimpleAudioVolume *volume)
static void MMSessionMainloop(audio_output_t *aout)
{
aout_sys_t *sys = aout->sys;
HRESULT hr;
bool report_volume = true;
bool report_mute = true;
mmdevice_volume_controler_t* volume = sys->volume;
while (sys->requested_device == NULL)
{
if (volume != NULL)
{
if (sys->requested_volume >= 0.f)
{
hr = ISimpleAudioVolume_SetMasterVolume(volume, sys->requested_volume, NULL);
if (FAILED(hr))
msg_Err(aout, "cannot set master volume (error 0x%lX)",
hr);
report_volume = true;
sys->requested_volume = -1.f;
}
if (report_volume)
{
float level;
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);
report_volume = false;
}
if (sys->requested_mute >= 0)
{
BOOL 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);
report_mute = true;
sys->requested_mute = -1;
}
if (report_mute)
{
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);
report_mute = false;
}
}
mmdevice_volume_controler_process(volume, sys->stream);
DWORD wait_ms = INFINITE;
DWORD ev_count = 1;
@ -890,7 +670,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;
@ -961,14 +740,14 @@ static HRESULT MMSession(audio_output_t *aout, IMMDeviceEnumerator *it)
return hr;
}
LPCGUID guid = var_GetBool(aout, "volume-save") ? &GUID_VLC_AUD_OUT : NULL;
/* Create session manager (for controls even w/o active audio client) */
hr = IMMDevice_Activate(sys->dev, &IID_IAudioSessionManager,
CLSCTX_ALL, NULL, &pv);
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,
&control);
@ -985,17 +764,12 @@ static HRESULT MMSession(audio_output_t *aout, IMMDeviceEnumerator *it)
}
free(ua);
}
IAudioSessionControl_RegisterAudioSessionNotification(control,
&sys->session_events);
}
else
{
control = NULL;
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;
@ -1031,11 +805,9 @@ static HRESULT MMSession(audio_output_t *aout, IMMDeviceEnumerator *it)
CoTaskMemFree(siid);
}
else
{
msg_Err(aout, "cannot activate session manager (error 0x%lX)", hr);
control = NULL;
volume = NULL;
}
mmdevice_volume_controler_initialize(sys->volume, manager, guid);
hr = IMMDevice_Activate(sys->dev, &IID_IAudioEndpointVolume,
CLSCTX_ALL, NULL, &pv);
@ -1054,10 +826,14 @@ static HRESULT MMSession(audio_output_t *aout, IMMDeviceEnumerator *it)
else
msg_Err(aout, "cannot activate endpoint volume (error 0x%lX)", hr);
MMSessionMainloop(aout, volume);
MMSessionMainloop(aout);
vlc_mutex_unlock(&sys->lock);
if (sys->volume)
mmdevice_volume_controler_release(sys->volume);
sys->volume = NULL;
if (endpoint != NULL)
IAudioEndpointVolume_Release(endpoint);
@ -1073,15 +849,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);
}
@ -1330,20 +1099,12 @@ 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->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"));
vlc_mutex_init(&sys->lock);
vlc_cond_init(&sys->ready);
@ -1351,6 +1112,17 @@ static int Open(vlc_object_t *obj)
if (unlikely(sys->work_event == NULL))
goto error;
if (var_InheritBool(aout, "mmdevice-session-volume"))
sys->volume = createMMDeviceSessionVolumeControler(aout, sys->work_event);
else
sys->volume = createMMDevicePlayerVolumeControler(aout, sys->work_event);
if (unlikely(sys->volume == NULL))
goto error;
if (!var_CreateGetBool(aout, "volume-save"))
VolumeSetLocked(aout, var_InheritFloat(aout, "mmdevice-volume"));
aout_HotplugReport(aout, default_device_b, _("Default"));
char *saved_device_b = var_InheritString(aout, "mmdevice-audio-device");
@ -1535,6 +1307,10 @@ static const char *const ppsz_mmdevice_passthrough_texts[] = {
#define VOLUME_TEXT N_("Audio volume")
#define VOLUME_LONGTEXT N_("Audio volume in hundredths of decibels (dB).")
#define SESSION_VOLUME_TEXT N_("Use session volume")
#define SESSION_VOLUME_LONGTEXT N_("Use session volume, or per player volume when disabled")
vlc_module_begin()
set_shortname("MMDevice")
set_description(N_("Windows Multimedia Device output"))
@ -1550,4 +1326,5 @@ vlc_module_begin()
add_string("mmdevice-audio-device", NULL, DEVICE_TEXT, DEVICE_LONGTEXT)
add_float("mmdevice-volume", 1.f, VOLUME_TEXT, VOLUME_LONGTEXT)
change_float_range( 0.f, 1.25f )
add_bool("mmdevice-session-volume", true, SESSION_VOLUME_TEXT, SESSION_VOLUME_LONGTEXT)
vlc_module_end()

View File

@ -40,6 +40,7 @@ struct aout_stream
HRESULT (*play)(aout_stream_t *, block_t *, vlc_tick_t);
HRESULT (*pause)(aout_stream_t *, bool);
HRESULT (*flush)(aout_stream_t *);
HRESULT (*getservice)(aout_stream_t *, REFIID riid, void **ppv);
};
struct aout_stream_owner
@ -110,6 +111,12 @@ HRESULT aout_stream_owner_Flush(struct aout_stream_owner *owner)
return owner->s.flush(&owner->s);
}
static inline HRESULT aout_stream_GetService(struct aout_stream_owner *owner,
REFIID riid, void **ppv)
{
return owner->s.getservice(&owner->s, riid, ppv);
}
static inline
void aout_stream_owner_AppendBlock(struct aout_stream_owner *owner,
block_t *block, vlc_tick_t date)

View File

@ -0,0 +1,196 @@
/*****************************************************************************
* Copyright (C) 2012-2023 VLC authors and VideoLAN
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#include "mmdevice_volume_control.h"
#include <vlc_aout.h>
#include "mmdevice.h"
typedef struct
{
mmdevice_volume_controler_t iface;
audio_output_t *aout;
float requested_volume; /**< volume */
signed char requested_mute; /**< mute */
bool report_mute;
bool report_volume;
float volume;
float gain; /**< gain */
HANDLE work_event;
} mmdevice_player_volume_controler_t;
#define VOLUME_CONTROL_IMPL_FROM_IFACE(obj) \
container_of((obj), mmdevice_player_volume_controler_t, iface)
static int RequestMute(mmdevice_volume_controler_t* controler, bool mute)
{
mmdevice_player_volume_controler_t *sys = VOLUME_CONTROL_IMPL_FROM_IFACE(controler);
sys->requested_mute = mute;
SetEvent(sys->work_event);
return 0;
}
static int RequestVolume(mmdevice_volume_controler_t* controler, float value, float* outGain)
{
mmdevice_player_volume_controler_t *sys = VOLUME_CONTROL_IMPL_FROM_IFACE(controler);
float gain = 1.f;
value = value * value * value; /* ISimpleAudioVolume is tapered linearly. */
if (value > 1.f)
{
gain = value;
value = 1.f;
}
sys->requested_volume = value;
sys->gain =gain;
if (outGain != NULL)
*outGain = gain;
SetEvent(sys->work_event);
return 0;
}
static void SetAllStreamVolume(IAudioStreamVolume *stream_volume, UINT32 chan_count, float volume)
{
float chan_volumes[chan_count];
for (UINT32 i = 0; i < chan_count; ++i)
chan_volumes[i] = volume;
IAudioStreamVolume_SetAllVolumes(stream_volume, chan_count, chan_volumes);
}
static void Process(mmdevice_volume_controler_t* controler, struct aout_stream_owner *stream)
{
mmdevice_player_volume_controler_t *sys = VOLUME_CONTROL_IMPL_FROM_IFACE(controler);
if (!stream
|| (sys->requested_mute < 0 && sys->requested_volume < 0))
return;
IAudioStreamVolume *stream_volume = NULL;
void* pv;
HRESULT hr = aout_stream_GetService(stream, &IID_IAudioStreamVolume, &pv);
if (FAILED(hr))
goto error;
stream_volume = pv;
UINT32 chan_count;
hr = IAudioStreamVolume_GetChannelCount(stream_volume, &chan_count);
if (FAILED(hr))
goto error;
assert(chan_count <= 64);
if (chan_count == 0)
goto error;
if (sys->requested_mute >= 0)
{
if (sys->requested_mute)
SetAllStreamVolume(stream_volume, chan_count, 0.f);
else
SetAllStreamVolume(stream_volume, chan_count, sys->volume);
sys->report_mute = true;
sys->requested_mute = -1;
}
if (sys->requested_volume >= 0.f)
{
SetAllStreamVolume(stream_volume, chan_count, sys->requested_volume);
sys->volume = sys->requested_volume;
sys->report_volume = true;
sys->requested_volume = -1.f;
}
float currentVolume;
hr = IAudioStreamVolume_GetChannelVolume(stream_volume, 0,
&currentVolume);
if (FAILED(hr))
goto error;
if (sys->report_mute)
{
aout_MuteReport(sys->aout, currentVolume == 0.f);
sys->report_mute =false;
}
if (sys->report_volume)
{
aout_VolumeReport(sys->aout, cbrtf(currentVolume * sys->gain));
sys->report_volume =false;
}
error:
if (stream_volume)
IAudioStreamVolume_Release(stream_volume);
}
static bool Initialize(struct mmdevice_volume_controler_t* controler, IAudioSessionManager *manager, LPCGUID guid)
{
VLC_UNUSED(controler);
VLC_UNUSED(manager);
VLC_UNUSED(guid);
return true;
}
static void Release(mmdevice_volume_controler_t* controler)
{
mmdevice_player_volume_controler_t *sys = VOLUME_CONTROL_IMPL_FROM_IFACE(controler);
free(sys);
}
mmdevice_volume_controler_t* createMMDevicePlayerVolumeControler(audio_output_t *aout, HANDLE work_event)
{
mmdevice_player_volume_controler_t* sys = malloc(sizeof(mmdevice_player_volume_controler_t));
if (!sys)
return NULL;
sys->aout = aout;
sys->requested_mute = -1;
sys->requested_volume = -1.f;
//report initial volume and mute state
sys->report_mute = true;
sys->report_volume = true;
sys->volume = 1.f;
sys->gain = 1.f;
sys->work_event = work_event;
sys->iface.release = Release;
sys->iface.initialize = Initialize;
sys->iface.process = Process;
sys->iface.requestMute = RequestMute;
sys->iface.requestVolume = RequestVolume;
return &sys->iface;
}

View File

@ -0,0 +1,399 @@
/*****************************************************************************
* mmdevice_session_volume.c : Windows Multimedia Device API audio output plugin for VLC
*****************************************************************************
* Copyright (C) 2012-2017 Rémi Denis-Courmont
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#include "mmdevice_volume_control.h"
#include <vlc_aout.h>
typedef struct
{
mmdevice_volume_controler_t iface;
audio_output_t *aout;
struct IAudioSessionEvents session_events;
ISimpleAudioVolume *volume;
IAudioSessionControl *control;
LONG refs;
HANDLE work_event;
vlc_mutex_t lock;
float gain; /**< Current software gain volume */
bool report_volume;
bool report_mute;
float requested_volume; /**< Requested volume, negative if none */
signed char requested_mute; /**< Requested mute, negative if none */
} mmdevice_session_volume_controler_t;
#define VOLUME_CONTROL_IMPL_FROM_IFACE(obj) \
container_of((obj), mmdevice_session_volume_controler_t, iface)
/*** Audio session events ***/
static STDMETHODIMP
vlc_AudioSessionEvents_QueryInterface(IAudioSessionEvents *this, REFIID riid,
void **ppv)
{
if (IsEqualIID(riid, &IID_IUnknown)
|| IsEqualIID(riid, &IID_IAudioSessionEvents))
{
*ppv = this;
IUnknown_AddRef(this);
return S_OK;
}
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
}
static STDMETHODIMP_(ULONG)
vlc_AudioSessionEvents_AddRef(IAudioSessionEvents *this)
{
mmdevice_session_volume_controler_t *sys = container_of(this, mmdevice_session_volume_controler_t, session_events);
return InterlockedIncrement(&sys->refs);
}
static STDMETHODIMP_(ULONG)
vlc_AudioSessionEvents_Release(IAudioSessionEvents *this)
{
mmdevice_session_volume_controler_t *sys = container_of(this, mmdevice_session_volume_controler_t, session_events);
return InterlockedDecrement(&sys->refs);
}
static STDMETHODIMP
vlc_AudioSessionEvents_OnDisplayNameChanged(IAudioSessionEvents *this,
LPCWSTR wname, LPCGUID ctx)
{
mmdevice_session_volume_controler_t *sys = container_of(this, mmdevice_session_volume_controler_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)
{
mmdevice_session_volume_controler_t *sys = container_of(this, mmdevice_session_volume_controler_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)
{
mmdevice_session_volume_controler_t *sys = container_of(this, mmdevice_session_volume_controler_t, session_events);
audio_output_t *aout = sys->aout;
msg_Dbg(aout, "simple volume changed: %f, muting %sabled", vol,
mute ? "en" : "dis");
vlc_mutex_lock(&sys->lock);
sys->report_volume = true;
sys->report_mute = true;
vlc_mutex_unlock(&sys->lock);
SetEvent(sys->work_event);
(void) ctx;
return S_OK;
}
static STDMETHODIMP
vlc_AudioSessionEvents_OnChannelVolumeChanged(IAudioSessionEvents *this,
DWORD count, float *vols,
DWORD changed, LPCGUID ctx)
{
mmdevice_session_volume_controler_t *sys = container_of(this, mmdevice_session_volume_controler_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)
{
mmdevice_session_volume_controler_t *sys = container_of(this, mmdevice_session_volume_controler_t, session_events);
audio_output_t *aout = sys->aout;
msg_Dbg(aout, "grouping parameter changed");
(void) param;
(void) ctx;
return S_OK;
}
static STDMETHODIMP
vlc_AudioSessionEvents_OnStateChanged(IAudioSessionEvents *this,
AudioSessionState state)
{
mmdevice_session_volume_controler_t *sys = container_of(this, mmdevice_session_volume_controler_t, session_events);
audio_output_t *aout = sys->aout;
msg_Dbg(aout, "state changed: %d", state);
return S_OK;
}
static STDMETHODIMP
vlc_AudioSessionEvents_OnSessionDisconnected(IAudioSessionEvents *this,
AudioSessionDisconnectReason reason)
{
mmdevice_session_volume_controler_t *sys = container_of(this, mmdevice_session_volume_controler_t, session_events);
audio_output_t *aout = sys->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 overridden");
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;
}
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 void Process(mmdevice_volume_controler_t *controler, struct aout_stream_owner * stream)
{
VLC_UNUSED(stream);
HRESULT hr;
mmdevice_session_volume_controler_t *sys = VOLUME_CONTROL_IMPL_FROM_IFACE(controler);
audio_output_t *aout = sys->aout;
vlc_mutex_lock(&sys->lock);
if (sys->requested_volume >= 0.f)
{
hr = ISimpleAudioVolume_SetMasterVolume(sys->volume, sys->requested_volume, NULL);
if (FAILED(hr))
msg_Err(aout, "cannot set master volume (error 0x%lX)", hr);
sys->requested_volume = -1.f;
}
if (sys->report_volume)
{
float level;
hr = ISimpleAudioVolume_GetMasterVolume(sys->volume, &level);
if (SUCCEEDED(hr))
aout_VolumeReport(aout, cbrtf(level * sys->gain));
else
msg_Err(aout, "cannot get master volume (error 0x%lX)", hr);
sys->report_volume = false;
}
if (sys->requested_mute >= 0)
{
BOOL mute = sys->requested_mute ? TRUE : FALSE;
hr = ISimpleAudioVolume_SetMute(sys->volume, mute, NULL);
if (FAILED(hr))
msg_Err(aout, "cannot set mute (error 0x%lX)", hr);
sys->requested_mute = -1;
}
if (sys->report_mute)
{
BOOL mute;
hr = ISimpleAudioVolume_GetMute(sys->volume, &mute);
if (SUCCEEDED(hr))
aout_MuteReport(aout, mute != FALSE);
else
msg_Err(aout, "cannot get mute (error 0x%lX)", hr);
sys->report_mute = false;
}
vlc_mutex_unlock(&sys->lock);
}
static int RequestMute(mmdevice_volume_controler_t *controler, bool mute)
{
mmdevice_session_volume_controler_t *sys = VOLUME_CONTROL_IMPL_FROM_IFACE(controler);
vlc_mutex_lock(&sys->lock);
{
sys->requested_mute = mute;
sys->report_mute = true;
}
vlc_mutex_unlock(&sys->lock);
SetEvent(sys->work_event);
return 0;
}
static int RequestVolume(mmdevice_volume_controler_t *controler, float vol, float* outGain)
{
mmdevice_session_volume_controler_t *sys = VOLUME_CONTROL_IMPL_FROM_IFACE(controler);
float gain = 1.f;
vol = vol * vol * vol; /* ISimpleAudioVolume is tapered linearly. */
if (vol > 1.f)
{
gain = vol;
vol = 1.f;
}
vlc_mutex_lock(&sys->lock);
{
sys->requested_volume = vol;
sys->report_volume = true;
sys->gain =gain;
}
vlc_mutex_unlock(&sys->lock);
if (outGain != NULL)
*outGain = gain;
SetEvent(sys->work_event);
return 0;
}
static void Release(mmdevice_volume_controler_t *controler)
{
mmdevice_session_volume_controler_t *sys = VOLUME_CONTROL_IMPL_FROM_IFACE(controler);
if (!sys)
return;
if (sys->volume != NULL)
ISimpleAudioVolume_Release(sys->volume);
if (sys->control)
{
IAudioSessionControl_UnregisterAudioSessionNotification(sys->control,
&sys->session_events);
IAudioSessionControl_Release(sys->control);
}
free(sys);
}
static bool Initialize(struct mmdevice_volume_controler_t* controler, IAudioSessionManager *manager, LPCGUID guid)
{
mmdevice_session_volume_controler_t *sys = VOLUME_CONTROL_IMPL_FROM_IFACE(controler);
HRESULT hr;
IAudioSessionControl *control;
hr = IAudioSessionManager_GetAudioSessionControl(manager, guid, 0,
&control);
if (FAILED(hr))
return false;
sys->control = control;
IAudioSessionControl_RegisterAudioSessionNotification(control,
&sys->session_events);
hr = IAudioSessionManager_GetSimpleAudioVolume(manager, guid, FALSE,
&sys->volume);
if (FAILED(hr))
{
msg_Err(sys->aout, "cannot get simple volume (error 0x%lx)", hr);
return false;
}
return true;
}
mmdevice_volume_controler_t* createMMDeviceSessionVolumeControler(audio_output_t *aout, HANDLE workevent)
{
mmdevice_session_volume_controler_t* sys = malloc(sizeof(mmdevice_session_volume_controler_t));
if (!sys)
return NULL;
sys->aout = aout;
sys->refs = 1;
sys->volume = NULL;
sys->control = NULL;
sys->gain = 1.f;
sys->work_event = workevent;
vlc_mutex_init(&sys->lock);
sys->report_volume = true;
sys->report_mute = true;
sys->requested_volume = -1.f;
sys->requested_mute = -1;
sys->session_events.lpVtbl = &vlc_AudioSessionEvents;
sys->iface.release = Release;
sys->iface.requestMute = RequestMute;
sys->iface.requestVolume = RequestVolume;
sys->iface.initialize = Initialize;
sys->iface.process = Process;
return &sys->iface;
}

View File

@ -0,0 +1,80 @@
/*****************************************************************************
* Copyright (C) 2012-2023 VLC authors and VideoLAN
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifndef MMDEVICE_VOLUME_H
#define MMDEVICE_VOLUME_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <vlc_common.h>
#define INITGUID
#define COBJMACROS
#define CONST_VTABLE
#include <endpointvolume.h>
#include <audiopolicy.h>
struct aout_stream_owner;
struct mmdevice_volume_controler_t
{
void (*release)(struct mmdevice_volume_controler_t* volume);
int (*requestMute)(struct mmdevice_volume_controler_t* volume, bool mute);
int (*requestVolume)(struct mmdevice_volume_controler_t* volume, float vol, float* gain);
//initialize is called from the MM Thread
bool (*initialize)(struct mmdevice_volume_controler_t* volume, IAudioSessionManager *manager, LPCGUID guid);
//process is called from the MM Thread
void (*process)(struct mmdevice_volume_controler_t* volume, struct aout_stream_owner * stream);
};
typedef struct mmdevice_volume_controler_t mmdevice_volume_controler_t;
inline static void mmdevice_volume_controler_release(mmdevice_volume_controler_t* controler)
{
controler->release(controler);
}
inline static int mmdevice_volume_controler_request_mute(mmdevice_volume_controler_t* controler, bool mute)
{
return controler->requestMute(controler, mute);
}
inline static int mmdevice_volume_controler_request_volume(mmdevice_volume_controler_t* controler, float vol, float* gain)
{
return controler->requestVolume(controler, vol, gain);
}
inline static bool mmdevice_volume_controler_initialize(mmdevice_volume_controler_t* controler, IAudioSessionManager *manager, LPCGUID guid)
{
return controler->initialize(controler, manager, guid);
}
inline static void mmdevice_volume_controler_process(mmdevice_volume_controler_t* controler, struct aout_stream_owner * stream)
{
controler->process(controler, stream);
}
mmdevice_volume_controler_t* createMMDeviceSessionVolumeControler(audio_output_t *aout, HANDLE work_event);
mmdevice_volume_controler_t* createMMDevicePlayerVolumeControler(audio_output_t *aout, HANDLE work_event);
#endif /* MMDEVICE_VOLUME_H */

View File

@ -365,6 +365,12 @@ static HRESULT Flush(aout_stream_t *s)
}
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[] = {
SPEAKER_FRONT_LEFT, SPEAKER_FRONT_RIGHT,
@ -930,6 +936,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;
s->stop = Stop;
return S_OK;
error: