/*****************************************************************************
* mmdevice.c : Windows Multimedia Device API audio output plugin for VLC
*****************************************************************************
- * Copyright (C) 2012 Rémi Denis-Courmont
+ * Copyright (C) 2012-2014 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
#include <assert.h>
#include <audiopolicy.h>
#include <mmdeviceapi.h>
+#include <endpointvolume.h>
DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd,
0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
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);
return 0;
}
#define TryEnterMTA(o) TryEnterMTA(VLC_OBJECT(o))
-#endif
static void EnterMTA(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 */
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 *device; /**< Requested device identifier, NULL if none */
float volume; /**< Requested volume, negative if none */
CONDITION_VARIABLE work;
CONDITION_VARIABLE ready;
vlc_thread_t thread; /**< Thread for audio session control */
-#else
- void *client;
-#endif
};
/* NOTE: The Core Audio API documentation totally fails to specify the thread
aout_sys_t *sys = aout->sys;
EnterMTA();
-
- if (wait)
- { /* Loosy drain emulation */
- mtime_t delay;
-
- if (SUCCEEDED(aout_stream_TimeGet(sys->stream, &delay)))
- Sleep((delay / (CLOCK_FREQ / 1000)) + 1);
- }
- else
- aout_stream_Flush(sys->stream);
-
+ aout_stream_Flush(sys->stream, wait);
LeaveMTA();
-
}
-#if !VLC_WINSTORE_APP
static int VolumeSet(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;
+ }
+
+ aout_GainRequest(aout, gain);
+
EnterCriticalSection(&sys->lock);
+ sys->gain = gain;
sys->volume = vol;
WakeConditionVariable(&sys->work);
LeaveCriticalSection(&sys->lock);
static STDMETHODIMP
vlc_AudioSessionEvents_OnSimpleVolumeChanged(IAudioSessionEvents *this,
- float vol, WINBOOL mute,
+ float vol, BOOL mute,
LPCGUID ctx)
{
aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
msg_Dbg(aout, "simple volume changed: %f, muting %sabled", vol,
mute ? "en" : "dis");
- aout_VolumeReport(aout, cbrtf(vol));
- aout_MuteReport(aout, mute == TRUE);
+ EnterCriticalSection(&sys->lock);
+ WakeConditionVariable(&sys->work); /* implicit state: vol & mute */
+ LeaveCriticalSection(&sys->lock);
(void) ctx;
return S_OK;
}
aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
audio_output_t *aout = sys->aout;
- msg_Dbg(aout, "channel volume %lu of %lu changed: %f", changed, count,
- vols[changed]);
+ 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;
}
vlc_AudioSessionEvents_OnSessionDisconnected,
};
+static inline aout_sys_t *vlc_AudioVolumeDuckNotification_sys(IAudioVolumeDuckNotification *this)
+{
+ return (void *)(((char *)this) - offsetof(aout_sys_t, duck));
+}
+
+static STDMETHODIMP
+vlc_AudioVolumeDuckNotification_QueryInterface(
+ IAudioVolumeDuckNotification *this, REFIID riid, void **ppv)
+{
+ if (IsEqualIID(riid, &IID_IUnknown)
+ || IsEqualIID(riid, &IID_IAudioVolumeDuckNotification))
+ {
+ *ppv = this;
+ IUnknown_AddRef(this);
+ return S_OK;
+ }
+ else
+ {
+ *ppv = NULL;
+ return E_NOINTERFACE;
+ }
+}
+
+static STDMETHODIMP_(ULONG)
+vlc_AudioVolumeDuckNotification_AddRef(IAudioVolumeDuckNotification *this)
+{
+ aout_sys_t *sys = vlc_AudioVolumeDuckNotification_sys(this);
+ return InterlockedIncrement(&sys->refs);
+}
+
+static STDMETHODIMP_(ULONG)
+vlc_AudioVolumeDuckNotification_Release(IAudioVolumeDuckNotification *this)
+{
+ aout_sys_t *sys = vlc_AudioVolumeDuckNotification_sys(this);
+ return InterlockedDecrement(&sys->refs);
+}
+
+static STDMETHODIMP
+vlc_AudioVolumeDuckNotification_OnVolumeDuckNotification(
+ IAudioVolumeDuckNotification *this, LPCWSTR sid, UINT32 count)
+{
+ aout_sys_t *sys = vlc_AudioVolumeDuckNotification_sys(this);
+ audio_output_t *aout = sys->aout;
+
+ msg_Dbg(aout, "volume ducked by %ls of %u sessions", sid, count);
+ sys->ducks++;
+ aout_PolicyReport(aout, true);
+ return S_OK;
+}
+
+static STDMETHODIMP
+vlc_AudioVolumeDuckNotification_OnVolumeUnduckNotification(
+ IAudioVolumeDuckNotification *this, LPCWSTR sid)
+{
+ aout_sys_t *sys = vlc_AudioVolumeDuckNotification_sys(this);
+ audio_output_t *aout = sys->aout;
+
+ msg_Dbg(aout, "volume unducked by %ls", sid);
+ sys->ducks--;
+ aout_PolicyReport(aout, sys->ducks != 0);
+ return S_OK;
+}
+
+static const struct IAudioVolumeDuckNotificationVtbl vlc_AudioVolumeDuckNotification =
+{
+ vlc_AudioVolumeDuckNotification_QueryInterface,
+ vlc_AudioVolumeDuckNotification_AddRef,
+ vlc_AudioVolumeDuckNotification_Release,
+
+ vlc_AudioVolumeDuckNotification_OnVolumeDuckNotification,
+ vlc_AudioVolumeDuckNotification_OnVolumeUnduckNotification,
+};
+
+
/*** Audio devices ***/
/** Gets the user-readable device name */
}
static STDMETHODIMP
-vlc_MMNotificationClient_OnDevicePropertyChanged(IMMNotificationClient *this,
- LPCWSTR wid,
- const PROPERTYKEY key)
+vlc_MMNotificationClient_OnPropertyValueChanged(IMMNotificationClient *this,
+ LPCWSTR wid,
+ const PROPERTYKEY key)
{
aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
audio_output_t *aout = sys->aout;
vlc_MMNotificationClient_OnDeviceAdded,
vlc_MMNotificationClient_OnDeviceRemoved,
vlc_MMNotificationClient_OnDefaultDeviceChange,
- vlc_MMNotificationClient_OnDevicePropertyChanged,
+ vlc_MMNotificationClient_OnPropertyValueChanged,
};
static int DevicesEnum(audio_output_t *aout, IMMDeviceEnumerator *it)
IAudioSessionManager *manager;
IAudioSessionControl *control;
ISimpleAudioVolume *volume;
+ IAudioEndpointVolume *endpoint;
void *pv;
HRESULT hr;
{
msg_Dbg(aout, "using selected device %ls", sys->device);
hr = IMMDeviceEnumerator_GetDevice(it, sys->device, &sys->dev);
+ if (FAILED(hr))
+ msg_Err(aout, "cannot get selected device %ls (error 0x%lx)",
+ sys->device, hr);
free(sys->device);
}
else
msg_Dbg(aout, "using default device");
hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(it, eRender,
eConsole, &sys->dev);
+ if (FAILED(hr))
+ msg_Err(aout, "cannot get default device (error 0x%lx)", hr);
}
sys->device = NULL;
}
else
{
- msg_Err(aout, "cannot get device (error 0x%lx)", hr);
+ msg_Err(aout, "cannot get device identifier (error 0x%lx)", hr);
return 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;
+
+ hr = IAudioSessionManager_QueryInterface(manager,
+ &IID_IAudioSessionControl2, &pv);
if (SUCCEEDED(hr))
- { /* Get current values _after_ registering for notification */
- BOOL mute;
- float level;
+ {
+ IAudioSessionControl2 *c2 = pv;
- hr = ISimpleAudioVolume_GetMute(volume, &mute);
- if (SUCCEEDED(hr))
- aout_MuteReport(aout, mute != FALSE);
- else
- msg_Err(aout, "cannot get mute (error 0x%lx)", hr);
+ IAudioSessionControl2_SetDuckingPreference(c2, FALSE);
+ hr = IAudioSessionControl2_GetSessionInstanceIdentifier(c2, &siid);
+ if (FAILED(hr))
+ siid = NULL;
+ IAudioSessionControl2_Release(c2);
+ }
+ else
+ msg_Dbg(aout, "version 2 session control unavailable");
- hr = ISimpleAudioVolume_GetMasterVolume(volume, &level);
- if (SUCCEEDED(hr))
- aout_VolumeReport(aout, level);
- else
- msg_Err(aout, "cannot get mute (error 0x%lx)", hr);
+ hr = IAudioSessionManager_QueryInterface(manager,
+ &IID_IAudioSessionManager2, &pv);
+ if (SUCCEEDED(hr))
+ {
+ IAudioSessionManager2 *m2 = pv;
+
+ IAudioSessionManager2_RegisterDuckNotification(m2, siid,
+ &sys->duck);
+ IAudioSessionManager2_Release(m2);
}
else
- msg_Err(aout, "cannot get simple volume (error 0x%lx)", hr);
+ msg_Dbg(aout, "version 2 session management unavailable");
+
+ CoTaskMemFree(siid);
}
else
{
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->device == NULL)
{
- if (volume != NULL && sys->volume >= 0.f)
+ if (volume != NULL)
{
- hr = ISimpleAudioVolume_SetMasterVolume(volume, sys->volume, NULL);
- if (FAILED(hr))
- msg_Err(aout, "cannot set master volume (error 0x%lx)", hr);
+ 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);
+
+ level = sys->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->volume = -1.f;
- }
- if (volume != NULL && sys->mute >= 0)
- {
- hr = ISimpleAudioVolume_SetMute(volume,
- sys->mute ? TRUE : FALSE, NULL);
- if (FAILED(hr))
- msg_Err(aout, "cannot set mute (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->mute >= 0)
+ {
+ mute = sys->mute ? TRUE : FALSE;
+
+ hr = ISimpleAudioVolume_SetMute(volume, mute, NULL);
+ if (FAILED(hr))
+ msg_Err(aout, "cannot set mute (error 0x%lx)", hr);
+ }
sys->mute = -1;
}
SleepConditionVariableCS(&sys->work, &sys->lock, INFINITE);
}
+ LeaveCriticalSection(&sys->lock);
+
+ if (endpoint != NULL)
+ IAudioEndpointVolume_Release(endpoint);
if (manager != NULL)
- {
- /* Deregister session control */
+ { /* Deregister callbacks *without* the lock */
+ hr = IAudioSessionManager_QueryInterface(manager,
+ &IID_IAudioSessionManager2, &pv);
+ if (SUCCEEDED(hr))
+ {
+ IAudioSessionManager2 *m2 = pv;
+
+ IAudioSessionManager2_UnregisterDuckNotification(m2, &sys->duck);
+ IAudioSessionManager2_Release(m2);
+ }
+
if (volume != NULL)
ISimpleAudioVolume_Release(volume);
IAudioSessionManager_Release(manager);
}
+ EnterCriticalSection(&sys->lock);
IMMDevice_Release(sys->dev);
sys->dev = NULL;
return S_OK;
IMMDevice *dev = opaque;
return IMMDevice_Activate(dev, iid, CLSCTX_ALL, actparms, pv);
}
-#else /* VLC_WINSTORE_APP */
-static HRESULT ActivateDevice(void *opaque, REFIID iid, PROPVARIANT *actparms,
- void **restrict pv)
-{
- aout_sys_t *sys = opaque;
-
- (void)iid; (void)actparms;
- *pv = sys->client;
- return S_OK;
-}
-#endif /* VLC_WINSTORE_APP */
static int aout_stream_Start(void *func, va_list ap)
{
static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
{
aout_sys_t *sys = aout->sys;
-#if !VLC_WINSTORE_APP
+
if (sys->dev == NULL)
return -1;
-#endif
aout_stream_t *s = vlc_object_create(aout, sizeof (*s));
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();
{
HRESULT hr;
- sys->module = vlc_module_load(s, "aout stream", NULL, false,
+ /* TODO: Do not overload the "aout" configuration item. */
+ sys->module = vlc_module_load(s, "aout stream", "$aout", false,
aout_stream_Start, s, fmt, &hr);
- if (hr != AUDCLNT_E_DEVICE_INVALIDATED
-#if !VLC_WINSTORE_APP
- || DeviceSelect(aout, NULL)
-#endif
- )
+ if (hr != AUDCLNT_E_DEVICE_INVALIDATED || DeviceSelect(aout, NULL))
break;
}
LeaveMTA();
assert (sys->stream == NULL);
sys->stream = s;
+ aout_GainRequest(aout, sys->gain);
return 0;
}
{
aout_sys_t *sys = aout->sys;
- assert (sys->stream != NULL);
+ assert(sys->stream != NULL);
EnterMTA();
vlc_module_unload(sys->module, aout_stream_Stop, sys->stream);
aout->sys = sys;
sys->stream = NULL;
-#if !VLC_WINSTORE_APP
sys->aout = aout;
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->device = default_device;
+ sys->gain = 1.f;
sys->volume = -1.f;
sys->mute = -1;
InitializeCriticalSection(&sys->lock);
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");
- assert(sys->client != NULL);
-#endif
+
aout->start = Start;
aout->stop = Stop;
aout->time_get = TimeGet;
aout->play = Play;
aout->pause = Pause;
aout->flush = Flush;
-#if !VLC_WINSTORE_APP
aout->volume_set = VolumeSet;
aout->mute_set = MuteSet;
aout->device_select = DeviceSelect;
DeleteCriticalSection(&sys->lock);
free(sys);
return VLC_EGENERIC;
-#else
- return VLC_SUCCESS;
-#endif
}
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
+
EnterCriticalSection(&sys->lock);
sys->device = default_device; /* break out of MMSession() loop */
sys->it = NULL; /* break out of MMThread() loop */
vlc_join(sys->thread, NULL);
DeleteCriticalSection(&sys->lock);
-#else
- free(sys->client);
-#endif
free(sys);
}
set_shortname("MMDevice")
set_description(N_("Windows Multimedia Device output"))
set_capability("audio output", 150)
-#if VLC_WINSTORE_APP
- /* Pointer to the activated AudioClient* */
- add_integer("mmdevice-audioclient", 0x0, NULL, NULL, true);
-#endif
set_category(CAT_AUDIO)
set_subcategory(SUBCAT_AUDIO_AOUT)
- add_shortcut("wasapi")
+ add_shortcut("wasapi", "directsound")
set_callbacks(Open, Close)
vlc_module_end()