# include <config.h>
#endif
-#undef _WIN32_WINNT
-#define _WIN32_WINNT 0x600 /* Windows Vista */
#define INITGUID
#define COBJMACROS
#define CONST_VTABLE
#include <stdlib.h>
+#include <math.h>
#include <assert.h>
#include <audiopolicy.h>
#include <mmdeviceapi.h>
#include <vlc_plugin.h>
#include <vlc_aout.h>
#include <vlc_charset.h>
+#include <vlc_modules.h>
#include "audio_output/mmdevice.h"
+#if (_WIN32_WINNT < 0x600)
+static VOID WINAPI (*InitializeConditionVariable)(PCONDITION_VARIABLE);
+static BOOL WINAPI (*SleepConditionVariableCS)(PCONDITION_VARIABLE,
+ PCRITICAL_SECTION, DWORD);
+static VOID WINAPI (*WakeConditionVariable)(PCONDITION_VARIABLE);
+#define LOOKUP(s) \
+ if (((s) = (void *)GetProcAddress(h, #s)) == NULL) return FALSE
+
+BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID); /* avoid warning */
+BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, LPVOID reserved)
+{
+ (void) dll;
+ (void) reserved;
+
+ switch (reason)
+ {
+ case DLL_PROCESS_ATTACH:
+ {
+ HANDLE h = GetModuleHandle(TEXT("kernel32.dll"));
+ if (unlikely(h == NULL))
+ return FALSE;
+ LOOKUP(InitializeConditionVariable);
+ LOOKUP(SleepConditionVariableCS);
+ LOOKUP(WakeConditionVariable);
+ break;
+ }
+ }
+ return TRUE;
+}
+#endif
+
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 */
- /*TODO: IMMNotificationClient*/
IMMDevice *dev; /**< Selected output device, NULL if none */
+ struct IMMNotificationClient device_events;
struct IAudioSessionEvents session_events;
LONG refs;
{
aout_sys_t *sys = aout->sys;
+ vol = vol * vol * vol; /* ISimpleAudioVolume is tapered linearly. */
EnterCriticalSection(&sys->lock);
sys->volume = vol;
WakeConditionVariable(&sys->work);
msg_Dbg(aout, "simple volume changed: %f, muting %sabled", vol,
mute ? "en" : "dis");
- aout_VolumeReport(aout, vol);
+ aout_VolumeReport(aout, cbrtf(vol));
aout_MuteReport(aout, mute == TRUE);
(void) ctx;
return S_OK;
};
/*** Audio devices ***/
+
+/** Gets the user-readable device name */
+static char *DeviceName(IMMDevice *dev)
+{
+ IPropertyStore *props;
+ char *name = NULL;
+ PROPVARIANT v;
+ HRESULT hr;
+
+ hr = IMMDevice_OpenPropertyStore(dev, STGM_READ, &props);
+ if (FAILED(hr))
+ return NULL;
+
+ PropVariantInit(&v);
+ hr = IPropertyStore_GetValue(props, &PKEY_Device_FriendlyName, &v);
+ if (SUCCEEDED(hr))
+ {
+ name = FromWide(v.pwszVal);
+ PropVariantClear(&v);
+ }
+ IPropertyStore_Release(props);
+ return name;
+}
+
+/** Checks that a device is an output device */
+static bool DeviceIsRender(IMMDevice *dev)
+{
+ void *pv;
+
+ if (FAILED(IMMDevice_QueryInterface(dev, &IID_IMMEndpoint, &pv)))
+ return false;
+
+ IMMEndpoint *ep = pv;
+ EDataFlow flow;
+
+ if (FAILED(IMMEndpoint_GetDataFlow(ep, &flow)))
+ flow = eCapture;
+
+ IMMEndpoint_Release(ep);
+ return flow == eRender;
+}
+
+static HRESULT DeviceUpdated(audio_output_t *aout, LPCWSTR wid)
+{
+ aout_sys_t *sys = aout->sys;
+ HRESULT hr;
+
+ IMMDevice *dev;
+ hr = IMMDeviceEnumerator_GetDevice(sys->it, wid, &dev);
+ if (FAILED(hr))
+ return hr;
+
+ if (!DeviceIsRender(dev))
+ {
+ IMMDevice_Release(dev);
+ return S_OK;
+ }
+
+ char *id = FromWide(wid);
+ if (unlikely(id == NULL))
+ {
+ IMMDevice_Release(dev);
+ return E_OUTOFMEMORY;
+ }
+
+ char *name = DeviceName(dev);
+ IMMDevice_Release(dev);
+
+ aout_HotplugReport(aout, id, (name != NULL) ? name : id);
+ free(name);
+ free(id);
+ return S_OK;
+}
+
+static inline aout_sys_t *vlc_MMNotificationClient_sys(IMMNotificationClient *this)
+{
+ return (void *)(((char *)this) - offsetof(aout_sys_t, device_events));
+}
+
+static STDMETHODIMP
+vlc_MMNotificationClient_QueryInterface(IMMNotificationClient *this,
+ REFIID riid, void **ppv)
+{
+ if (IsEqualIID(riid, &IID_IUnknown)
+ || IsEqualIID(riid, &IID_IMMNotificationClient))
+ {
+ *ppv = this;
+ IUnknown_AddRef(this);
+ return S_OK;
+ }
+ else
+ {
+ *ppv = NULL;
+ return E_NOINTERFACE;
+ }
+}
+
+static STDMETHODIMP_(ULONG)
+vlc_MMNotificationClient_AddRef(IMMNotificationClient *this)
+{
+ aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
+ return InterlockedIncrement(&sys->refs);
+}
+
+static STDMETHODIMP_(ULONG)
+vlc_MMNotificationClient_Release(IMMNotificationClient *this)
+{
+ aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
+ return InterlockedDecrement(&sys->refs);
+}
+
+static STDMETHODIMP
+vlc_MMNotificationClient_OnDefaultDeviceChange(IMMNotificationClient *this,
+ EDataFlow flow, ERole role,
+ LPCWSTR wid)
+{
+ aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
+ audio_output_t *aout = sys->aout;
+
+ if (flow != eRender)
+ return S_OK;
+ if (role != eConsole)
+ return S_OK;
+
+ msg_Dbg(aout, "default device changed: %ls", wid); /* TODO? migrate */
+ return S_OK;
+}
+
+static STDMETHODIMP
+vlc_MMNotificationClient_OnDeviceAdded(IMMNotificationClient *this,
+ LPCWSTR wid)
+{
+ aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
+ audio_output_t *aout = sys->aout;
+
+ msg_Dbg(aout, "device %ls added", wid);
+ return DeviceUpdated(aout, wid);
+}
+
+static STDMETHODIMP
+vlc_MMNotificationClient_OnDeviceRemoved(IMMNotificationClient *this,
+ LPCWSTR wid)
+{
+ aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
+ audio_output_t *aout = sys->aout;
+ char *id = FromWide(wid);
+
+ msg_Dbg(aout, "device %ls removed", wid);
+ if (unlikely(id == NULL))
+ return E_OUTOFMEMORY;
+
+ aout_HotplugReport(aout, id, NULL);
+ free(id);
+ return S_OK;
+}
+
+static STDMETHODIMP
+vlc_MMNotificationClient_OnDeviceStateChanged(IMMNotificationClient *this,
+ LPCWSTR wid, DWORD state)
+{
+ aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
+ audio_output_t *aout = sys->aout;
+
+ /* TODO: show device state / ignore missing devices */
+ msg_Dbg(aout, "device %ls state changed %08lx", wid, state);
+ return S_OK;
+}
+
+static STDMETHODIMP
+vlc_MMNotificationClient_OnDevicePropertyChanged(IMMNotificationClient *this,
+ LPCWSTR wid,
+ const PROPERTYKEY key)
+{
+ aout_sys_t *sys = vlc_MMNotificationClient_sys(this);
+ audio_output_t *aout = sys->aout;
+
+ if (key.pid == PKEY_Device_FriendlyName.pid)
+ {
+ msg_Dbg(aout, "device %ls name changed", wid);
+ return DeviceUpdated(aout, wid);
+ }
+ return S_OK;
+}
+
+static const struct IMMNotificationClientVtbl vlc_MMNotificationClient =
+{
+ vlc_MMNotificationClient_QueryInterface,
+ vlc_MMNotificationClient_AddRef,
+ vlc_MMNotificationClient_Release,
+
+ vlc_MMNotificationClient_OnDeviceStateChanged,
+ vlc_MMNotificationClient_OnDeviceAdded,
+ vlc_MMNotificationClient_OnDeviceRemoved,
+ vlc_MMNotificationClient_OnDefaultDeviceChange,
+ vlc_MMNotificationClient_OnDevicePropertyChanged,
+};
+
static int DevicesEnum(audio_output_t *aout, IMMDeviceEnumerator *it)
{
HRESULT hr;
for (UINT i = 0; i < count; i++)
{
IMMDevice *dev;
- char *id, *name = NULL;
+ char *id, *name;
hr = IMMDeviceCollection_Item(devs, i, &dev);
if (FAILED(hr))
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);
- }
+ name = DeviceName(dev);
IMMDevice_Release(dev);
aout_HotplugReport(aout, id, (name != NULL) ? name : id);
SleepConditionVariableCS(&sys->ready, &sys->lock, INFINITE);
LeaveCriticalSection(&sys->lock);
- if (sys->stream != NULL)
+ if (sys->stream != NULL && sys->dev != NULL)
/* Request restart of stream with the new device */
aout_RestartRequest(aout, AOUT_RESTART_OUTPUT);
return (sys->dev != NULL) ? 0 : -1;
hr = AUDCLNT_E_DEVICE_INVALIDATED;
while (hr == AUDCLNT_E_DEVICE_INVALIDATED)
- { /* Default device selected by policy */
+ { /* Default device selected by policy and with stream routing.
+ * "Do not use eMultimedia" says MSDN. */
msg_Dbg(aout, "using default device");
hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(it, eRender,
eConsole, &sys->dev);
{
audio_output_t *aout = data;
aout_sys_t *sys = aout->sys;
+ IMMDeviceEnumerator *it = sys->it;
EnterMTA();
+ IMMDeviceEnumerator_RegisterEndpointNotificationCallback(it,
+ &sys->device_events);
+ DevicesEnum(aout, it);
+
EnterCriticalSection(&sys->lock);
- while (sys->it != NULL)
- if (FAILED(MMSession(aout, sys->it)))
+ do
+ if (FAILED(MMSession(aout, it)))
SleepConditionVariableCS(&sys->work, &sys->lock, INFINITE);
+ while (sys->it != NULL);
LeaveCriticalSection(&sys->lock);
+
+ IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(it,
+ &sys->device_events);
+ IMMDeviceEnumerator_Release(it);
LeaveMTA();
return NULL;
}
}
#endif /* VLC_WINSTORE_APP */
+static int aout_stream_Start(void *func, va_list ap)
+{
+ aout_stream_start_t start = func;
+ aout_stream_t *s = va_arg(ap, aout_stream_t *);
+ 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);
+ if (*hr == AUDCLNT_E_DEVICE_INVALIDATED)
+ return VLC_ETIMEOUT;
+ return SUCCEEDED(*hr) ? VLC_SUCCESS : VLC_EGENERIC;
+}
+
+static void aout_stream_Stop(void *func, va_list ap)
+{
+ aout_stream_stop_t stop = func;
+ aout_stream_t *s = va_arg(ap, aout_stream_t *);
+
+ stop(s);
+}
+
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 !VLC_WINSTORE_APP
- /* Open the default device if required (to deal with restarts) */
- if (sys->dev == NULL && FAILED(DeviceSelect(aout, NULL)))
+ if (sys->dev == NULL)
return -1;
#endif
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();
- hr = aout_stream_Start(s, fmt, &GUID_VLC_AUD_OUT);
- if (SUCCEEDED(hr))
- sys->stream = s;
- else
- vlc_object_release(s);
+ for (;;)
+ {
+ HRESULT hr;
+
+ sys->module = vlc_module_load(s, "aout stream", NULL, false,
+ aout_stream_Start, s, fmt, &hr);
+ if (hr != AUDCLNT_E_DEVICE_INVALIDATED
+#if !VLC_WINSTORE_APP
+ || DeviceSelect(aout, NULL)
+#endif
+ )
+ break;
+ }
LeaveMTA();
- return vlc_FromHR(aout, hr);
+ if (sys->module == NULL)
+ {
+ vlc_object_release(s);
+ return -1;
+ }
+
+ assert (sys->stream == NULL);
+ sys->stream = s;
+ return 0;
}
static void Stop(audio_output_t *aout)
assert (sys->stream != NULL);
EnterMTA();
- aout_stream_Stop(sys->stream);
+ vlc_module_unload(sys->module, aout_stream_Stop, sys->stream);
LeaveMTA();
vlc_object_release(sys->stream);
{
audio_output_t *aout = (audio_output_t *)obj;
- if (!aout->b_force && var_InheritBool(aout, "spdif"))
- /* Fallback to other plugin until pass-through is implemented */
- return VLC_EGENERIC;
-
aout_sys_t *sys = malloc(sizeof (*sys));
if (unlikely(sys == NULL))
return VLC_ENOMEM;
goto error;
}
- DeviceSelect(aout, NULL);
+ EnterCriticalSection(&sys->lock);
+ while (sys->device != NULL)
+ 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");
aout->volume_set = VolumeSet;
aout->mute_set = MuteSet;
aout->device_select = DeviceSelect;
- EnterMTA();
- DevicesEnum(aout, sys->it);
- LeaveMTA();
return VLC_SUCCESS;
error:
audio_output_t *aout = (audio_output_t *)obj;
aout_sys_t *sys = aout->sys;
#if !VLC_WINSTORE_APP
- IMMDeviceEnumerator *it = sys->it;
-
- EnterMTA(); /* Enter MTA before thread leaves MTA */
EnterCriticalSection(&sys->lock);
sys->device = default_device; /* break out of MMSession() loop */
sys->it = NULL; /* break out of MMThread() loop */
WakeConditionVariable(&sys->work);
LeaveCriticalSection(&sys->lock);
- IMMDeviceEnumerator_Release(it);
- LeaveMTA();
-
vlc_join(sys->thread, NULL);
DeleteCriticalSection(&sys->lock);
#else
vlc_module_begin()
set_shortname("MMDevice")
set_description(N_("Windows Multimedia Device output"))
- set_capability("audio output", /*150*/0)
+ set_capability("audio output", 150)
#if VLC_WINSTORE_APP
/* Pointer to the activated AudioClient* */
add_integer("mmdevice-audioclient", 0x0, NULL, NULL, true);