Commit 771335af authored by Rémi Denis-Courmont's avatar Rémi Denis-Courmont

wasapi: split MMDevice and session code from output

- Run the volume/mute handling on the COM thread,
  removes remaining volume hacks.
- Spawn session manager from device rather than client (fixes #7809)
- Insulate session management from output API (refs #7394)
parent 6c5d040e
...@@ -54,11 +54,11 @@ libjack_plugin_la_LIBADD = $(AM_LIBADD) $(JACK_LIBS) $(LIBM) ...@@ -54,11 +54,11 @@ libjack_plugin_la_LIBADD = $(AM_LIBADD) $(JACK_LIBS) $(LIBM)
EXTRA_LTLIBRARIES += libjack_plugin.la EXTRA_LTLIBRARIES += libjack_plugin.la
libvlc_LTLIBRARIES += $(LTLIBjack) libvlc_LTLIBRARIES += $(LTLIBjack)
libwasapi_plugin_la_SOURCES = wasapi.c libmmdevice_plugin_la_SOURCES = mmdevice.c mmdevice.h wasapi.c
libwasapi_plugin_la_CFLAGS = $(AM_CFLAGS) libmmdevice_plugin_la_CFLAGS = $(AM_CFLAGS)
libwasapi_plugin_la_LIBADD = $(AM_LIBADD) -lole32 -lksuser libmmdevice_plugin_la_LIBADD = $(AM_LIBADD) -lole32 -lksuser
if HAVE_WASAPI if HAVE_WASAPI
libvlc_LTLIBRARIES += libwasapi_plugin.la libvlc_LTLIBRARIES += libmmdevice_plugin.la
endif endif
libdirectsound_plugin_la_SOURCES = directx.c windows_audio_common.h packet.c libdirectsound_plugin_la_SOURCES = directx.c windows_audio_common.h packet.c
......
/*****************************************************************************
* mmdevice.c : Windows Multimedia Device API audio output plugin for VLC
*****************************************************************************
* Copyright (C) 2012 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.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x600 /* Windows Vista */
#define INITGUID
#define COBJMACROS
#define CONST_VTABLE
#include <stdlib.h>
#include <assert.h>
#include <audiopolicy.h>
#include <mmdeviceapi.h>
DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd,
0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
#include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_aout.h>
#include <vlc_charset.h>
#include "mmdevice.h"
DEFINE_GUID (GUID_VLC_AUD_OUT, 0x4533f59d, 0x59ee, 0x00c6,
0xad, 0xb2, 0xc6, 0x8b, 0x50, 0x1a, 0x66, 0x55);
static int TryEnter(vlc_object_t *obj)
{
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if (unlikely(FAILED(hr)))
{
msg_Err (obj, "cannot initialize COM (error 0x%lx)", hr);
return -1;
}
return 0;
}
#define TryEnter(o) TryEnter(VLC_OBJECT(o))
static void Enter(void)
{
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if (unlikely(FAILED(hr)))
abort();
}
static void Leave(void)
{
CoUninitialize();
}
struct aout_sys_t
{
audio_output_t *aout;
aout_api_t *api; /**< Audio output back-end API */
IMMDeviceEnumerator *it;
IMMDevice *dev; /**< Selected output device */
IAudioSessionManager *manager; /**< Session for the output device */
/*TODO: IMMNotificationClient*/
struct IAudioSessionEvents session_events;
LONG refs;
CRITICAL_SECTION lock;
CONDITION_VARIABLE request_wait;
CONDITION_VARIABLE reply_wait;
bool active; /**< Flag to request thread to keep running */
bool running; /**< Whether the thread is still running */
int8_t mute; /**< Requested mute state or negative value */
float volume; /**< Requested volume or negative value */
};
static int vlc_FromHR(audio_output_t *aout, HRESULT hr)
{
/* Restart on unplug */
if (unlikely(hr == AUDCLNT_E_DEVICE_INVALIDATED))
var_TriggerCallback(aout, "audio-device");
return SUCCEEDED(hr) ? 0 : -1;
}
/*** VLC audio output callbacks ***/
static int TimeGet(audio_output_t *aout, mtime_t *restrict delay)
{
aout_sys_t *sys = aout->sys;
HRESULT hr = aout_api_TimeGet(sys->api, delay);
return SUCCEEDED(hr) ? 0 : -1;
}
static void Play(audio_output_t *aout, block_t *block)
{
aout_sys_t *sys = aout->sys;
HRESULT hr = aout_api_Play(sys->api, block);
vlc_FromHR(aout, hr);
}
static void Pause(audio_output_t *aout, bool paused, mtime_t date)
{
aout_sys_t *sys = aout->sys;
HRESULT hr = aout_api_Pause(sys->api, paused);
vlc_FromHR(aout, hr);
(void) date;
}
static void Flush(audio_output_t *aout, bool wait)
{
aout_sys_t *sys = aout->sys;
if (wait)
return; /* Drain not implemented */
aout_api_Flush(sys->api);
}
static int VolumeSet(audio_output_t *aout, float vol)
{
aout_sys_t *sys = aout->sys;
EnterCriticalSection(&sys->lock);
sys->volume = vol;
LeaveCriticalSection(&sys->lock);
WakeConditionVariable(&sys->request_wait);
return 0;
}
static int MuteSet(audio_output_t *aout, bool mute)
{
aout_sys_t *sys = aout->sys;
EnterCriticalSection(&sys->lock);
sys->mute = mute;
LeaveCriticalSection(&sys->lock);
WakeConditionVariable(&sys->request_wait);
return 0;
}
/*** Audio devices ***/
static int DeviceChanged(vlc_object_t *obj, const char *varname,
vlc_value_t prev, vlc_value_t cur, void *data)
{
/* FIXME: This does not work. sys->dev, sys->manager and sys->api must be
* recreated. Those pointers are protected by the aout lock, which
* serializes accesses to the audio_output_t. Unfortunately,
* aout lock cannot be taken from a variable callback.
* Solution: add device_change callback to audio_output_t. */
aout_ChannelsRestart(obj, varname, prev, cur, data);
return VLC_SUCCESS;
}
static void GetDevices(vlc_object_t *obj, IMMDeviceEnumerator *it)
{
HRESULT hr;
vlc_value_t val, text;
var_Create (obj, "audio-device", VLC_VAR_STRING | VLC_VAR_HASCHOICE);
text.psz_string = _("Audio Device");
var_Change (obj, "audio-device", VLC_VAR_SETTEXT, &text, NULL);
/* TODO: implement IMMNotificationClient for hotplug devices */
IMMDeviceCollection *devs;
hr = IMMDeviceEnumerator_EnumAudioEndpoints(it, eRender,
DEVICE_STATE_ACTIVE, &devs);
if (FAILED(hr))
{
msg_Warn (obj, "cannot enumerate audio endpoints (error 0x%lx)", hr);
return;
}
UINT n;
hr = IMMDeviceCollection_GetCount(devs, &n);
if (FAILED(hr))
{
msg_Warn (obj, "cannot count audio endpoints (error 0x%lx)", hr);
n = 0;
}
else
msg_Dbg(obj, "Available Windows Audio devices:");
while (n > 0)
{
IMMDevice *dev;
hr = IMMDeviceCollection_Item(devs, --n, &dev);
if (FAILED(hr))
continue;
/* Unique device ID */
LPWSTR devid;
hr = IMMDevice_GetId(dev, &devid);
if (FAILED(hr))
{
IMMDevice_Release(dev);
continue;
}
val.psz_string = FromWide(devid);
CoTaskMemFree(devid);
text.psz_string = val.psz_string;
/* 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))
text.psz_string = FromWide(v.pwszVal);
PropVariantClear(&v);
IPropertyStore_Release(props);
}
IMMDevice_Release(dev);
msg_Dbg(obj, "%s (%s)", val.psz_string, text.psz_string);
var_Change(obj, "audio-device", VLC_VAR_ADDCHOICE, &val, &text);
if (likely(text.psz_string != val.psz_string))
free(text.psz_string);
free(val.psz_string);
}
IMMDeviceCollection_Release(devs);
}
/*** Audio session events ***/
static inline aout_sys_t *vlc_AudioSessionEvents_sys(IAudioSessionEvents *this)
{
return (void *)(((char *)this) - offsetof(aout_sys_t, 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 = vlc_AudioSessionEvents_sys(this);
return InterlockedIncrement(&sys->refs);
}
static STDMETHODIMP_(ULONG)
vlc_AudioSessionEvents_Release(IAudioSessionEvents *this)
{
aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
return InterlockedDecrement(&sys->refs);
}
static STDMETHODIMP
vlc_AudioSessionEvents_OnDisplayNameChanged(IAudioSessionEvents *this,
LPCWSTR wname, LPCGUID ctx)
{
aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
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 = vlc_AudioSessionEvents_sys(this);
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, WINBOOL mute,
LPCGUID ctx)
{
aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
audio_output_t *aout = sys->aout;
msg_Dbg(aout, "simple volume changed: %f, muting %sabled", vol,
mute ? "en" : "dis");
aout_VolumeReport(aout, vol);
aout_MuteReport(aout, mute == TRUE);
(void) ctx;
return S_OK;
}
static STDMETHODIMP
vlc_AudioSessionEvents_OnChannelVolumeChanged(IAudioSessionEvents *this,
DWORD count, float *vols,
DWORD changed, LPCGUID ctx)
{
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]);
(void) ctx;
return S_OK;
}
static STDMETHODIMP
vlc_AudioSessionEvents_OnGroupingParamChanged(IAudioSessionEvents *this,
LPCGUID param, LPCGUID ctx)
{
aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
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 = vlc_AudioSessionEvents_sys(this);
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 = vlc_AudioSessionEvents_sys(this);
audio_output_t *aout = sys->aout;
msg_Dbg(aout, "session disconnected: reason %d", reason);
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,
};
/*** Initialization / deinitialization **/
static wchar_t *var_InheritWide(vlc_object_t *obj, const char *name)
{
char *v8 = var_InheritString(obj, name);
if (v8 == NULL)
return NULL;
wchar_t *v16 = ToWide(v8);
free(v8);
return v16;
}
#define var_InheritWide(o,n) var_InheritWide(VLC_OBJECT(o),n)
/** MMDevice audio output thread.
* A number of Core Audio Interfaces must be deleted from the same thread than
* they were created from... */
static void MMThread(void *data)
{
audio_output_t *aout = data;
aout_sys_t *sys = aout->sys;
IAudioSessionControl *control;
ISimpleAudioVolume *volume;
HRESULT hr;
Enter();
/* Instantiate thread-invariable interfaces */
hr = IAudioSessionManager_GetAudioSessionControl(sys->manager,
&GUID_VLC_AUD_OUT, 0,
&control);
if (FAILED(hr))
msg_Warn(aout, "cannot get session control (error 0x%lx)", hr);
else
{
wchar_t *ua = var_InheritWide(aout, "user-agent");
IAudioSessionControl_SetDisplayName(control, ua, NULL);
free(ua);
sys->session_events.lpVtbl = &vlc_AudioSessionEvents;
IAudioSessionControl_RegisterAudioSessionNotification(control,
&sys->session_events);
}
hr = IAudioSessionManager_GetSimpleAudioVolume(sys->manager,
&GUID_VLC_AUD_OUT, FALSE,
&volume);
if (FAILED(hr))
msg_Err(aout, "cannot get simple volume (error 0x%lx)", hr);
EnterCriticalSection(&sys->lock);
while (sys->active)
{
/* Update volume */
if (sys->volume >= 0.f)
{
hr = ISimpleAudioVolume_SetMasterVolume(volume, sys->volume, NULL);
if (FAILED(hr))
msg_Err(aout, "cannot set volume (error 0x%lx)", hr);
sys->volume = -1.f;
}
/* Update mute state */
if (sys->mute >= 0)
{
hr = ISimpleAudioVolume_SetMute(volume, sys->mute, NULL);
if (FAILED(hr))
msg_Err(aout, "cannot set mute (error 0x%lx)", hr);
sys->mute = -1;
}
SleepConditionVariableCS(&sys->request_wait, &sys->lock, INFINITE);
}
LeaveCriticalSection(&sys->lock);
if (volume != NULL)
ISimpleAudioVolume_Release(volume);
if (control != NULL)
{
IAudioSessionControl_UnregisterAudioSessionNotification(control,
&sys->session_events);
IAudioSessionControl_Release(control);
}
EnterCriticalSection(&sys->lock);
sys->running = false;
LeaveCriticalSection(&sys->lock);
WakeConditionVariable(&sys->reply_wait);
}
static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
{
aout_sys_t *sys = aout->sys;
assert (sys->api == NULL);
if (sys->dev == NULL)
return -1;
sys->api = aout_api_Start(aout, fmt, sys->dev, &GUID_VLC_AUD_OUT);
return (sys->api != NULL) ? 0 : -1;
}
static void Stop(audio_output_t *aout)
{
aout_sys_t *sys = aout->sys;
assert (sys->api != NULL);
aout_api_Stop(sys->api);
sys->api = NULL;
}
static int Open(vlc_object_t *obj)
{
audio_output_t *aout = (audio_output_t *)obj;
void *pv;
HRESULT hr;
if (!aout->b_force && var_InheritBool(aout, "spdif"))
/* Fallback to other plugin until pass-through is implemented */
return VLC_EGENERIC;
/* Initialize MMDevice API */
if (TryEnter(aout))
return VLC_EGENERIC;
aout_sys_t *sys = malloc(sizeof (*sys));
if (unlikely(sys == NULL))
return VLC_ENOMEM;
sys->aout = aout;
sys->api = NULL;
sys->it = NULL;
sys->dev = NULL;
sys->manager = NULL;
sys->refs = 1;
InitializeCriticalSection(&sys->lock);
InitializeConditionVariable(&sys->request_wait);
InitializeConditionVariable(&sys->reply_wait);
sys->active = true;
sys->running = true;
sys->volume = -1.f;
sys->mute = -1;
hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
&IID_IMMDeviceEnumerator, &pv);
if (FAILED(hr))
{
msg_Dbg(aout, "cannot create device enumerator (error 0x%lx)", hr);
Leave();
goto error;
}
sys->it = pv;
GetDevices(obj, sys->it);
/* Get audio device according to policy */
wchar_t *devid = var_InheritWide(aout, "audio-device");
if (devid != NULL)
{
msg_Dbg (aout, "using selected device %ls", devid);
hr = IMMDeviceEnumerator_GetDevice (sys->it, devid, &sys->dev);
if (FAILED(hr))
msg_Err(aout, "cannot get device %ls (error 0x%lx)", devid, hr);
free (devid);
}
if (sys->dev == NULL)
{
msg_Dbg (aout, "using default device");
hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(sys->it, eRender,
eConsole, &sys->dev);
if (FAILED(hr))
msg_Err(aout, "cannot get default device (error 0x%lx)", hr);
}
if (sys->dev == NULL)
/* TODO: VLC should be able to start without devices, so long as
* a device becomes available before Start() is called. */
goto error;
hr = IMMDevice_Activate(sys->dev, &IID_IAudioSessionManager,
CLSCTX_ALL, NULL, &pv);
if (FAILED(hr))
msg_Err(aout, "cannot activate session manager (error 0x%lx)", hr);
else
sys->manager = pv;
/* Note: thread handle released by CRT, ignore it. */
if (_beginthread(MMThread, 0, aout) == (uintptr_t)-1)
goto error;
Leave();
aout->sys = sys;
aout->start = Start;
aout->stop = Stop;
aout->time_get = TimeGet;
aout->play = Play;
aout->pause = Pause;
aout->flush = Flush;
aout->volume_set = VolumeSet;
aout->mute_set = MuteSet;
var_AddCallback (aout, "audio-device", DeviceChanged, NULL);
return VLC_SUCCESS;
error:
if (sys->manager != NULL)
IAudioSessionManager_Release(sys->manager);
if (sys->dev != NULL)
IMMDevice_Release(sys->dev);
if (sys->it != NULL)
IMMDeviceEnumerator_Release(sys->it);
DeleteCriticalSection(&sys->lock);
free(sys);
return VLC_EGENERIC;
}
static void Close(vlc_object_t *obj)
{
audio_output_t *aout = (audio_output_t *)obj;
aout_sys_t *sys = aout->sys;
EnterCriticalSection(&sys->lock);
sys->active = false;
WakeConditionVariable(&sys->request_wait);
while (sys->running)
SleepConditionVariableCS(&sys->reply_wait, &sys->lock, INFINITE);
LeaveCriticalSection(&sys->lock);
var_DelCallback (aout, "audio-device", DeviceChanged, NULL);
var_Destroy (aout, "audio-device");
Enter();
if (sys->manager != NULL)
IAudioSessionManager_Release(sys->manager);
IMMDevice_Release(sys->dev);
IMMDeviceEnumerator_Release(sys->it);
Leave();
free(sys);
}
vlc_module_begin()
set_shortname("MMDevice")
set_description(N_("Windows Multimedia Device output"))
set_capability("audio output", 150)
set_category(CAT_AUDIO)
set_subcategory(SUBCAT_AUDIO_AOUT)
add_shortcut("wasapi")
set_callbacks(Open, Close)
vlc_module_end()
/*****************************************************************************
* mmdevice.h : Windows Multimedia Device API audio output plugin for VLC
*****************************************************************************
* Copyright (C) 2012 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.
*****************************************************************************/
#ifndef VLC_AOUT_MMDEVICE_H
# define VLC_AOUT_MMDEVICE_H 1
typedef struct aout_api aout_api_t;
/**
* Audio output simplified API for Windows
*/
struct aout_api
{
VLC_COMMON_MEMBERS
void *sys;
HRESULT (*time_get)(aout_api_t *, mtime_t *);
HRESULT (*play)(aout_api_t *, block_t *);
HRESULT (*pause)(aout_api_t *, bool);
HRESULT (*flush)(aout_api_t *);
};
/**
* Creates an audio output stream on a given Windows multimedia device.
* \param parent parent VLC object
* \param fmt audio output sample format [IN/OUT]
* \param dev audio output device
* \param sid audio output session GUID [IN]
*/
aout_api_t *aout_api_Start(vlc_object_t *parent, audio_sample_format_t *fmt,
IMMDevice *dev, const GUID *sid);
#define aout_api_Start(o,f,d,s) aout_api_Start(VLC_OBJECT(o),f,d,s)
/**
* Destroys an audio output stream.
*/
void aout_api_Stop(aout_api_t *);
static inline HRESULT aout_api_TimeGet(aout_api_t *api, mtime_t *delay)
{
return (api->time_get)(api, delay);
}
static inline HRESULT aout_api_Play(aout_api_t *api, block_t *block)
{
return (api->play)(api, block);
}
static inline HRESULT aout_api_Pause(aout_api_t *api, bool paused)
{
return (api->pause)(api, paused);
}
static inline HRESULT aout_api_Flush(aout_api_t *api)
{
return (api->flush)(api);
}
#endif
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
*****************************************************************************/ *****************************************************************************/
#ifdef HAVE_CONFIG_H #ifdef HAVE_CONFIG_H
# include "config.h" # include <config.h>
#endif #endif
#define INITGUID #define INITGUID
...@@ -29,32 +29,11 @@ ...@@ -29,32 +29,11 @@
#include <stdlib.h> #include <stdlib.h>
#include <assert.h> #include <assert.h>
#include <audioclient.h> #include <audioclient.h>
#include <audiopolicy.h>
#include <mmdeviceapi.h> #include <mmdeviceapi.h>
#include <vlc_common.h> #include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_aout.h> #include <vlc_aout.h>
#include <vlc_charset.h> #include "mmdevice.h"
DEFINE_GUID (GUID_VLC_AUD_OUT, 0x4533f59d, 0x59ee, 0x00c6,
0xad, 0xb2, 0xc6, 0x8b, 0x50, 0x1a, 0x66, 0x55);
DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd,
0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
static int Open(vlc_object_t *);
static void Close(vlc_object_t *);
vlc_module_begin()
set_shortname("WASAPI")
set_description(N_("Windows Audio Session output") )
set_capability("audio output", 150)
set_category(CAT_AUDIO)
set_subcategory(SUBCAT_AUDIO_AOUT)
add_shortcut("was", "audioclient")
set_callbacks(Open, Close)
vlc_module_end()
static LARGE_INTEGER freq; /* performance counters frequency */ static LARGE_INTEGER freq; /* performance counters frequency */
...@@ -86,18 +65,6 @@ static UINT64 GetQPC(void) ...@@ -86,18 +65,6 @@ static UINT64 GetQPC(void)
return (d.quot * 10000000) + ((d.rem * 10000000) / freq.QuadPart); return (d.quot * 10000000) + ((d.rem * 10000000) / freq.QuadPart);
} }
static int TryEnter(vlc_object_t *obj)
{
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if (unlikely(FAILED(hr)))
{
msg_Err (obj, "cannot initialize COM (error 0x%lx)", hr);
return -1;
}
return 0;
}
#define TryEnter(o) TryEnter(VLC_OBJECT(o))
static void Enter(void) static void Enter(void)
{ {
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
...@@ -110,100 +77,87 @@ static void Leave(void) ...@@ -110,100 +77,87 @@ static void Leave(void)
CoUninitialize(); CoUninitialize();
} }
struct aout_sys_t typedef struct aout_api_sys
{ {
audio_output_t *aout;
IMMDeviceEnumerator *it;
IAudioClient *client; IAudioClient *client;
IAudioRenderClient *render;
IAudioClock *clock;
IAudioSessionControl *control;
struct IAudioSessionEvents events;
LONG refs;
uint8_t chans_table[AOUT_CHAN_MAX]; uint8_t chans_table[AOUT_CHAN_MAX];
uint8_t chans_to_reorder; uint8_t chans_to_reorder;
uint8_t bits; /**< Bits per sample */ uint8_t bits; /**< Bits per sample */
unsigned rate; /**< Sample rate */ unsigned rate; /**< Sample rate */
unsigned bytes_per_frame; unsigned bytes_per_frame;
UINT32 written; /**< Frames written to the buffer */ UINT32 written; /**< Frames written to the buffer */
UINT32 frames; /**< Total buffer size (frames) */ UINT32 frames; /**< Total buffer size (frames) */
} aout_api_sys_t;
float volume_hack; /**< Deferred volume request */
int mute_hack; /**< Deferred mute request */
HANDLE ready; /**< Semaphore from MTA thread */
HANDLE done; /**< Semaphore to MTA thread */
};
/*** VLC audio output callbacks ***/ /*** VLC audio output callbacks ***/
static int TimeGet(audio_output_t *aout, mtime_t *restrict delay) static HRESULT TimeGet(aout_api_t *api, mtime_t *restrict delay)
{ {
aout_sys_t *sys = aout->sys; aout_api_sys_t *sys = api->sys;
void *pv;
UINT64 pos, qpcpos; UINT64 pos, qpcpos;
HRESULT hr; HRESULT hr;
if (sys->clock == NULL)
return -1;
Enter(); Enter();
hr = IAudioClock_GetPosition(sys->clock, &pos, &qpcpos); hr = IAudioClient_GetService(sys->client, &IID_IAudioClock, &pv);
Leave(); if (SUCCEEDED(hr))
if (FAILED(hr))
{ {
msg_Err(aout, "cannot get position (error 0x%lx)", hr); IAudioClock *clock = pv;
return -1;
}
if (pos == 0) hr = IAudioClock_GetPosition(clock, &pos, &qpcpos);
{ if (FAILED(hr))
*delay = sys->written * CLOCK_FREQ / sys->rate; msg_Err(api, "cannot get position (error 0x%lx)", hr);
msg_Dbg(aout, "extrapolating position: still propagating buffers"); IAudioClock_Release(clock);
return 0;
} }
else
msg_Err(api, "cannot get clock (error 0x%lx)", hr);
Leave();
if (SUCCEEDED(hr))
{
if (pos != 0)
{
*delay = ((GetQPC() - qpcpos) / (10000000 / CLOCK_FREQ)); *delay = ((GetQPC() - qpcpos) / (10000000 / CLOCK_FREQ));
static_assert((10000000 % CLOCK_FREQ) == 0, "Frequency conversion broken"); static_assert((10000000 % CLOCK_FREQ) == 0,
return 0; "Frequency conversion broken");
} }
else
static void CheckVolumeHack(audio_output_t *aout) {
{ *delay = sys->written * CLOCK_FREQ / sys->rate;
aout_sys_t *sys = aout->sys; msg_Dbg(api, "extrapolating position: still propagating buffers");
if (unlikely(sys->volume_hack >= 0.f))
{ /* Apply volume now, if it failed earlier */
aout->volume_set(aout, sys->volume_hack);
sys->volume_hack = -1.f;
} }
if (unlikely(sys->mute_hack >= 0))
{ /* Apply volume now, if it failed earlier */
aout->mute_set(aout, sys->mute_hack);
sys->mute_hack = -1;
} }
return hr;
} }
static void Play(audio_output_t *aout, block_t *block) static HRESULT Play(aout_api_t *api, block_t *block)
{ {
aout_sys_t *sys = aout->sys; aout_api_sys_t *sys = api->sys;
HRESULT hr = S_OK; void *pv;
HRESULT hr;
CheckVolumeHack(aout);
if (sys->chans_to_reorder) if (sys->chans_to_reorder)
aout_ChannelReorder(block->p_buffer, block->i_buffer, aout_ChannelReorder(block->p_buffer, block->i_buffer,
sys->chans_to_reorder, sys->chans_table, sys->bits); sys->chans_to_reorder, sys->chans_table, sys->bits);
Enter(); Enter();
hr = IAudioClient_GetService(sys->client, &IID_IAudioRenderClient, &pv);
if (FAILED(hr))
{
msg_Err(api, "cannot get render client (error 0x%lx)", hr);
goto out;
}
IAudioRenderClient *render = pv;
for (;;) for (;;)
{ {
UINT32 frames; UINT32 frames;
hr = IAudioClient_GetCurrentPadding(sys->client, &frames); hr = IAudioClient_GetCurrentPadding(sys->client, &frames);
if (FAILED(hr)) if (FAILED(hr))
{ {
msg_Err(aout, "cannot get current padding (error 0x%lx)", hr); msg_Err(api, "cannot get current padding (error 0x%lx)", hr);
break; break;
} }
...@@ -213,20 +167,20 @@ static void Play(audio_output_t *aout, block_t *block) ...@@ -213,20 +167,20 @@ static void Play(audio_output_t *aout, block_t *block)
frames = block->i_nb_samples; frames = block->i_nb_samples;
BYTE *dst; BYTE *dst;
hr = IAudioRenderClient_GetBuffer(sys->render, frames, &dst); hr = IAudioRenderClient_GetBuffer(render, frames, &dst);
if (FAILED(hr)) if (FAILED(hr))
{ {
msg_Err(aout, "cannot get buffer (error 0x%lx)", hr); msg_Err(api, "cannot get buffer (error 0x%lx)", hr);
break; break;
} }
const size_t copy = frames * sys->bytes_per_frame; const size_t copy = frames * sys->bytes_per_frame;
memcpy(dst, block->p_buffer, copy); memcpy(dst, block->p_buffer, copy);
hr = IAudioRenderClient_ReleaseBuffer(sys->render, frames, 0); hr = IAudioRenderClient_ReleaseBuffer(render, frames, 0);
if (FAILED(hr)) if (FAILED(hr))
{ {
msg_Err(aout, "cannot release buffer (error 0x%lx)", hr); msg_Err(api, "cannot release buffer (error 0x%lx)", hr);
break; break;
} }
IAudioClient_Start(sys->client); IAudioClient_Start(sys->client);
...@@ -242,339 +196,48 @@ static void Play(audio_output_t *aout, block_t *block) ...@@ -242,339 +196,48 @@ static void Play(audio_output_t *aout, block_t *block)
msleep(AOUT_MIN_PREPARE_TIME msleep(AOUT_MIN_PREPARE_TIME
+ block->i_nb_samples * CLOCK_FREQ / sys->rate); + block->i_nb_samples * CLOCK_FREQ / sys->rate);
} }
IAudioRenderClient_Release(render);
out:
Leave(); Leave();
block_Release(block); block_Release(block);
/* Restart on unplug */ return hr;
if (unlikely(hr == AUDCLNT_E_DEVICE_INVALIDATED))
var_TriggerCallback(aout, "audio-device");
} }
static void Pause(audio_output_t *aout, bool paused, mtime_t date) static HRESULT Pause(aout_api_t *api, bool paused)
{ {
aout_sys_t *sys = aout->sys; aout_api_sys_t *sys = api->sys;
HRESULT hr; HRESULT hr;
CheckVolumeHack(aout);
Enter(); Enter();
if (paused) if (paused)
hr = IAudioClient_Stop(sys->client); hr = IAudioClient_Stop(sys->client);
else else
hr = IAudioClient_Start(sys->client); hr = IAudioClient_Start(sys->client);
if (FAILED(hr)) if (FAILED(hr))
msg_Warn(aout, "cannot %s stream (error 0x%lx)", msg_Warn(api, "cannot %s stream (error 0x%lx)",
paused ? "stop" : "start", hr); paused ? "stop" : "start", hr);
Leave(); Leave();
return hr;
(void) date;
} }
static void Flush(audio_output_t *aout, bool wait) static HRESULT Flush(aout_api_t *api)
{ {
aout_sys_t *sys = aout->sys; aout_api_sys_t *sys = api->sys;
HRESULT hr; HRESULT hr;
CheckVolumeHack(aout);
if (wait)
return; /* Drain not implemented */
Enter(); Enter();
IAudioClient_Stop(sys->client); IAudioClient_Stop(sys->client);
hr = IAudioClient_Reset(sys->client); hr = IAudioClient_Reset(sys->client);
Leave(); Leave();
if (FAILED(hr)) if (FAILED(hr))
msg_Warn(aout, "cannot reset stream (error 0x%lx)", hr); msg_Warn(api, "cannot reset stream (error 0x%lx)", hr);
else else
sys->written = 0; sys->written = 0;
return hr;
} }
static int SimpleVolumeSet(audio_output_t *aout, float vol)
{
aout_sys_t *sys = aout->sys;
ISimpleAudioVolume *simple;
HRESULT hr;
if (TryEnter(aout))
return -1;
hr = IAudioClient_GetService(sys->client, &IID_ISimpleAudioVolume,
(void **)&simple);
if (SUCCEEDED(hr))
{
hr = ISimpleAudioVolume_SetMasterVolume(simple, vol, NULL);
ISimpleAudioVolume_Release(simple);
}
Leave();
if (FAILED(hr))
{
msg_Err(aout, "cannot set volume (error 0x%lx)", hr);
sys->volume_hack = vol;
return -1;
}
sys->volume_hack = -1.f;
return 0;
}
static int SimpleMuteSet(audio_output_t *aout, bool mute)
{
aout_sys_t *sys = aout->sys;
ISimpleAudioVolume *simple;
HRESULT hr;
if (TryEnter(aout))
return -1;
hr = IAudioClient_GetService(sys->client, &IID_ISimpleAudioVolume,
(void **)&simple);
if (SUCCEEDED(hr))
{
hr = ISimpleAudioVolume_SetMute(simple, mute, NULL);
ISimpleAudioVolume_Release(simple);
}
Leave();
if (FAILED(hr))
{
msg_Err(aout, "cannot set mute (error 0x%lx)", hr);
sys->mute_hack = mute;
return -1;
}
sys->mute_hack = -1;
return 0;
}
/*** Audio devices ***/
static int DeviceChanged(vlc_object_t *obj, const char *varname,
vlc_value_t prev, vlc_value_t cur, void *data)
{
aout_ChannelsRestart(obj, varname, prev, cur, data);
if (!var_Type (obj, "wasapi-audio-device"))
var_Create (obj, "wasapi-audio-device", VLC_VAR_STRING);
var_SetString (obj, "wasapi-audio-device", cur.psz_string);
return VLC_SUCCESS;
}
static void GetDevices(vlc_object_t *obj, IMMDeviceEnumerator *it)
{
HRESULT hr;
vlc_value_t val, text;
var_Create (obj, "audio-device", VLC_VAR_STRING | VLC_VAR_HASCHOICE);
text.psz_string = _("Audio Device");
var_Change (obj, "audio-device", VLC_VAR_SETTEXT, &text, NULL);
IMMDeviceCollection *devs;
hr = IMMDeviceEnumerator_EnumAudioEndpoints(it, eRender,
DEVICE_STATE_ACTIVE, &devs);
if (FAILED(hr))
{
msg_Warn (obj, "cannot enumerate audio endpoints (error 0x%lx)", hr);
return;
}
UINT n;
hr = IMMDeviceCollection_GetCount(devs, &n);
if (FAILED(hr))
{
msg_Warn (obj, "cannot count audio endpoints (error 0x%lx)", hr);
n = 0;
}
else
msg_Dbg(obj, "Available Windows Audio devices:");
while (n > 0)
{
IMMDevice *dev;
hr = IMMDeviceCollection_Item(devs, --n, &dev);
if (FAILED(hr))
continue;
/* Unique device ID */
LPWSTR devid;
hr = IMMDevice_GetId(dev, &devid);
if (FAILED(hr))
{
IMMDevice_Release(dev);
continue;
}
val.psz_string = FromWide(devid);
CoTaskMemFree(devid);
text.psz_string = val.psz_string;
/* 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))
text.psz_string = FromWide(v.pwszVal);
PropVariantClear(&v);
IPropertyStore_Release(props);
}
IMMDevice_Release(dev);
msg_Dbg(obj, "%s (%s)", val.psz_string, text.psz_string);
var_Change(obj, "audio-device", VLC_VAR_ADDCHOICE, &val, &text);
if (likely(text.psz_string != val.psz_string))
free(text.psz_string);
free(val.psz_string);
}
IMMDeviceCollection_Release(devs);
}
/*** Audio session events ***/
static inline aout_sys_t *vlc_AudioSessionEvents_sys(IAudioSessionEvents *this)
{
return (aout_sys_t *)(((char *)this) - offsetof(aout_sys_t, 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 = vlc_AudioSessionEvents_sys(this);
return InterlockedIncrement(&sys->refs);
}
static STDMETHODIMP_(ULONG)
vlc_AudioSessionEvents_Release(IAudioSessionEvents *this)
{
aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
return InterlockedDecrement(&sys->refs);
}
static STDMETHODIMP
vlc_AudioSessionEvents_OnDisplayNameChanged(IAudioSessionEvents *this,
LPCWSTR wname, LPCGUID ctx)
{
aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
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 = vlc_AudioSessionEvents_sys(this);
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,
WINBOOL mute, LPCGUID ctx)
{
aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
audio_output_t *aout = sys->aout;
msg_Dbg(aout, "simple volume changed: %f, muting %sabled", vol,
mute ? "en" : "dis");
aout_VolumeReport(aout, vol);
aout_MuteReport(aout, mute == TRUE);
(void) ctx;
return S_OK;
}
static STDMETHODIMP
vlc_AudioSessionEvents_OnChannelVolumeChanged(IAudioSessionEvents *this,
DWORD count, float *vols,
DWORD changed, LPCGUID ctx)
{
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]);
(void) ctx;
return S_OK;
}
static STDMETHODIMP
vlc_AudioSessionEvents_OnGroupingParamChanged(IAudioSessionEvents *this,
LPCGUID param, LPCGUID ctx)
{
aout_sys_t *sys = vlc_AudioSessionEvents_sys(this);
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 = vlc_AudioSessionEvents_sys(this);
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 = vlc_AudioSessionEvents_sys(this);
audio_output_t *aout = sys->aout;
msg_Dbg(aout, "session disconnected: reason %d", reason);
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,
};
/*** Initialization / deinitialization **/ /*** Initialization / deinitialization **/
static const uint32_t chans_out[] = { static const uint32_t chans_out[] = {
...@@ -668,135 +331,25 @@ static unsigned vlc_CheckWaveOrder (const WAVEFORMATEX *restrict wf, ...@@ -668,135 +331,25 @@ static unsigned vlc_CheckWaveOrder (const WAVEFORMATEX *restrict wf,
return aout_CheckChannelReorder(chans_in, chans_out, mask, table); return aout_CheckChannelReorder(chans_in, chans_out, mask, table);
} }
static wchar_t *var_InheritWide(vlc_object_t *obj, const char *name) static HRESULT Start(aout_api_t *api, audio_sample_format_t *restrict fmt,
IMMDevice *dev, const GUID *sid)
{ {
char *v8 = var_InheritString(obj, name); aout_api_sys_t *sys = malloc(sizeof (*sys));
if (v8 == NULL) if (unlikely(sys == NULL))
return NULL; return E_OUTOFMEMORY;
sys->client = NULL;
wchar_t *v16 = ToWide(v8);
free(v8);
return v16;
}
#define var_InheritWide(o,n) var_InheritWide(VLC_OBJECT(o),n)
static int var_SetWide(vlc_object_t *obj, const char *name, const wchar_t *val)
{
char *str = FromWide(val);
if (unlikely(str == NULL))
return VLC_ENOMEM;
int ret = var_SetString(obj, name, str);
free(str);
return ret;
}
#define var_SetWide(o,n,v) var_SetWide(VLC_OBJECT(o),n,v)
/* Dummy thread to create and release COM interfaces when needed. */
static void MTAThread(void *data)
{
audio_output_t *aout = data;
aout_sys_t *sys = aout->sys;
HRESULT hr;
Enter();
hr = IAudioClient_GetService(sys->client, &IID_IAudioRenderClient,
(void **)&sys->render);
if (FAILED(hr))
{
msg_Err(aout, "cannot get audio render service (error 0x%lx)", hr);
goto fail;
}
hr = IAudioClient_GetService(sys->client, &IID_IAudioClock,
(void **)&sys->clock);
if (FAILED(hr))
msg_Warn(aout, "cannot get audio clock (error 0x%lx)", hr);
hr = IAudioClient_GetService(sys->client, &IID_IAudioSessionControl,
(void **)&sys->control);
if (FAILED(hr))
msg_Warn(aout, "cannot get audio session control (error 0x%lx)", hr);
else
{
wchar_t *ua = var_InheritWide(aout, "user-agent");
IAudioSessionControl_SetDisplayName(sys->control, ua, NULL);
free(ua);
}
/* do nothing until the audio session terminates */
ReleaseSemaphore(sys->ready, 1, NULL);
WaitForSingleObject(sys->done, INFINITE);
if (sys->control != NULL)
IAudioSessionControl_Release(sys->control);
if (sys->clock != NULL)
IAudioClock_Release(sys->clock);
IAudioRenderClient_Release(sys->render);
fail:
Leave();
ReleaseSemaphore(sys->ready, 1, NULL);
}
static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
{
aout_sys_t *sys = aout->sys;
HRESULT hr; HRESULT hr;
sys->client = NULL;
sys->render = NULL;
sys->clock = NULL;
sys->events.lpVtbl = &vlc_AudioSessionEvents;
sys->refs = 1;
sys->ready = NULL;
sys->done = NULL;
Enter(); Enter();
retry: void *pv;
/* Get audio device according to policy */ hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_ALL, NULL, &pv);
// Without configuration item, the variable must be created explicitly.
var_Create (aout, "wasapi-audio-device", VLC_VAR_STRING);
LPWSTR devid = var_InheritWide (aout, "wasapi-audio-device");
var_Destroy (aout, "wasapi-audio-device");
IMMDevice *dev = NULL;
if (devid != NULL)
{
msg_Dbg (aout, "using selected device %ls", devid);
hr = IMMDeviceEnumerator_GetDevice (sys->it, devid, &dev);
if (FAILED(hr))
msg_Warn(aout, "cannot get audio endpoint (error 0x%lx)", hr);
free (devid);
}
if (dev == NULL)
{
msg_Dbg (aout, "using default device");
hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(sys->it, eRender,
eConsole, &dev);
}
if (FAILED(hr))
{
msg_Err(aout, "cannot get audio endpoint (error 0x%lx)", hr);
goto error;
}
hr = IMMDevice_GetId(dev, &devid);
if (SUCCEEDED(hr))
{
msg_Dbg(aout, "using device %ls", devid);
var_SetWide (aout, "audio-device", devid);
CoTaskMemFree(devid);
}
hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_ALL, NULL,
(void **)&sys->client);
IMMDevice_Release(dev);
if (FAILED(hr)) if (FAILED(hr))
{ {
msg_Err(aout, "cannot activate audio client (error 0x%lx)", hr); msg_Err(api, "cannot activate client (error 0x%lx)", hr);
goto error; goto error;
} }
sys->client = pv;
/* Configure audio stream */ /* Configure audio stream */
WAVEFORMATEXTENSIBLE wf; WAVEFORMATEXTENSIBLE wf;
...@@ -807,7 +360,7 @@ retry: ...@@ -807,7 +360,7 @@ retry:
&wf.Format, &pwf); &wf.Format, &pwf);
if (FAILED(hr)) if (FAILED(hr))
{ {
msg_Err(aout, "cannot negotiate audio format (error 0x%lx)", hr); msg_Err(api, "cannot negotiate audio format (error 0x%lx)", hr);
goto error; goto error;
} }
...@@ -817,10 +370,11 @@ retry: ...@@ -817,10 +370,11 @@ retry:
if (vlc_FromWave(pwf, fmt)) if (vlc_FromWave(pwf, fmt))
{ {
CoTaskMemFree(pwf); CoTaskMemFree(pwf);
msg_Err(aout, "unsupported audio format"); msg_Err(api, "unsupported audio format");
hr = E_INVALIDARG;
goto error; goto error;
} }
msg_Dbg(aout, "modified format"); msg_Dbg(api, "modified format");
} }
else else
assert(pwf == NULL); assert(pwf == NULL);
...@@ -831,140 +385,68 @@ retry: ...@@ -831,140 +385,68 @@ retry:
hr = IAudioClient_Initialize(sys->client, AUDCLNT_SHAREMODE_SHARED, 0, hr = IAudioClient_Initialize(sys->client, AUDCLNT_SHAREMODE_SHARED, 0,
AOUT_MAX_PREPARE_TIME * 10, 0, AOUT_MAX_PREPARE_TIME * 10, 0,
(hr == S_OK) ? &wf.Format : pwf, (hr == S_OK) ? &wf.Format : pwf, sid);
&GUID_VLC_AUD_OUT);
CoTaskMemFree(pwf); CoTaskMemFree(pwf);
if (FAILED(hr)) if (FAILED(hr))
{ {
msg_Err(aout, "cannot initialize audio client (error 0x%lx)", hr); msg_Err(api, "cannot initialize audio client (error 0x%lx)", hr);
goto error; goto error;
} }
hr = IAudioClient_GetBufferSize(sys->client, &sys->frames); hr = IAudioClient_GetBufferSize(sys->client, &sys->frames);
if (FAILED(hr)) if (FAILED(hr))
{ {
msg_Err(aout, "cannot get buffer size (error 0x%lx)", hr); msg_Err(api, "cannot get buffer size (error 0x%lx)", hr);
goto error; goto error;
} }
sys->ready = CreateSemaphore(NULL, 0, 1, NULL);
sys->done = CreateSemaphore(NULL, 0, 1, NULL);
if (unlikely(sys->ready == NULL || sys->done == NULL))
goto error;
/* Note: thread handle released by CRT, ignore it. */
if (_beginthread(MTAThread, 0, aout) == (uintptr_t)-1)
goto error;
WaitForSingleObject(sys->ready, INFINITE);
if (sys->render == NULL)
goto error;
Leave(); Leave();
sys->rate = fmt->i_rate; sys->rate = fmt->i_rate;
sys->bytes_per_frame = fmt->i_bytes_per_frame; sys->bytes_per_frame = fmt->i_bytes_per_frame;
sys->written = 0; sys->written = 0;
aout->time_get = TimeGet; api->sys = sys;
aout->play = Play; api->time_get = TimeGet;
aout->pause = Pause; api->play = Play;
aout->flush = Flush; api->pause = Pause;
if (likely(sys->control != NULL)) api->flush = Flush;
IAudioSessionControl_RegisterAudioSessionNotification(sys->control,
&sys->events);
var_AddCallback (aout, "audio-device", DeviceChanged, NULL);
return VLC_SUCCESS; return VLC_SUCCESS;
error: error:
if (sys->done != NULL)
CloseHandle(sys->done);
if (sys->ready != NULL)
CloseHandle(sys->done);
if (sys->client != NULL) if (sys->client != NULL)
IAudioClient_Release(sys->client); IAudioClient_Release(sys->client);
if (hr == AUDCLNT_E_DEVICE_INVALIDATED)
{
var_SetString(aout, "audio-device", "");
msg_Warn(aout, "device invalidated, retrying");
goto retry;
}
Leave(); Leave();
return VLC_EGENERIC; return hr;
} }
static void Stop(audio_output_t *aout) static void Stop(aout_api_t *api)
{ {
aout_sys_t *sys = aout->sys; aout_api_sys_t *sys = api->sys;
Enter(); Enter();
if (likely(sys->control != NULL))
IAudioSessionControl_UnregisterAudioSessionNotification(sys->control,
&sys->events);
ReleaseSemaphore(sys->done, 1, NULL); /* tell MTA thread to finish */
WaitForSingleObject(sys->ready, INFINITE); /* wait for that ^ */
IAudioClient_Stop(sys->client); /* should not be needed */ IAudioClient_Stop(sys->client); /* should not be needed */
IAudioClient_Release(sys->client); IAudioClient_Release(sys->client);
Leave(); Leave();
var_DelCallback (aout, "audio-device", DeviceChanged, NULL);
CloseHandle(sys->done);
CloseHandle(sys->ready);
} }
static int Open(vlc_object_t *obj) #undef aout_api_Start
aout_api_t *aout_api_Start(vlc_object_t *parent, audio_sample_format_t *fmt,
IMMDevice *dev, const GUID *sid)
{ {
audio_output_t *aout = (audio_output_t *)obj; aout_api_t *api = vlc_object_create(parent, sizeof (*api));
void *pv; if (unlikely(api == NULL))
HRESULT hr; return NULL;
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;
sys->aout = aout;
if (TryEnter(aout))
goto error;
hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, HRESULT hr = Start(api, fmt, dev, sid);
&IID_IMMDeviceEnumerator, &pv);
if (FAILED(hr)) if (FAILED(hr))
{ {
msg_Dbg(aout, "cannot create device enumerator (error 0x%lx)", hr); vlc_object_release(api);
Leave(); api = NULL;
goto error;
} }
sys->it = pv; return NULL;
GetDevices(obj, sys->it);
Leave();
sys->volume_hack = -1.f;
sys->mute_hack = -1;
aout->sys = sys;
aout->start = Start;
aout->stop = Stop;
aout->volume_set = SimpleVolumeSet; /* FIXME */
aout->mute_set = SimpleMuteSet;
return VLC_SUCCESS;
error:
free(sys);
return VLC_EGENERIC;
} }
static void Close(vlc_object_t *obj) void aout_api_Stop(aout_api_t *api)
{ {
audio_output_t *aout = (audio_output_t *)obj; Stop(api);
aout_sys_t *sys = aout->sys; vlc_object_release(api);
var_Destroy (aout, "audio-device");
Enter();
IMMDeviceEnumerator_Release(sys->it);
Leave();
free(sys);
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment