]> git.sesse.net Git - vlc/commitdiff
wasapi: split MMDevice and session code from output
authorRémi Denis-Courmont <remi@remlab.net>
Sun, 2 Dec 2012 20:57:07 +0000 (22:57 +0200)
committerRémi Denis-Courmont <remi@remlab.net>
Sun, 2 Dec 2012 21:06:03 +0000 (23:06 +0200)
- 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)

modules/audio_output/Modules.am
modules/audio_output/mmdevice.c [new file with mode: 0644]
modules/audio_output/mmdevice.h [new file with mode: 0644]
modules/audio_output/wasapi.c

index 3926c20f392981c3b1c0d5566291302ccfd41672..92bd1e7c58653412f311637ccca8e7ae57f91248 100644 (file)
@@ -54,11 +54,11 @@ libjack_plugin_la_LIBADD = $(AM_LIBADD) $(JACK_LIBS) $(LIBM)
 EXTRA_LTLIBRARIES += libjack_plugin.la
 libvlc_LTLIBRARIES += $(LTLIBjack)
 
-libwasapi_plugin_la_SOURCES = wasapi.c
-libwasapi_plugin_la_CFLAGS = $(AM_CFLAGS)
-libwasapi_plugin_la_LIBADD = $(AM_LIBADD) -lole32 -lksuser
+libmmdevice_plugin_la_SOURCES = mmdevice.c mmdevice.h wasapi.c
+libmmdevice_plugin_la_CFLAGS = $(AM_CFLAGS)
+libmmdevice_plugin_la_LIBADD = $(AM_LIBADD) -lole32 -lksuser
 if HAVE_WASAPI
-libvlc_LTLIBRARIES += libwasapi_plugin.la
+libvlc_LTLIBRARIES += libmmdevice_plugin.la
 endif
 
 libdirectsound_plugin_la_SOURCES = directx.c windows_audio_common.h packet.c
diff --git a/modules/audio_output/mmdevice.c b/modules/audio_output/mmdevice.c
new file mode 100644 (file)
index 0000000..1fd9d9c
--- /dev/null
@@ -0,0 +1,637 @@
+/*****************************************************************************
+ * 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()
diff --git a/modules/audio_output/mmdevice.h b/modules/audio_output/mmdevice.h
new file mode 100644 (file)
index 0000000..7f4c557
--- /dev/null
@@ -0,0 +1,75 @@
+/*****************************************************************************
+ * 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
index 1d1e46bb40b52bfe0dbb2872dc090e7ca2771844..d8e9f2633f1d057cf6109adc7f31c55069352c1f 100644 (file)
@@ -19,7 +19,7 @@
  *****************************************************************************/
 
 #ifdef HAVE_CONFIG_H
-# include "config.h"
+# include <config.h>
 #endif
 
 #define INITGUID
 #include <stdlib.h>
 #include <assert.h>
 #include <audioclient.h>
-#include <audiopolicy.h>
 #include <mmdeviceapi.h>
 
 #include <vlc_common.h>
-#include <vlc_plugin.h>
 #include <vlc_aout.h>
-#include <vlc_charset.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()
+#include "mmdevice.h"
 
 static LARGE_INTEGER freq; /* performance counters frequency */
 
@@ -86,18 +65,6 @@ static UINT64 GetQPC(void)
     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)
 {
     HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
@@ -110,100 +77,87 @@ static void Leave(void)
     CoUninitialize();
 }
 
-struct aout_sys_t
+typedef struct aout_api_sys
 {
-    audio_output_t *aout;
-    IMMDeviceEnumerator *it;
     IAudioClient *client;
-    IAudioRenderClient *render;
-    IAudioClock *clock;
-
-    IAudioSessionControl *control;
-    struct IAudioSessionEvents events;
-    LONG refs;
 
     uint8_t chans_table[AOUT_CHAN_MAX];
     uint8_t chans_to_reorder;
+
     uint8_t bits; /**< Bits per sample */
     unsigned rate; /**< Sample rate */
     unsigned bytes_per_frame;
     UINT32 written; /**< Frames written to the buffer */
     UINT32 frames; /**< Total buffer size (frames) */
-
-    float volume_hack; /**< Deferred volume request */
-    int mute_hack; /**< Deferred mute request */
-
-    HANDLE ready; /**< Semaphore from MTA thread */
-    HANDLE done; /**< Semaphore to MTA thread */
-};
+} aout_api_sys_t;
 
 
 /*** 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;
     HRESULT hr;
 
-    if (sys->clock == NULL)
-        return -1;
-
     Enter();
-    hr = IAudioClock_GetPosition(sys->clock, &pos, &qpcpos);
-    Leave();
-    if (FAILED(hr))
+    hr = IAudioClient_GetService(sys->client, &IID_IAudioClock, &pv);
+    if (SUCCEEDED(hr))
     {
-        msg_Err(aout, "cannot get position (error 0x%lx)", hr);
-        return -1;
-    }
+        IAudioClock *clock = pv;
 
-    if (pos == 0)
-    {
-        *delay = sys->written * CLOCK_FREQ / sys->rate;
-        msg_Dbg(aout, "extrapolating position: still propagating buffers");
-        return 0;
+        hr = IAudioClock_GetPosition(clock, &pos, &qpcpos);
+        if (FAILED(hr))
+            msg_Err(api, "cannot get position (error 0x%lx)", hr);
+        IAudioClock_Release(clock);
     }
+    else
+        msg_Err(api, "cannot get clock (error 0x%lx)", hr);
+    Leave();
 
-    *delay = ((GetQPC() - qpcpos) / (10000000 / CLOCK_FREQ));
-    static_assert((10000000 % CLOCK_FREQ) == 0, "Frequency conversion broken");
-    return 0;
-}
-
-static void CheckVolumeHack(audio_output_t *aout)
-{
-    aout_sys_t *sys = aout->sys;
-
-    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;
+    if (SUCCEEDED(hr))
+    {
+        if (pos != 0)
+        {
+            *delay = ((GetQPC() - qpcpos) / (10000000 / CLOCK_FREQ));
+            static_assert((10000000 % CLOCK_FREQ) == 0,
+                          "Frequency conversion broken");
+        }
+        else
+        {
+            *delay = sys->written * CLOCK_FREQ / sys->rate;
+            msg_Dbg(api, "extrapolating position: still propagating buffers");
+        }
     }
+    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;
-    HRESULT hr = S_OK;
-
-    CheckVolumeHack(aout);
+    aout_api_sys_t *sys = api->sys;
+    void *pv;
+    HRESULT hr;
 
     if (sys->chans_to_reorder)
         aout_ChannelReorder(block->p_buffer, block->i_buffer,
                           sys->chans_to_reorder, sys->chans_table, sys->bits);
 
     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 (;;)
     {
         UINT32 frames;
         hr = IAudioClient_GetCurrentPadding(sys->client, &frames);
         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;
         }
 
@@ -213,20 +167,20 @@ static void Play(audio_output_t *aout, block_t *block)
             frames = block->i_nb_samples;
 
         BYTE *dst;
-        hr = IAudioRenderClient_GetBuffer(sys->render, frames, &dst);
+        hr = IAudioRenderClient_GetBuffer(render, frames, &dst);
         if (FAILED(hr))
         {
-            msg_Err(aout, "cannot get buffer (error 0x%lx)", hr);
+            msg_Err(api, "cannot get buffer (error 0x%lx)", hr);
             break;
         }
 
         const size_t copy = frames * sys->bytes_per_frame;
 
         memcpy(dst, block->p_buffer, copy);
-        hr = IAudioRenderClient_ReleaseBuffer(sys->render, frames, 0);
+        hr = IAudioRenderClient_ReleaseBuffer(render, frames, 0);
         if (FAILED(hr))
         {
-            msg_Err(aout, "cannot release buffer (error 0x%lx)", hr);
+            msg_Err(api, "cannot release buffer (error 0x%lx)", hr);
             break;
         }
         IAudioClient_Start(sys->client);
@@ -242,339 +196,48 @@ static void Play(audio_output_t *aout, block_t *block)
         msleep(AOUT_MIN_PREPARE_TIME
              + block->i_nb_samples * CLOCK_FREQ / sys->rate);
     }
-
+    IAudioRenderClient_Release(render);
+out:
     Leave();
     block_Release(block);
 
-    /* Restart on unplug */
-    if (unlikely(hr == AUDCLNT_E_DEVICE_INVALIDATED))
-        var_TriggerCallback(aout, "audio-device");
+    return hr;
 }
 
-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;
 
-    CheckVolumeHack(aout);
-
     Enter();
     if (paused)
         hr = IAudioClient_Stop(sys->client);
     else
         hr = IAudioClient_Start(sys->client);
     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);
     Leave();
-
-    (void) date;
+    return hr;
 }
 
-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;
 
-    CheckVolumeHack(aout);
-
-    if (wait)
-        return; /* Drain not implemented */
-
     Enter();
     IAudioClient_Stop(sys->client);
     hr = IAudioClient_Reset(sys->client);
     Leave();
 
     if (FAILED(hr))
-        msg_Warn(aout, "cannot reset stream (error 0x%lx)", hr);
+        msg_Warn(api, "cannot reset stream (error 0x%lx)", hr);
     else
         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 **/
 static const uint32_t chans_out[] = {
@@ -668,135 +331,25 @@ static unsigned vlc_CheckWaveOrder (const WAVEFORMATEX *restrict wf,
     return aout_CheckChannelReorder(chans_in, chans_out, mask, table);
 }
 
-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)
-
-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)
+static HRESULT Start(aout_api_t *api, audio_sample_format_t *restrict fmt,
+                     IMMDevice *dev, const GUID *sid)
 {
-    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);
-}
+    aout_api_sys_t *sys = malloc(sizeof (*sys));
+    if (unlikely(sys == NULL))
+        return E_OUTOFMEMORY;
+    sys->client = NULL;
 
-static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
-{
-    aout_sys_t *sys = aout->sys;
     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();
-retry:
-    /* Get audio device according to policy */
-    // 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);
+    void *pv;
+    hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_ALL, NULL, &pv);
     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;
     }
+    sys->client = pv;
 
     /* Configure audio stream */
     WAVEFORMATEXTENSIBLE wf;
@@ -807,7 +360,7 @@ retry:
                                         &wf.Format, &pwf);
     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;
     }
 
@@ -817,10 +370,11 @@ retry:
         if (vlc_FromWave(pwf, fmt))
         {
             CoTaskMemFree(pwf);
-            msg_Err(aout, "unsupported audio format");
+            msg_Err(api, "unsupported audio format");
+            hr = E_INVALIDARG;
             goto error;
         }
-        msg_Dbg(aout, "modified format");
+        msg_Dbg(api, "modified format");
     }
     else
         assert(pwf == NULL);
@@ -831,140 +385,68 @@ retry:
 
     hr = IAudioClient_Initialize(sys->client, AUDCLNT_SHAREMODE_SHARED, 0,
                                  AOUT_MAX_PREPARE_TIME * 10, 0,
-                                 (hr == S_OK) ? &wf.Format : pwf,
-                                 &GUID_VLC_AUD_OUT);
+                                 (hr == S_OK) ? &wf.Format : pwf, sid);
     CoTaskMemFree(pwf);
     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;
     }
 
     hr = IAudioClient_GetBufferSize(sys->client, &sys->frames);
     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;
     }
 
-    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();
 
     sys->rate = fmt->i_rate;
     sys->bytes_per_frame = fmt->i_bytes_per_frame;
     sys->written = 0;
-    aout->time_get = TimeGet;
-    aout->play = Play;
-    aout->pause = Pause;
-    aout->flush = Flush;
-    if (likely(sys->control != NULL))
-       IAudioSessionControl_RegisterAudioSessionNotification(sys->control,
-                                                             &sys->events);
-    var_AddCallback (aout, "audio-device", DeviceChanged, NULL);
-
+    api->sys = sys;
+    api->time_get = TimeGet;
+    api->play = Play;
+    api->pause = Pause;
+    api->flush = Flush;
     return VLC_SUCCESS;
 error:
-    if (sys->done != NULL)
-        CloseHandle(sys->done);
-    if (sys->ready != NULL)
-        CloseHandle(sys->done);
     if (sys->client != NULL)
         IAudioClient_Release(sys->client);
-    if (hr == AUDCLNT_E_DEVICE_INVALIDATED)
-    {
-        var_SetString(aout, "audio-device", "");
-        msg_Warn(aout, "device invalidated, retrying");
-        goto retry;
-    }
     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();
-    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_Release(sys->client);
     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;
-    void *pv;
-    HRESULT hr;
-
-    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;
+    aout_api_t *api = vlc_object_create(parent, sizeof (*api));
+    if (unlikely(api == NULL))
+        return NULL;
 
-    hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
-                          &IID_IMMDeviceEnumerator, &pv);
+    HRESULT hr = Start(api, fmt, dev, sid);
     if (FAILED(hr))
     {
-        msg_Dbg(aout, "cannot create device enumerator (error 0x%lx)", hr);
-        Leave();
-        goto error;
+        vlc_object_release(api);
+        api = NULL;
     }
-    sys->it = pv;
-    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;
+    return NULL;
 }
 
-static void Close(vlc_object_t *obj)
+void aout_api_Stop(aout_api_t *api)
 {
-    audio_output_t *aout = (audio_output_t *)obj;
-    aout_sys_t *sys = aout->sys;
-
-    var_Destroy (aout, "audio-device");
-
-    Enter();
-    IMMDeviceEnumerator_Release(sys->it);
-    Leave();
-
-    free(sys);
+    Stop(api);
+    vlc_object_release(api);
 }