Subject: [PATCH] winepulse.drv: Add PulseAudio driver
From: Andrew Eikum <[email-masked]>
Date: Fri, 15 Jun 2012 15:39:33 -0500

The configure.ac changes and parts of the driver itself were written by
Maarten Lankhorst.

---

Okay, this time we've got a better method of detecting PulseAudio.
Turns out the default mainloop implementation /doesn't/ create its on
thread like I thought it did. Instead we just have to manually trigger
mainloop iterations until we find out if the context connection
succeeded or not. This should work better.

I also reordered a few of the lock/unlock sequencies to prevent an
out-of-order deadlock.

One thing to note is that PulseAudio has an absurdly high default
latency (two seconds), and due to its poor API there's no (easy) way
for us to control it. This will cause test failures, as the tests
aren't designed to account for very high latency devices. Putting
Pulse into low-latency mode, by opening pavucontrol for example,
results in no test failures on the couple of OSes I've tried it on.

I have a patch in my tree to disable the time-sensitive tests on
high-latency devices, but I am curious to see what behavior happens in
the test results once this is out in the wild. I'm planning to send
the test changes later in the week, before release, after we've got a
couple of test runs to see if anything interesting pops out. If you
don't want me to do that, I can just send the test changes, too.
---
 configure.ac                          |   31 +-
 dlls/mmdevapi/main.c                  |    4 +-
 dlls/winepulse.drv/Makefile.in        |    9 +
 dlls/winepulse.drv/mmdevdrv.c         | 3165 +++++++++++++++++++++++++++++++++
 dlls/winepulse.drv/winepulse.drv.spec |    5 +
 5 files changed, 3210 insertions(+), 4 deletions(-)
 create mode 100644 dlls/winepulse.drv/Makefile.in
 create mode 100644 dlls/winepulse.drv/mmdevdrv.c
 create mode 100644 dlls/winepulse.drv/winepulse.drv.spec

diff --git a/configure.ac b/configure.ac
index 9688ba7..3fb2405 100644
--- a/configure.ac
+++ b/configure.ac
@@ -74,6 +74,7 @@ AC_ARG_WITH(png,       AS_HELP_STRING([--without-png],[do not use PNG]),
             [if test "x$withval" = "xno"; then ac_cv_header_png_h=no; fi])
 AC_ARG_WITH(pthread,   AS_HELP_STRING([--without-pthread],[do not use the pthread library]),
             [if test "x$withval" = "xno"; then ac_cv_header_pthread_h=no; fi])
+AC_ARG_WITH(pulse,     AS_HELP_STRING([--without-pulseaudio],[do not use PulseAudio sound support]))
 AC_ARG_WITH(sane,      AS_HELP_STRING([--without-sane],[do not use SANE (scanner support)]))
 AC_ARG_WITH(tiff,       AS_HELP_STRING([--without-tiff],[do not use TIFF]),
             [if test "x$withval" = "xno"; then ac_cv_header_tiffio_h=no; fi])
@@ -1482,6 +1483,30 @@ then
           [GetText ${notice_platform}development files not found (or too old), po files can't be rebuilt.])
 fi
 
+dnl **** Check for PulseAudio ****
+AC_SUBST(PULSELIBS,"")
+AC_SUBST(PULSEINCL,"")
+if test "x$with_pulse" != "xno";
+then
+    ac_save_CPPFLAGS="$CPPFLAGS"
+    if test "$PKG_CONFIG" != "false";
+    then
+        ac_pulse_libs="`$PKG_CONFIG --libs libpulse 2>/dev/null`"
+        ac_pulse_cflags="`$PKG_CONFIG --cflags-only-I libpulse 2>/dev/null`"
+
+        CPPFLAGS="$CPPFLAGS $ac_pulse_cflags"
+        AC_CHECK_HEADERS(pulse/pulseaudio.h,
+            [AC_CHECK_LIB(pulse, pa_stream_is_corked,
+                [AC_DEFINE(HAVE_PULSEAUDIO, 1, [Define if you have PulseAudio])
+                 PULSELIBS="$ac_pulse_libs"
+                 PULSEINCL="$ac_pulse_cflags"],,$ac_pulse_libs)
+        ])
+    fi
+    CPPFLAGS="$ac_save_CPPFLAGS"
+fi
+WINE_NOTICE_WITH(pulse, [test "$ac_cv_lib_pulse_pa_stream_is_corked" != "yes"],
+        [libpulse ${notice_platform}development files not found or too old, PulseAudio won't be supported.])
+
 dnl **** Check for gstreamer ****
 if test "x$with_gstreamer" != "xno"
 then
@@ -1688,13 +1713,14 @@ WINE_CHECK_SONAME(odbc,SQLConnect,,[AC_DEFINE_UNQUOTED(SONAME_LIBODBC,["libodbc.
 dnl **** Disable unsupported winmm drivers ****
 test -n "$ALSALIBS" || enable_winealsa_drv=${enable_winealsa_drv:-no}
 test -n "$COREAUDIO" || enable_winecoreaudio_drv=${enable_winecoreaudio_drv:-no}
+test -n "$PULSELIBS" || enable_winepulse_drv=${enable_winepulse_drv:-no}
 test "x$ac_cv_member_oss_sysinfo_numaudioengines" = xyes || enable_wineoss_drv=${enable_wineoss_drv:-no}
 test "$ac_cv_header_linux_joystick_h" = "yes" || enable_winejoystick_drv=${enable_winejoystick_drv:-no}
 
 dnl **** Check for any sound system ****
-if test "x$ALSALIBS$COREAUDIO" = "x" -a \
+if test "x$ALSALIBS$COREAUDIO$PULSELIBS" = "x" -a \
         "x$ac_cv_member_oss_sysinfo_numaudioengines" != xyes -a \
-        "x$with_alsa$with_coreaudio$with_oss" != xnonono
+        "x$with_alsa$with_coreaudio$with_oss$with_pulse" != xnononono
 then
     WINE_WARNING([No sound system was found. Windows applications will be silent.])
 fi
@@ -2961,6 +2987,7 @@ WINE_CONFIG_DLL(winemp3.acm)
 WINE_CONFIG_DLL(wineoss.drv)
 WINE_CONFIG_DLL(wineps.drv,,[install-lib,po])
 WINE_CONFIG_DLL(wineps16.drv16,enable_win16)
+WINE_CONFIG_DLL(winepulse.drv)
 WINE_CONFIG_DLL(wineqtdecoder)
 WINE_CONFIG_DLL(winequartz.drv)
 WINE_CONFIG_DLL(winex11.drv)
diff --git a/dlls/mmdevapi/main.c b/dlls/mmdevapi/main.c
index c0e2ff6..38a8f23 100644
--- a/dlls/mmdevapi/main.c
+++ b/dlls/mmdevapi/main.c
@@ -111,8 +111,8 @@ static BOOL init_driver(void)
 {
     static const WCHAR drv_value[] = {'A','u','d','i','o',0};
 
-    static WCHAR default_list[] = {'a','l','s','a',',','o','s','s',',',
-        'c','o','r','e','a','u','d','i','o',0};
+    static WCHAR default_list[] = {'p','u','l','s','e',',','a','l','s','a',',',
+        'o','s','s',',','c','o','r','e','a','u','d','i','o',0};
 
     DriverFuncs driver;
     HKEY key;
diff --git a/dlls/winepulse.drv/Makefile.in b/dlls/winepulse.drv/Makefile.in
new file mode 100644
index 0000000..8b9ecdd
--- /dev/null
+++ b/dlls/winepulse.drv/Makefile.in
@@ -0,0 +1,9 @@
+MODULE    = winepulse.drv
+IMPORTS   = uuid ole32
+EXTRALIBS = @PULSELIBS@ @LIBPTHREAD@
+EXTRAINCL = @PULSEINCL@
+
+C_SRCS = \
+	mmdevdrv.c
+
+@MAKE_DLL_RULES@
diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c
new file mode 100644
index 0000000..636e607
--- /dev/null
+++ b/dlls/winepulse.drv/mmdevdrv.c
@@ -0,0 +1,3165 @@
+/*
+ * Copyright 2012 Andrew Eikum for CodeWeavers
+ * Copyright 2011-2012 Maarten Lankhorst
+ * Copyright 2010-2011 Maarten Lankhorst for CodeWeavers
+ *
+ * This library 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 library 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 library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#define NONAMELESSUNION
+#define COBJMACROS
+#include "config.h"
+
+#include <stdarg.h>
+#include <math.h>
+
+#include "windef.h"
+#include "winbase.h"
+#include "winnls.h"
+#include "winreg.h"
+#include "wine/debug.h"
+#include "wine/unicode.h"
+#include "wine/list.h"
+
+#include "ole2.h"
+#include "mmdeviceapi.h"
+#include "devpkey.h"
+#include "dshow.h"
+#include "dsound.h"
+
+#include "initguid.h"
+#include "endpointvolume.h"
+#include "audioclient.h"
+#include "audiopolicy.h"
+
+#include <poll.h>
+#include <pthread.h>
+#include <pulse/pulseaudio.h>
+
+WINE_DEFAULT_DEBUG_CHANNEL(pulse);
+
+#define NULL_PTR_ERR MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, RPC_X_NULL_REF_POINTER)
+
+static const REFERENCE_TIME DefaultPeriod = 100000;
+static const REFERENCE_TIME MinimumPeriod = 50000;
+
+struct ACImpl;
+typedef struct ACImpl ACImpl;
+
+typedef struct _AudioSession {
+    GUID guid;
+    struct list clients;
+
+    IMMDevice *device;
+
+    float master_vol;
+    UINT32 channel_count;
+    float *channel_vols;
+    BOOL mute;
+
+    CRITICAL_SECTION lock;
+
+    struct list entry;
+} AudioSession;
+
+typedef struct _AudioSessionWrapper {
+    IAudioSessionControl2 IAudioSessionControl2_iface;
+    IChannelAudioVolume IChannelAudioVolume_iface;
+    ISimpleAudioVolume ISimpleAudioVolume_iface;
+
+    LONG ref;
+
+    ACImpl *client;
+    AudioSession *session;
+} AudioSessionWrapper;
+
+struct ACImpl {
+    IAudioClient IAudioClient_iface;
+    IAudioRenderClient IAudioRenderClient_iface;
+    IAudioCaptureClient IAudioCaptureClient_iface;
+    IAudioClock IAudioClock_iface;
+    IAudioClock2 IAudioClock2_iface;
+    IAudioStreamVolume IAudioStreamVolume_iface;
+
+    LONG ref;
+
+    pa_stream *stream;
+    pa_sample_spec ss;
+    pa_channel_map map;
+
+    IMMDevice *parent;
+
+    EDataFlow dataflow;
+    WAVEFORMATEX *fmt;
+    DWORD flags;
+    AUDCLNT_SHAREMODE share;
+    HANDLE event;
+    float *vols;
+
+    BOOL initted, started;
+    REFERENCE_TIME mmdev_period_rt, latency_rt;
+    UINT64 pos_frames, written_frames;
+    UINT32 bufsize_frames, tmp_buffer_frames, mmdev_period_frames;
+    UINT32 latency_frames, held_frames, lcl_offs_frames, in_pulse_frames;
+
+    HANDLE timer;
+    BYTE *tmp_buffer, *local_buffer;
+    LONG32 getbuf_last; /* <0 when using tmp_buffer */
+
+    CRITICAL_SECTION lock;
+
+    AudioSession *session;
+    AudioSessionWrapper *session_wrapper;
+
+    struct list entry;
+};
+
+typedef struct _SessionMgr {
+    IAudioSessionManager2 IAudioSessionManager2_iface;
+
+    LONG ref;
+
+    IMMDevice *device;
+} SessionMgr;
+
+static HANDLE g_timer_q;
+
+static const GUID render_guid = {0x5e02c700, 0x0f03, 0x4c4e, {0x98, 0xb8, 0xea, 0xea, 0x87, 0x65, 0x84, 0x00}};
+static const GUID capture_guid = {0x5e02c700, 0x0f03, 0x4c4e, {0x98, 0xb8, 0xea, 0xea, 0x87, 0x65, 0x84, 0x01}};
+
+static CRITICAL_SECTION g_sessions_lock;
+static CRITICAL_SECTION_DEBUG g_sessions_lock_debug =
+{
+    0, 0, &g_sessions_lock,
+    { &g_sessions_lock_debug.ProcessLocksList, &g_sessions_lock_debug.ProcessLocksList },
+      0, 0, { (DWORD_PTR)(__FILE__ ": g_sessions_lock") }
+};
+static CRITICAL_SECTION g_sessions_lock = { &g_sessions_lock_debug, -1, 0, 0, 0, 0 };
+static struct list g_sessions = LIST_INIT(g_sessions);
+
+static const IAudioClientVtbl AudioClient_Vtbl;
+static const IAudioRenderClientVtbl AudioRenderClient_Vtbl;
+static const IAudioCaptureClientVtbl AudioCaptureClient_Vtbl;
+static const IAudioSessionControl2Vtbl AudioSessionControl2_Vtbl;
+static const ISimpleAudioVolumeVtbl SimpleAudioVolume_Vtbl;
+static const IAudioClockVtbl AudioClock_Vtbl;
+static const IAudioClock2Vtbl AudioClock2_Vtbl;
+static const IAudioStreamVolumeVtbl AudioStreamVolume_Vtbl;
+static const IChannelAudioVolumeVtbl ChannelAudioVolume_Vtbl;
+static const IAudioSessionManager2Vtbl AudioSessionManager2_Vtbl;
+
+static AudioSessionWrapper *AudioSessionWrapper_Create(ACImpl *client);
+
+static pa_mainloop *pulse_ml;
+static pa_context *pulse_ctx;
+static pthread_mutex_t pulse_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t pulse_cond = PTHREAD_COND_INITIALIZER;
+static HANDLE pulse_thread;
+
+static inline ACImpl *impl_from_IAudioClient(IAudioClient *iface)
+{
+    return CONTAINING_RECORD(iface, ACImpl, IAudioClient_iface);
+}
+
+static inline ACImpl *impl_from_IAudioRenderClient(IAudioRenderClient *iface)
+{
+    return CONTAINING_RECORD(iface, ACImpl, IAudioRenderClient_iface);
+}
+
+static inline ACImpl *impl_from_IAudioCaptureClient(IAudioCaptureClient *iface)
+{
+    return CONTAINING_RECORD(iface, ACImpl, IAudioCaptureClient_iface);
+}
+
+static inline AudioSessionWrapper *impl_from_IAudioSessionControl2(IAudioSessionControl2 *iface)
+{
+    return CONTAINING_RECORD(iface, AudioSessionWrapper, IAudioSessionControl2_iface);
+}
+
+static inline AudioSessionWrapper *impl_from_ISimpleAudioVolume(ISimpleAudioVolume *iface)
+{
+    return CONTAINING_RECORD(iface, AudioSessionWrapper, ISimpleAudioVolume_iface);
+}
+
+static inline AudioSessionWrapper *impl_from_IChannelAudioVolume(IChannelAudioVolume *iface)
+{
+    return CONTAINING_RECORD(iface, AudioSessionWrapper, IChannelAudioVolume_iface);
+}
+
+static inline ACImpl *impl_from_IAudioClock(IAudioClock *iface)
+{
+    return CONTAINING_RECORD(iface, ACImpl, IAudioClock_iface);
+}
+
+static inline ACImpl *impl_from_IAudioClock2(IAudioClock2 *iface)
+{
+    return CONTAINING_RECORD(iface, ACImpl, IAudioClock2_iface);
+}
+
+static inline ACImpl *impl_from_IAudioStreamVolume(IAudioStreamVolume *iface)
+{
+    return CONTAINING_RECORD(iface, ACImpl, IAudioStreamVolume_iface);
+}
+
+static inline SessionMgr *impl_from_IAudioSessionManager2(IAudioSessionManager2 *iface)
+{
+    return CONTAINING_RECORD(iface, SessionMgr, IAudioSessionManager2_iface);
+}
+
+BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, void *reserved)
+{
+    TRACE("%p %x %p\n", dll, reason, reserved);
+    switch (reason)
+    {
+    case DLL_PROCESS_ATTACH:
+        g_timer_q = CreateTimerQueue();
+        if(!g_timer_q)
+            return FALSE;
+        break;
+
+    case DLL_PROCESS_DETACH:
+        DeleteCriticalSection(&g_sessions_lock);
+
+        pthread_mutex_lock(&pulse_lock);
+
+        if(pulse_ctx){
+            pa_context_unref(pulse_ctx);
+            pulse_ctx = NULL;
+        }
+
+        if(pulse_ml){
+            pa_mainloop_quit(pulse_ml, 0);
+            pthread_cond_wait(&pulse_cond, &pulse_lock);
+        }
+
+        pthread_mutex_unlock(&pulse_lock);
+
+        break;
+    }
+    return TRUE;
+}
+
+/* From <dlls/mmdevapi/mmdevapi.h> */
+enum DriverPriority {
+    Priority_Unavailable = 0,
+    Priority_Low,
+    Priority_Neutral,
+    Priority_Preferred
+};
+
+static BOOL is_pulse_running(void)
+{
+    pa_context *tmp_ctx;
+    pa_mainloop *ml;
+    int err;
+    pa_context_state_t state;
+
+    ml = pa_mainloop_new();
+    if(!ml)
+        return FALSE;
+
+    tmp_ctx = pa_context_new(pa_mainloop_get_api(ml), "Wine");
+    if(!tmp_ctx){
+        TRACE("pa_context_new failed\n");
+        pa_mainloop_free(ml);
+        return FALSE;
+    }
+
+    err = pa_context_connect(tmp_ctx, NULL, 0, NULL);
+    if(err < 0){
+        TRACE("pa_context_connect failed: %d\n", err);
+        pa_context_unref(tmp_ctx);
+        pa_mainloop_free(ml);
+        return FALSE;
+    }
+
+    do{
+        if(pa_mainloop_iterate(ml, 0, NULL) < 0){
+            TRACE("pa_mainloop_iterate failed\n");
+            pa_context_unref(tmp_ctx);
+            pa_mainloop_free(ml);
+            return FALSE;
+        }
+
+        state = pa_context_get_state(tmp_ctx);
+
+        if(!PA_CONTEXT_IS_GOOD(state)){
+            TRACE("context failed to connect: %u\n", state);
+            pa_context_unref(tmp_ctx);
+            pa_mainloop_free(ml);
+            return FALSE;
+        }
+    }while(state != PA_CONTEXT_READY);
+
+    pa_context_disconnect(tmp_ctx);
+
+    do{
+        if(pa_mainloop_iterate(ml, 0, NULL) < 0){
+            TRACE("pa_mainloop_iterate failed\n");
+            pa_context_unref(tmp_ctx);
+            pa_mainloop_free(ml);
+            return FALSE;
+        }
+
+        state = pa_context_get_state(tmp_ctx);
+    }while(state != PA_CONTEXT_TERMINATED && PA_CONTEXT_IS_GOOD(state));
+
+    pa_context_unref(tmp_ctx);
+
+    pa_mainloop_free(ml);
+
+    TRACE("success\n");
+
+    return TRUE;
+}
+
+int WINAPI AUDDRV_GetPriority(void)
+{
+    if(!is_pulse_running()){
+        TRACE("Priority_Unavailable: PulseAudio isn't running\n");
+        return Priority_Unavailable;
+    }
+
+    TRACE("Priority_Preferred: PulseAudio is running\n");
+    return Priority_Preferred;
+}
+
+HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, WCHAR ***ids, GUID **guids,
+        UINT *num, UINT *def_index)
+{
+    static const WCHAR out_pulseaudioW[] = {'O','u','t',':',' ','P','u','l','s','e','A','u','d','i','o',0};
+    static const WCHAR in_pulseaudioW[] = {'I','n',':',' ','P','u','l','s','e','A','u','d','i','o',0};
+
+    TRACE("%d %p %p %p %p\n", flow, ids, guids, num, def_index);
+
+    *num = 1;
+    *def_index = 0;
+
+    *ids = HeapAlloc(GetProcessHeap(), 0, sizeof(WCHAR *));
+    if(flow == eRender){
+        (*ids)[0] = HeapAlloc(GetProcessHeap(), 0, sizeof(out_pulseaudioW));
+        memcpy((*ids)[0], out_pulseaudioW, sizeof(out_pulseaudioW));
+        *guids = HeapAlloc(GetProcessHeap(), 0, sizeof(GUID));
+        memcpy(*guids, &render_guid, sizeof(GUID));
+    }else{
+        (*ids)[0] = HeapAlloc(GetProcessHeap(), 0, sizeof(in_pulseaudioW));
+        memcpy((*ids)[0], in_pulseaudioW, sizeof(in_pulseaudioW));
+        *guids = HeapAlloc(GetProcessHeap(), 0, sizeof(GUID));
+        memcpy(*guids, &capture_guid, sizeof(GUID));
+    }
+
+    return S_OK;
+}
+
+static int pulse_poll_func(struct pollfd *ufds, unsigned long nfds,
+        int timeout, void *user)
+{
+    int r;
+    pthread_mutex_unlock(&pulse_lock);
+    r = poll(ufds, nfds, timeout);
+    pthread_mutex_lock(&pulse_lock);
+    return r;
+}
+
+static DWORD CALLBACK pulse_mainloop_thread(void *user)
+{
+    int ret;
+
+    pulse_ml = pa_mainloop_new();
+    if(!pulse_ml){
+        pthread_cond_signal(&pulse_cond);
+        return 1;
+    }
+
+    pa_mainloop_set_poll_func(pulse_ml, pulse_poll_func, NULL);
+
+    TRACE("entering mainloop thread\n");
+    pthread_mutex_lock(&pulse_lock);
+    pthread_cond_signal(&pulse_cond);
+
+    pa_mainloop_run(pulse_ml, &ret);
+
+    pa_mainloop_free(pulse_ml);
+    pulse_ml = NULL;
+
+    TRACE("exiting mainloop thread\n");
+    pthread_cond_signal(&pulse_cond);
+    pthread_mutex_unlock(&pulse_lock);
+
+    return 0;
+}
+
+static void context_state_cb(pa_context *ctx, void *user)
+{
+    pthread_cond_signal(&pulse_cond);
+}
+
+static HRESULT ready_pulse(void)
+{
+    HRESULT ret;
+    int err;
+    pa_context_state_t state;
+    WCHAR path[PATH_MAX], *filename;
+    char *pa_name;
+
+    pthread_mutex_lock(&pulse_lock);
+
+    if(pulse_ctx){
+        pthread_mutex_unlock(&pulse_lock);
+        return S_FALSE;
+    }
+
+    pulse_thread = CreateThread(NULL, 0, pulse_mainloop_thread, NULL, 0, NULL);
+    if(!pulse_thread){
+        WARN("CreateThread failed: %u\n", GetLastError());
+        pthread_mutex_lock(&pulse_lock);
+        return E_FAIL;
+    }
+
+    pthread_cond_wait(&pulse_cond, &pulse_lock);
+    if(!pulse_ml){
+        WARN("mainloop initialization failed\n");
+        ret = AUDCLNT_E_SERVICE_NOT_RUNNING;
+        goto error;
+    }
+
+    if(!GetModuleFileNameW(NULL, path, sizeof(path)/sizeof(WCHAR))){
+        WARN("GetModuleFileNameW failed: %u\n", GetLastError());
+        pa_name = NULL;
+    }else{
+        DWORD len;
+        filename = strrchrW(path, '\\');
+        if(!filename)
+            filename = path;
+        else
+            ++filename;
+        len = WideCharToMultiByte(CP_UNIXCP, 0, filename, -1, NULL, 0, NULL, NULL);
+        pa_name = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
+        WideCharToMultiByte(CP_UNIXCP, 0, filename, -1, pa_name, len, NULL, NULL);
+    }
+
+    pulse_ctx = pa_context_new(pa_mainloop_get_api(pulse_ml), pa_name);
+    HeapFree(GetProcessHeap(), 0, pa_name);
+    if(!pulse_ctx){
+        WARN("pa_context_new failed\n");
+        ret = AUDCLNT_E_SERVICE_NOT_RUNNING;
+        goto error;
+    }
+
+    pa_context_set_state_callback(pulse_ctx, context_state_cb, NULL);
+
+    err = pa_context_connect(pulse_ctx, NULL, 0, NULL);
+    if(err < 0){
+        WARN("pa_context_connect: %d\n", err);
+        ret = AUDCLNT_E_SERVICE_NOT_RUNNING;
+        goto error;
+    }
+
+    do{
+        pthread_cond_wait(&pulse_cond, &pulse_lock);
+
+        state = pa_context_get_state(pulse_ctx);
+
+        if(!PA_CONTEXT_IS_GOOD(state)){
+            WARN("got invalid state: %u\n", state);
+            ret = AUDCLNT_E_SERVICE_NOT_RUNNING;
+            goto error;
+        }
+    }while(state != PA_CONTEXT_READY);
+
+    TRACE("pulse is ready now\n");
+
+    pthread_mutex_unlock(&pulse_lock);
+
+    return S_OK;
+
+error:
+    if(pulse_ctx){
+        pa_context_unref(pulse_ctx);
+        pulse_ctx = NULL;
+    }
+
+    if(pulse_ml){
+        pa_mainloop_quit(pulse_ml, 0);
+        pthread_cond_wait(&pulse_cond, &pulse_lock);
+    }
+
+    pthread_mutex_unlock(&pulse_lock);
+
+    return ret;
+}
+
+HRESULT WINAPI AUDDRV_GetAudioEndpoint(const GUID *guid, IMMDevice *dev, IAudioClient **out)
+{
+    ACImpl *This;
+    EDataFlow dataflow;
+
+    TRACE("%s %p %p\n", debugstr_guid(guid), dev, out);
+
+    if(IsEqualGUID(guid, &render_guid))
+        dataflow = eRender;
+    else if(IsEqualGUID(guid, &capture_guid))
+        dataflow = eCapture;
+    else
+        return AUDCLNT_E_DEVICE_INVALIDATED;
+
+    This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(ACImpl));
+    if(!This)
+        return E_OUTOFMEMORY;
+
+    This->IAudioClient_iface.lpVtbl = &AudioClient_Vtbl;
+    This->IAudioRenderClient_iface.lpVtbl = &AudioRenderClient_Vtbl;
+    This->IAudioCaptureClient_iface.lpVtbl = &AudioCaptureClient_Vtbl;
+    This->IAudioClock_iface.lpVtbl = &AudioClock_Vtbl;
+    This->IAudioClock2_iface.lpVtbl = &AudioClock2_Vtbl;
+    This->IAudioStreamVolume_iface.lpVtbl = &AudioStreamVolume_Vtbl;
+
+    This->dataflow = dataflow;
+
+    InitializeCriticalSection(&This->lock);
+    This->lock.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": ACImpl.lock");
+
+    This->parent = dev;
+    IMMDevice_AddRef(This->parent);
+
+    *out = &This->IAudioClient_iface;
+    IAudioClient_AddRef(&This->IAudioClient_iface);
+
+    return S_OK;
+}
+
+static HRESULT WINAPI AudioClient_QueryInterface(IAudioClient *iface,
+        REFIID riid, void **ppv)
+{
+    TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv);
+
+    if(!ppv)
+        return E_POINTER;
+    *ppv = NULL;
+    if(IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IAudioClient))
+        *ppv = iface;
+    if(*ppv){
+        IUnknown_AddRef((IUnknown*)*ppv);
+        return S_OK;
+    }
+    WARN("Unknown interface %s\n", debugstr_guid(riid));
+    return E_NOINTERFACE;
+}
+
+static ULONG WINAPI AudioClient_AddRef(IAudioClient *iface)
+{
+    ACImpl *This = impl_from_IAudioClient(iface);
+    ULONG ref;
+    ref = InterlockedIncrement(&This->ref);
+    TRACE("(%p) Refcount now %u\n", This, ref);
+    return ref;
+}
+
+static ULONG WINAPI AudioClient_Release(IAudioClient *iface)
+{
+    ACImpl *This = impl_from_IAudioClient(iface);
+    ULONG ref;
+    ref = InterlockedDecrement(&This->ref);
+    TRACE("(%p) Refcount now %u\n", This, ref);
+    if(!ref){
+        IAudioClient_Stop(iface);
+        IMMDevice_Release(This->parent);
+        This->lock.DebugInfo->Spare[0] = 0;
+        DeleteCriticalSection(&This->lock);
+        if(This->initted){
+            EnterCriticalSection(&g_sessions_lock);
+            list_remove(&This->entry);
+            LeaveCriticalSection(&g_sessions_lock);
+        }
+        if(This->stream){
+            pthread_mutex_lock(&pulse_lock);
+            pa_stream_disconnect(This->stream);
+            pa_stream_unref(This->stream);
+            pthread_mutex_unlock(&pulse_lock);
+        }
+        HeapFree(GetProcessHeap(), 0, This->vols);
+        HeapFree(GetProcessHeap(), 0, This->tmp_buffer);
+        HeapFree(GetProcessHeap(), 0, This->local_buffer);
+        CoTaskMemFree(This->fmt);
+        HeapFree(GetProcessHeap(), 0, This);
+    }
+    return ref;
+}
+
+static void dump_fmt(const WAVEFORMATEX *fmt)
+{
+    TRACE("wFormatTag: 0x%x (", fmt->wFormatTag);
+    switch(fmt->wFormatTag){
+    case WAVE_FORMAT_PCM:
+        TRACE("WAVE_FORMAT_PCM");
+        break;
+    case WAVE_FORMAT_IEEE_FLOAT:
+        TRACE("WAVE_FORMAT_IEEE_FLOAT");
+        break;
+    case WAVE_FORMAT_EXTENSIBLE:
+        TRACE("WAVE_FORMAT_EXTENSIBLE");
+        break;
+    default:
+        TRACE("Unknown");
+        break;
+    }
+    TRACE(")\n");
+
+    TRACE("nChannels: %u\n", fmt->nChannels);
+    TRACE("nSamplesPerSec: %u\n", fmt->nSamplesPerSec);
+    TRACE("nAvgBytesPerSec: %u\n", fmt->nAvgBytesPerSec);
+    TRACE("nBlockAlign: %u\n", fmt->nBlockAlign);
+    TRACE("wBitsPerSample: %u\n", fmt->wBitsPerSample);
+    TRACE("cbSize: %u\n", fmt->cbSize);
+
+    if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE){
+        WAVEFORMATEXTENSIBLE *fmtex = (void*)fmt;
+        TRACE("dwChannelMask: %08x\n", fmtex->dwChannelMask);
+        TRACE("Samples: %04x\n", fmtex->Samples.wReserved);
+        TRACE("SubFormat: %s\n", wine_dbgstr_guid(&fmtex->SubFormat));
+    }
+}
+
+static WAVEFORMATEX *clone_format(const WAVEFORMATEX *fmt)
+{
+    WAVEFORMATEX *ret;
+    size_t size;
+
+    if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
+        size = sizeof(WAVEFORMATEXTENSIBLE);
+    else
+        size = sizeof(WAVEFORMATEX);
+
+    ret = CoTaskMemAlloc(size);
+    if(!ret)
+        return NULL;
+
+    memcpy(ret, fmt, size);
+
+    ret->cbSize = size - sizeof(WAVEFORMATEX);
+
+    return ret;
+}
+
+static void session_init_vols(AudioSession *session, UINT channels)
+{
+    if(session->channel_count < channels){
+        UINT i;
+
+        if(session->channel_vols)
+            session->channel_vols = HeapReAlloc(GetProcessHeap(), 0,
+                    session->channel_vols, sizeof(float) * channels);
+        else
+            session->channel_vols = HeapAlloc(GetProcessHeap(), 0,
+                    sizeof(float) * channels);
+        if(!session->channel_vols)
+            return;
+
+        for(i = session->channel_count; i < channels; ++i)
+            session->channel_vols[i] = 1.f;
+
+        session->channel_count = channels;
+    }
+}
+
+static AudioSession *create_session(const GUID *guid, IMMDevice *device,
+        UINT num_channels)
+{
+    AudioSession *ret;
+
+    ret = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(AudioSession));
+    if(!ret)
+        return NULL;
+
+    memcpy(&ret->guid, guid, sizeof(GUID));
+
+    ret->device = device;
+
+    list_init(&ret->clients);
+
+    list_add_head(&g_sessions, &ret->entry);
+
+    InitializeCriticalSection(&ret->lock);
+    ret->lock.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": AudioSession.lock");
+
+    session_init_vols(ret, num_channels);
+
+    ret->master_vol = 1.f;
+
+    return ret;
+}
+
+/* if channels == 0, then this will return or create a session with
+ * matching dataflow and GUID. otherwise, channels must also match */
+static HRESULT get_audio_session(const GUID *sessionguid,
+        IMMDevice *device, UINT channels, AudioSession **out)
+{
+    AudioSession *session;
+
+    if(!sessionguid || IsEqualGUID(sessionguid, &GUID_NULL)){
+        *out = create_session(&GUID_NULL, device, channels);
+        if(!*out)
+            return E_OUTOFMEMORY;
+
+        return S_OK;
+    }
+
+    *out = NULL;
+    LIST_FOR_EACH_ENTRY(session, &g_sessions, AudioSession, entry){
+        if(session->device == device &&
+                IsEqualGUID(sessionguid, &session->guid)){
+            session_init_vols(session, channels);
+            *out = session;
+            break;
+        }
+    }
+
+    if(!*out){
+        *out = create_session(sessionguid, device, channels);
+        if(!*out)
+            return E_OUTOFMEMORY;
+    }
+
+    return S_OK;
+}
+
+static DWORD get_default_channelmask(unsigned int channels)
+{
+    switch(channels){
+    case 1:
+        return KSAUDIO_SPEAKER_MONO;
+    case 2:
+        return KSAUDIO_SPEAKER_STEREO;
+    case 3:
+        return KSAUDIO_SPEAKER_STEREO | SPEAKER_LOW_FREQUENCY;
+    case 4:
+        return KSAUDIO_SPEAKER_QUAD; /* not _SURROUND */
+    case 5:
+        return KSAUDIO_SPEAKER_QUAD | SPEAKER_LOW_FREQUENCY;
+    case 6:
+        return KSAUDIO_SPEAKER_5POINT1; /* not 5POINT1_SURROUND */
+    case 7:
+        return KSAUDIO_SPEAKER_5POINT1 | SPEAKER_BACK_CENTER;
+    case 8:
+        return KSAUDIO_SPEAKER_7POINT1_SURROUND; /* Vista deprecates 7POINT1 */
+    }
+    return 0;
+}
+
+static pa_sample_format_t get_pa_sample_format(const WAVEFORMATEX *fmt)
+{
+    const WAVEFORMATEXTENSIBLE *fmtex = (const WAVEFORMATEXTENSIBLE *)fmt;
+
+    if(fmt->wFormatTag == WAVE_FORMAT_PCM ||
+            (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
+             IsEqualGUID(&fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))){
+        switch(fmt->wBitsPerSample){
+        case 8:
+            return PA_SAMPLE_U8;
+        case 16:
+            return PA_SAMPLE_S16LE;
+        case 24:
+            return PA_SAMPLE_S24LE;
+        case 32:
+            return PA_SAMPLE_S32LE;
+        }
+        return PA_SAMPLE_INVALID;
+    }
+    if(fmt->wFormatTag == WAVE_FORMAT_IEEE_FLOAT ||
+            (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
+             IsEqualGUID(&fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))){
+        if(fmt->wBitsPerSample == 32)
+            return PA_SAMPLE_FLOAT32LE;
+        return PA_SAMPLE_INVALID;
+    }
+    if(fmt->wFormatTag == WAVE_FORMAT_ALAW ||
+            (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
+             IsEqualGUID(&fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_ALAW))){
+        if(fmt->wBitsPerSample == 8)
+            return PA_SAMPLE_ALAW;
+        return PA_SAMPLE_INVALID;
+    }
+    if(fmt->wFormatTag == WAVE_FORMAT_MULAW ||
+            (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
+             IsEqualGUID(&fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_MULAW))){
+        if(fmt->wBitsPerSample == 8)
+            return PA_SAMPLE_ULAW;
+        return PA_SAMPLE_INVALID;
+    }
+    return PA_SAMPLE_INVALID;
+}
+
+static pa_channel_position_t mmdev_chan_to_pa_chan(DWORD mmdev)
+{
+    switch(mmdev){
+    case SPEAKER_FRONT_LEFT:
+        return PA_CHANNEL_POSITION_FRONT_LEFT;
+    case SPEAKER_FRONT_RIGHT:
+        return PA_CHANNEL_POSITION_FRONT_RIGHT;
+    case SPEAKER_FRONT_CENTER:
+        return PA_CHANNEL_POSITION_FRONT_CENTER;
+    case SPEAKER_LOW_FREQUENCY:
+        return PA_CHANNEL_POSITION_LFE;
+    case SPEAKER_BACK_LEFT:
+        return PA_CHANNEL_POSITION_REAR_LEFT;
+    case SPEAKER_BACK_RIGHT:
+        return PA_CHANNEL_POSITION_REAR_RIGHT;
+    case SPEAKER_FRONT_LEFT_OF_CENTER:
+        return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER;
+    case SPEAKER_FRONT_RIGHT_OF_CENTER:
+        return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
+    case SPEAKER_BACK_CENTER:
+        return PA_CHANNEL_POSITION_REAR_CENTER;
+    case SPEAKER_SIDE_LEFT:
+        return PA_CHANNEL_POSITION_SIDE_LEFT;
+    case SPEAKER_SIDE_RIGHT:
+        return PA_CHANNEL_POSITION_SIDE_RIGHT;
+    case SPEAKER_TOP_CENTER:
+        return PA_CHANNEL_POSITION_TOP_CENTER;
+    case SPEAKER_TOP_FRONT_LEFT:
+        return PA_CHANNEL_POSITION_TOP_FRONT_LEFT;
+    case SPEAKER_TOP_FRONT_CENTER:
+        return PA_CHANNEL_POSITION_TOP_FRONT_CENTER;
+    case SPEAKER_TOP_FRONT_RIGHT:
+        return PA_CHANNEL_POSITION_TOP_FRONT_RIGHT;
+    case SPEAKER_TOP_BACK_LEFT:
+        return PA_CHANNEL_POSITION_TOP_REAR_LEFT;
+    case SPEAKER_TOP_BACK_CENTER:
+        return PA_CHANNEL_POSITION_TOP_REAR_CENTER;
+    case SPEAKER_TOP_BACK_RIGHT:
+        return PA_CHANNEL_POSITION_TOP_REAR_RIGHT;
+    }
+    WARN("Unknown mmdevapi channel position: 0x%x\n", mmdev);
+    return PA_CHANNEL_POSITION_INVALID;
+}
+
+static HRESULT get_pa_sample_spec(const WAVEFORMATEX *fmt, pa_sample_spec *ss,
+        pa_channel_map *map)
+{
+    const WAVEFORMATEXTENSIBLE *fmtex = (const WAVEFORMATEXTENSIBLE *)fmt;
+
+    if(ss){
+        ss->rate = fmt->nSamplesPerSec;
+        ss->channels = fmt->nChannels;
+        ss->format = get_pa_sample_format(fmt);
+        if(ss->format == PA_SAMPLE_INVALID)
+            return AUDCLNT_E_UNSUPPORTED_FORMAT;
+    }
+
+    if(map){
+        /* MMDevAPI streams are always ordered such that the first channel
+         * in the data maps to the LSB set in dwChannelMask, and so on */
+        int i = 0, j = 0, flag = SPEAKER_FRONT_LEFT;
+        DWORD chan_mask;
+
+        pa_channel_map_init(map);
+
+        if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
+                fmtex->dwChannelMask != 0 &&
+                !(fmtex->dwChannelMask & SPEAKER_ALL))
+            chan_mask = fmtex->dwChannelMask;
+        else
+            chan_mask = get_default_channelmask(fmt->nChannels);
+
+        if(chan_mask == 0)
+            /* indicate to caller that the channel map is unused */
+            map->channels = 0;
+        else
+            map->channels = fmt->nChannels;
+
+        /* special-case mono streams */
+        if(map->channels == 1 && chan_mask == SPEAKER_FRONT_CENTER)
+            map->map[0] = PA_CHANNEL_POSITION_MONO;
+        else{
+            while(i < map->channels && !(flag & SPEAKER_RESERVED)){
+                if(chan_mask & flag){
+                    map->map[i] = mmdev_chan_to_pa_chan(flag);
+                    ++i;
+                }
+                flag <<= 1;
+            }
+            while(i < map->channels &&
+                    PA_CHANNEL_POSITION_AUX0 + j <= PA_CHANNEL_POSITION_AUX31){
+                map->map[i] = PA_CHANNEL_POSITION_AUX0 + j;
+                ++j;
+                ++i;
+            }
+            if(i < map->channels){
+                WARN("Too many channels to map (%u)! Just giving up.\n", map->channels);
+                map->channels = 0;
+            }
+        }
+    }
+
+    return S_OK;
+}
+
+static UINT32 write_data_to_pulse(ACImpl *This, UINT32 max_frames)
+{
+    DWORD to_write_frames;
+    size_t to_write_bytes;
+    BYTE *data;
+    int err;
+
+    to_write_frames = min(max_frames, This->held_frames);
+    if(!to_write_frames)
+        return 0;
+
+    to_write_bytes = to_write_frames * This->fmt->nBlockAlign;
+    data = This->local_buffer + This->lcl_offs_frames * This->fmt->nBlockAlign;
+
+    if(This->lcl_offs_frames + to_write_frames < This->bufsize_frames){
+        err = pa_stream_write(This->stream, data, to_write_bytes, NULL, 0, PA_SEEK_RELATIVE);
+        if(err != 0){
+            WARN("(%p) pa_stream_write failed: %d\n", This, err);
+            return 0;
+        }
+    }else{
+        DWORD chunk_bytes = (This->bufsize_frames - This->lcl_offs_frames) * This->fmt->nBlockAlign;
+
+        err = pa_stream_write(This->stream, data, chunk_bytes, NULL, 0, PA_SEEK_RELATIVE);
+        if(err != 0){
+            WARN("(%p) pa_stream_write failed: %d\n", This, err);
+            return 0;
+        }
+
+        err = pa_stream_write(This->stream, This->local_buffer, to_write_bytes - chunk_bytes, NULL, 0, PA_SEEK_RELATIVE);
+        if(err != 0){
+            WARN("(%p) pa_stream_write failed: %d\n", This, err);
+            return 0;
+        }
+    }
+
+    This->lcl_offs_frames += to_write_frames;
+    This->lcl_offs_frames %= This->bufsize_frames;
+    This->held_frames -= to_write_frames;
+
+    TRACE("(%p) wrote %u frames, held_frames: %u\n", This, to_write_frames, This->held_frames);
+
+    return to_write_frames;
+}
+
+/* it's not documented, but 'bytes' always seems to contain
+ * the total amount of free space in the buffer. so, we can
+ * subtract 'bytes' from the buffer size ('tlength') to get
+ * the amount of data in the buffer. */
+static void push_render_data(pa_stream *stream, size_t bytes, void *user)
+{
+    ACImpl *This = user;
+    UINT32 written_frames;
+    const pa_buffer_attr *attr;
+
+    if(!This->initted || !This->started)
+        return;
+
+    if(bytes < This->mmdev_period_frames * This->fmt->nBlockAlign)
+        return;
+
+    EnterCriticalSection(&This->lock);
+
+    written_frames = write_data_to_pulse(This, bytes / This->fmt->nBlockAlign);
+
+    attr = pa_stream_get_buffer_attr(This->stream);
+    This->in_pulse_frames = ((attr->tlength - bytes) / This->fmt->nBlockAlign) + written_frames;
+    This->pos_frames = This->written_frames - This->in_pulse_frames - This->held_frames;
+
+    LeaveCriticalSection(&This->lock);
+}
+
+static void pull_capture_data(pa_stream *stream, size_t bytes, void *user)
+{
+    ACImpl *This = user;
+    DWORD pos_bytes, bufsize_bytes = This->bufsize_frames * This->fmt->nBlockAlign, data_frames;
+    size_t data_bytes, copy;
+    const char *data;
+    int err;
+    UINT32 old_held;
+
+    if(bytes < This->mmdev_period_frames * This->fmt->nBlockAlign)
+        return;
+
+    EnterCriticalSection(&This->lock);
+
+    if(!This->started){
+        LeaveCriticalSection(&This->lock);
+        return;
+    }
+
+    old_held = This->held_frames;
+
+    pos_bytes = ((This->held_frames + This->lcl_offs_frames) * This->fmt->nBlockAlign) % bufsize_bytes;
+
+    while(pa_stream_readable_size(This->stream) > 0){
+        err = pa_stream_peek(This->stream, (const void **)&data, &data_bytes);
+        if(err != 0){
+            WARN("(%p) pa_stream_peek failed: %d\n", This, err);
+            LeaveCriticalSection(&This->lock);
+            return;
+        }
+
+        copy = min(data_bytes, bufsize_bytes - pos_bytes);
+
+        if(This->session->mute){
+            if(This->ss.format == PA_SAMPLE_U8)
+                memset(This->local_buffer + pos_bytes, 128, copy);
+            else
+                memset(This->local_buffer + pos_bytes, 0, copy);
+        }else
+            memcpy(This->local_buffer + pos_bytes, data, copy);
+
+        if(data_bytes - copy > 0){
+            if(This->session->mute){
+                if(This->ss.format == PA_SAMPLE_U8)
+                    memset(This->local_buffer, 128, data_bytes - copy);
+                else
+                    memset(This->local_buffer, 0, data_bytes - copy);
+            }else
+                memcpy(This->local_buffer, data + copy, data_bytes - copy);
+        }
+
+        data_frames = data_bytes / This->fmt->nBlockAlign;
+        This->held_frames += data_frames;
+        pos_bytes += data_bytes;
+        pos_bytes %= bufsize_bytes;
+
+        pa_stream_drop(This->stream);
+    }
+
+    if(This->held_frames > This->bufsize_frames){
+        This->lcl_offs_frames += This->held_frames;
+        This->lcl_offs_frames %= This->bufsize_frames;
+        This->held_frames = This->bufsize_frames;
+    }
+
+    This->pos_frames += This->held_frames - old_held;
+    TRACE("(%p) pos_frames: 0x%s, held_frames: %u\n", This,
+            wine_dbgstr_longlong(This->pos_frames), This->held_frames);
+
+    LeaveCriticalSection(&This->lock);
+}
+
+static void stream_state_cb(pa_stream *stream, void *user)
+{
+    pthread_cond_signal(&pulse_cond);
+}
+
+static void hooray_cb(pa_stream *stream, int success, void *user)
+{
+    int *out = user;
+    *out = success;
+    pthread_cond_signal(&pulse_cond);
+}
+
+static HRESULT WINAPI AudioClient_Initialize(IAudioClient *iface,
+        AUDCLNT_SHAREMODE mode, DWORD flags, REFERENCE_TIME duration,
+        REFERENCE_TIME period, const WAVEFORMATEX *fmt,
+        const GUID *sessionguid)
+{
+    static const char output_name[] = "Output Stream";
+    static const char input_name[] = "Input Stream";
+
+    ACImpl *This = impl_from_IAudioClient(iface);
+    HRESULT hr = S_OK;
+    pa_operation *o;
+    int err, i, success;
+    const pa_timing_info *tinfo;
+    const char *name = This->dataflow == eRender ? output_name : input_name;
+    pa_buffer_attr attr;
+
+    TRACE("(%p)->(%x, %x, 0x%s, 0x%s, %p, %s)\n", This, mode, flags,
+          wine_dbgstr_longlong(duration), wine_dbgstr_longlong(period), fmt, debugstr_guid(sessionguid));
+
+    if(FAILED(ready_pulse())){
+        ERR("PulseAudio no longer running?\n");
+        return AUDCLNT_E_SERVICE_NOT_RUNNING;
+    }
+
+    if(!fmt)
+        return E_POINTER;
+
+    if(mode != AUDCLNT_SHAREMODE_SHARED && mode != AUDCLNT_SHAREMODE_EXCLUSIVE)
+        return AUDCLNT_E_NOT_INITIALIZED;
+
+    if(flags & ~(AUDCLNT_STREAMFLAGS_CROSSPROCESS |
+                AUDCLNT_STREAMFLAGS_LOOPBACK |
+                AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
+                AUDCLNT_STREAMFLAGS_NOPERSIST |
+                AUDCLNT_STREAMFLAGS_RATEADJUST |
+                AUDCLNT_SESSIONFLAGS_EXPIREWHENUNOWNED |
+                AUDCLNT_SESSIONFLAGS_DISPLAY_HIDE |
+                AUDCLNT_SESSIONFLAGS_DISPLAY_HIDEWHENEXPIRED)){
+        TRACE("Unknown flags: %08x\n", flags);
+        return E_INVALIDARG;
+    }
+
+    if(mode == AUDCLNT_SHAREMODE_SHARED){
+        period = DefaultPeriod;
+        if( duration < 3 * period)
+            duration = 3 * period;
+    }else{
+        if(!period)
+            period = DefaultPeriod; /* not minimum */
+        if(period < MinimumPeriod || period > 5000000)
+            return AUDCLNT_E_INVALID_DEVICE_PERIOD;
+        if(duration > 20000000) /* the smaller the period, the lower this limit */
+            return AUDCLNT_E_BUFFER_SIZE_ERROR;
+        if(flags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK){
+            if(duration != period)
+                return AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL;
+            FIXME("EXCLUSIVE mode with EVENTCALLBACK\n");
+            return AUDCLNT_E_DEVICE_IN_USE;
+        }else{
+            if( duration < 8 * period)
+                duration = 8 * period; /* may grow above 2s */
+        }
+    }
+
+    EnterCriticalSection(&This->lock);
+
+    if(This->initted){
+        LeaveCriticalSection(&This->lock);
+        return AUDCLNT_E_ALREADY_INITIALIZED;
+    }
+
+    dump_fmt(fmt);
+
+    if(FAILED(get_pa_sample_spec(fmt, &This->ss, &This->map))){
+        LeaveCriticalSection(&This->lock);
+        return AUDCLNT_E_UNSUPPORTED_FORMAT;
+    }
+
+    This->mmdev_period_rt = period;
+
+    This->mmdev_period_frames = MulDiv(fmt->nSamplesPerSec,
+            This->mmdev_period_rt, 10000000);
+
+    This->bufsize_frames = MulDiv(duration, fmt->nSamplesPerSec, 10000000);
+    This->local_buffer = HeapAlloc(GetProcessHeap(), 0, This->bufsize_frames * fmt->nBlockAlign);
+
+    attr.tlength = attr.fragsize = This->mmdev_period_frames * fmt->nBlockAlign;
+    attr.maxlength = -1;
+    attr.minreq = -1;
+    attr.prebuf = fmt->nBlockAlign;
+
+    pthread_mutex_lock(&pulse_lock);
+
+    This->stream = pa_stream_new(pulse_ctx, name, &This->ss,
+            This->map.channels == 0 ? NULL : &This->map);
+    if(!This->stream){
+        WARN("pa_stream_new failed\n");
+        pthread_mutex_unlock(&pulse_lock);
+        LeaveCriticalSection(&This->lock);
+        return AUDCLNT_E_DEVICE_INVALIDATED;
+    }
+
+    pa_stream_set_state_callback(This->stream, stream_state_cb, This);
+
+    if(This->dataflow == eRender)
+        pa_stream_set_write_callback(This->stream, push_render_data, This);
+    else
+        pa_stream_set_read_callback(This->stream, pull_capture_data, This);
+
+    if(This->dataflow == eRender)
+        err = pa_stream_connect_playback(This->stream, NULL, &attr,
+                PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING |
+                PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_EARLY_REQUESTS,
+                NULL, NULL);
+    else
+        err = pa_stream_connect_record(This->stream, NULL, &attr,
+                PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING |
+                PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_EARLY_REQUESTS);
+    if(err != 0){
+        WARN("pa_stream_connect failed: %d\n", err);
+        pa_stream_unref(This->stream);
+        This->stream = NULL;
+        pthread_mutex_unlock(&pulse_lock);
+        LeaveCriticalSection(&This->lock);
+        return AUDCLNT_E_DEVICE_INVALIDATED;
+    }
+
+    while(pa_stream_get_state(This->stream) == PA_STREAM_CREATING)
+        pthread_cond_wait(&pulse_cond, &pulse_lock);
+
+    if(pa_stream_get_state(This->stream) != PA_STREAM_READY){
+        WARN("pa_stream_connect operation failed\n");
+        pa_stream_unref(This->stream);
+        This->stream = NULL;
+        pthread_mutex_unlock(&pulse_lock);
+        LeaveCriticalSection(&This->lock);
+        return AUDCLNT_E_DEVICE_INVALIDATED;
+    }
+
+    o = pa_stream_update_timing_info(This->stream, &hooray_cb, &success);
+    if(!o){
+        WARN("pa_stream_update_timing_info failed\n");
+        pthread_mutex_unlock(&pulse_lock);
+        hr = E_FAIL;
+        goto exit;
+    }
+
+    while(pa_operation_get_state(o) == PA_OPERATION_RUNNING)
+        pthread_cond_wait(&pulse_cond, &pulse_lock);
+
+    if(pa_operation_get_state(o) != PA_OPERATION_DONE || !success){
+        WARN("pa_stream_update_timing_info didn't succeed\n");
+        pthread_mutex_unlock(&pulse_lock);
+        pa_operation_unref(o);
+        hr = E_FAIL;
+        goto exit;
+    }
+
+    pa_operation_unref(o);
+
+    tinfo = pa_stream_get_timing_info(This->stream);
+    if(tinfo){
+        This->latency_rt = DefaultPeriod + (tinfo->transport_usec + tinfo->sink_usec) * 10;
+    }else{
+        WARN("pa_stream_get_latency failed (%d), faking it\n", err);
+        This->latency_rt = DefaultPeriod * 3;
+    }
+
+    pthread_mutex_unlock(&pulse_lock);
+
+    This->latency_frames = MulDiv(fmt->nSamplesPerSec,
+            This->latency_rt, 10000000);
+
+    This->fmt = clone_format(fmt);
+    if(!This->fmt){
+        hr = E_OUTOFMEMORY;
+        goto exit;
+    }
+
+    This->vols = HeapAlloc(GetProcessHeap(), 0, fmt->nChannels * sizeof(float));
+    if(!This->vols){
+        hr = E_OUTOFMEMORY;
+        goto exit;
+    }
+
+    for(i = 0; i < fmt->nChannels; ++i)
+        This->vols[i] = 1.f;
+
+    This->share = mode;
+    This->flags = flags;
+
+    EnterCriticalSection(&g_sessions_lock);
+
+    hr = get_audio_session(sessionguid, This->parent, fmt->nChannels,
+            &This->session);
+    if(FAILED(hr)){
+        LeaveCriticalSection(&g_sessions_lock);
+        goto exit;
+    }
+
+    list_add_tail(&This->session->clients, &This->entry);
+
+    LeaveCriticalSection(&g_sessions_lock);
+
+    This->initted = TRUE;
+
+    TRACE("MMDevice period: %u frames\n", This->mmdev_period_frames);
+    TRACE("MMDevice buffer: %u frames\n", This->bufsize_frames);
+
+exit:
+    if(FAILED(hr)){
+        pthread_mutex_lock(&pulse_lock);
+        pa_stream_disconnect(This->stream);
+        pa_stream_unref(This->stream);
+        pthread_mutex_unlock(&pulse_lock);
+        This->stream = NULL;
+        CoTaskMemFree(This->fmt);
+        This->fmt = NULL;
+        HeapFree(GetProcessHeap(), 0, This->vols);
+        This->vols = NULL;
+        HeapFree(GetProcessHeap(), 0, This->local_buffer);
+        This->local_buffer = NULL;
+    }
+
+    LeaveCriticalSection(&This->lock);
+
+    return hr;
+}
+
+static HRESULT WINAPI AudioClient_GetBufferSize(IAudioClient *iface,
+        UINT32 *out)
+{
+    ACImpl *This = impl_from_IAudioClient(iface);
+
+    TRACE("(%p)->(%p)\n", This, out);
+
+    if(!out)
+        return E_POINTER;
+
+    EnterCriticalSection(&This->lock);
+
+    if(!This->initted){
+        LeaveCriticalSection(&This->lock);
+        return AUDCLNT_E_NOT_INITIALIZED;
+    }
+
+    *out = This->bufsize_frames;
+
+    LeaveCriticalSection(&This->lock);
+
+    return S_OK;
+}
+
+static HRESULT WINAPI AudioClient_GetStreamLatency(IAudioClient *iface,
+        REFERENCE_TIME *latency)
+{
+    ACImpl *This = impl_from_IAudioClient(iface);
+
+    TRACE("(%p)->(%p)\n", This, latency);
+
+    if(!latency)
+        return E_POINTER;
+
+    EnterCriticalSection(&This->lock);
+
+    if(!This->initted){
+        LeaveCriticalSection(&This->lock);
+        return AUDCLNT_E_NOT_INITIALIZED;
+    }
+
+    *latency = This->latency_rt + DefaultPeriod;
+
+    LeaveCriticalSection(&This->lock);
+
+    return S_OK;
+}
+
+static HRESULT WINAPI AudioClient_GetCurrentPadding(IAudioClient *iface,
+        UINT32 *out)
+{
+    ACImpl *This = impl_from_IAudioClient(iface);
+
+    TRACE("(%p)->(%p)\n", This, out);
+
+    if(!out)
+        return E_POINTER;
+
+    EnterCriticalSection(&This->lock);
+
+    if(!This->initted){
+        LeaveCriticalSection(&This->lock);
+        return AUDCLNT_E_NOT_INITIALIZED;
+    }
+
+    *out = This->in_pulse_frames + This->held_frames;
+    TRACE("pad: %u\n", *out);
+
+    LeaveCriticalSection(&This->lock);
+
+    return S_OK;
+}
+
+static HRESULT WINAPI AudioClient_IsFormatSupported(IAudioClient *iface,
+        AUDCLNT_SHAREMODE mode, const WAVEFORMATEX *fmt,
+        WAVEFORMATEX **out)
+{
+    ACImpl *This = impl_from_IAudioClient(iface);
+    pa_sample_spec ss;
+
+    TRACE("(%p)->(%x, %p, %p)\n", This, mode, fmt, out);
+
+    if(!fmt || (mode == AUDCLNT_SHAREMODE_SHARED && !out))
+        return E_POINTER;
+
+    if(mode != AUDCLNT_SHAREMODE_SHARED && mode != AUDCLNT_SHAREMODE_EXCLUSIVE)
+        return E_INVALIDARG;
+
+    if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
+            fmt->cbSize < sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX))
+        return E_INVALIDARG;
+
+    dump_fmt(fmt);
+
+    if(out){
+        *out = NULL;
+        if(mode != AUDCLNT_SHAREMODE_SHARED)
+            out = NULL;
+    }
+
+    if(FAILED(get_pa_sample_spec(fmt, &ss, NULL)))
+        return AUDCLNT_E_UNSUPPORTED_FORMAT;
+
+    if(!pa_sample_spec_valid(&ss)){
+        TRACE("invalid\n");
+        return AUDCLNT_E_UNSUPPORTED_FORMAT;
+    }
+
+    TRACE("valid\n");
+    return S_OK;
+}
+
+static HRESULT WINAPI AudioClient_GetMixFormat(IAudioClient *iface,
+        WAVEFORMATEX **pwfx)
+{
+    ACImpl *This = impl_from_IAudioClient(iface);
+    WAVEFORMATEXTENSIBLE *fmt;
+
+    TRACE("(%p)->(%p)\n", This, pwfx);
+
+    if(!pwfx)
+        return E_POINTER;
+    *pwfx = NULL;
+
+    fmt = CoTaskMemAlloc(sizeof(WAVEFORMATEXTENSIBLE));
+    if(!fmt)
+        return E_OUTOFMEMORY;
+
+    /* It's possible for GetMixFormat to be called from within the
+     * loader CS, so we can't connect to the pulse server to get
+     * the actual server format.  So we just fake it to a common
+     * format. */
+    fmt->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+    fmt->Format.nChannels = 2;
+    fmt->Format.nSamplesPerSec = 44100;
+    fmt->Format.wBitsPerSample = 16;
+    fmt->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
+    fmt->Format.nBlockAlign = (fmt->Format.wBitsPerSample *
+            fmt->Format.nChannels) / 8;
+    fmt->Format.nAvgBytesPerSec = fmt->Format.nSamplesPerSec *
+        fmt->Format.nBlockAlign;
+    fmt->Samples.wValidBitsPerSample = fmt->Format.wBitsPerSample;
+    fmt->dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
+    fmt->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+
+    dump_fmt((WAVEFORMATEX*)fmt);
+    *pwfx = (WAVEFORMATEX*)fmt;
+
+    return S_OK;
+}
+
+static HRESULT WINAPI AudioClient_GetDevicePeriod(IAudioClient *iface,
+        REFERENCE_TIME *defperiod, REFERENCE_TIME *minperiod)
+{
+    ACImpl *This = impl_from_IAudioClient(iface);
+
+    TRACE("(%p)->(%p, %p)\n", This, defperiod, minperiod);
+
+    if(!defperiod && !minperiod)
+        return E_POINTER;
+
+    if(defperiod)
+        *defperiod = DefaultPeriod;
+    if(minperiod)
+        *minperiod = MinimumPeriod;
+
+    return S_OK;
+}
+
+static void CALLBACK timer_cb(void *user, BOOLEAN timer)
+{
+    ACImpl *This = user;
+
+    EnterCriticalSection(&This->lock);
+
+    if(This->started)
+        if(This->event)
+            SetEvent(This->event);
+
+    LeaveCriticalSection(&This->lock);
+}
+
+static HRESULT WINAPI AudioClient_Start(IAudioClient *iface)
+{
+    ACImpl *This = impl_from_IAudioClient(iface);
+    int success;
+    pa_operation *o;
+
+    TRACE("(%p)\n", This);
+
+    pthread_mutex_lock(&pulse_lock);
+
+    EnterCriticalSection(&This->lock);
+
+    if(!This->initted){
+        LeaveCriticalSection(&This->lock);
+        pthread_mutex_unlock(&pulse_lock);
+        return AUDCLNT_E_NOT_INITIALIZED;
+    }
+
+    if((This->flags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) && !This->event){
+        LeaveCriticalSection(&This->lock);
+        pthread_mutex_unlock(&pulse_lock);
+        return AUDCLNT_E_EVENTHANDLE_NOT_SET;
+    }
+
+    if(This->started){
+        LeaveCriticalSection(&This->lock);
+        pthread_mutex_unlock(&pulse_lock);
+        return AUDCLNT_E_NOT_STOPPED;
+    }
+
+    o = pa_stream_cork(This->stream, 0, hooray_cb, &success);
+    if(!o){
+        WARN("pa_stream_cork(0) failed\n");
+        LeaveCriticalSection(&This->lock);
+        pthread_mutex_unlock(&pulse_lock);
+        return E_FAIL;
+    }
+
+    while(pa_operation_get_state(o) == PA_OPERATION_RUNNING)
+        pthread_cond_wait(&pulse_cond, &pulse_lock);
+
+    if(pa_operation_get_state(o) != PA_OPERATION_DONE || !success){
+        LeaveCriticalSection(&This->lock);
+        pthread_mutex_unlock(&pulse_lock);
+        pa_operation_unref(o);
+        WARN("pa_stream_cork(0) operation failed\n");
+        return E_FAIL;
+    }
+
+    pa_operation_unref(o);
+
+    if(!CreateTimerQueueTimer(&This->timer, g_timer_q, timer_cb,
+            This, 0, This->mmdev_period_rt / 10000, WT_EXECUTEINTIMERTHREAD)){
+        LeaveCriticalSection(&This->lock);
+        pthread_mutex_unlock(&pulse_lock);
+        WARN("Unable to create timer: %u\n", GetLastError());
+        return E_OUTOFMEMORY;
+    }
+
+    This->started = TRUE;
+
+    LeaveCriticalSection(&This->lock);
+    pthread_mutex_unlock(&pulse_lock);
+
+    return S_OK;
+}
+
+static HRESULT WINAPI AudioClient_Stop(IAudioClient *iface)
+{
+    ACImpl *This = impl_from_IAudioClient(iface);
+    int success;
+    pa_operation *o;
+    HANDLE event;
+    DWORD wait;
+
+    TRACE("(%p)\n", This);
+
+    pthread_mutex_lock(&pulse_lock);
+
+    EnterCriticalSection(&This->lock);
+
+    if(!This->initted){
+        LeaveCriticalSection(&This->lock);
+        pthread_mutex_unlock(&pulse_lock);
+        return AUDCLNT_E_NOT_INITIALIZED;
+    }
+
+    if(!This->started){
+        LeaveCriticalSection(&This->lock);
+        pthread_mutex_unlock(&pulse_lock);
+        return S_FALSE;
+    }
+
+    This->started = FALSE;
+
+    o = pa_stream_cork(This->stream, 1, hooray_cb, &success);
+    if(!o){
+        WARN("pa_stream_cork(1) failed\n");
+        pthread_mutex_unlock(&pulse_lock);
+        LeaveCriticalSection(&This->lock);
+        return E_FAIL;
+    }
+
+    LeaveCriticalSection(&This->lock);
+    while(pa_operation_get_state(o) == PA_OPERATION_RUNNING)
+        pthread_cond_wait(&pulse_cond, &pulse_lock);
+    EnterCriticalSection(&This->lock);
+
+    if(pa_operation_get_state(o) != PA_OPERATION_DONE){
+        pthread_mutex_unlock(&pulse_lock);
+        LeaveCriticalSection(&This->lock);
+        pa_operation_unref(o);
+        WARN("pa_stream_cork(1) operation failed\n");
+        return E_FAIL;
+    }
+
+    pa_operation_unref(o);
+
+    if(!success){
+        WARN("pa_stream_cork(1) didn't succeed\n");
+        LeaveCriticalSection(&This->lock);
+        pthread_mutex_unlock(&pulse_lock);
+        return E_FAIL;
+    }
+
+    event = CreateEventW(NULL, TRUE, FALSE, NULL);
+    wait = !DeleteTimerQueueTimer(g_timer_q, This->timer, event);
+    if(wait)
+        WARN("DeleteTimerQueueTimer error %u\n", GetLastError());
+    wait = wait && GetLastError() == ERROR_IO_PENDING;
+
+    LeaveCriticalSection(&This->lock);
+
+    pthread_mutex_unlock(&pulse_lock);
+
+    if(event && wait)
+        WaitForSingleObject(event, INFINITE);
+    CloseHandle(event);
+
+    return S_OK;
+}
+
+static HRESULT WINAPI AudioClient_Reset(IAudioClient *iface)
+{
+    ACImpl *This = impl_from_IAudioClient(iface);
+    pa_operation *o;
+    int success;
+
+    TRACE("(%p)\n", This);
+
+    pthread_mutex_lock(&pulse_lock);
+
+    EnterCriticalSection(&This->lock);
+
+    if(!This->initted){
+        LeaveCriticalSection(&This->lock);
+        pthread_mutex_unlock(&pulse_lock);
+        return AUDCLNT_E_NOT_INITIALIZED;
+    }
+
+    if(This->started){
+        LeaveCriticalSection(&This->lock);
+        pthread_mutex_unlock(&pulse_lock);
+        return AUDCLNT_E_NOT_STOPPED;
+    }
+
+    if(This->getbuf_last){
+        LeaveCriticalSection(&This->lock);
+        pthread_mutex_unlock(&pulse_lock);
+        return AUDCLNT_E_BUFFER_OPERATION_PENDING;
+    }
+
+    if(This->dataflow == eRender){
+        This->in_pulse_frames = 0;
+        This->held_frames = 0;
+        This->pos_frames = 0;
+        This->written_frames = 0;
+
+        o = pa_stream_flush(This->stream, hooray_cb, &success);
+        if(!o){
+            WARN("pa_stream_flush failed\n");
+            LeaveCriticalSection(&This->lock);
+            pthread_mutex_unlock(&pulse_lock);
+            return E_FAIL;
+        }
+
+        while(pa_operation_get_state(o) == PA_OPERATION_RUNNING)
+            pthread_cond_wait(&pulse_cond, &pulse_lock);
+
+        if(pa_operation_get_state(o) != PA_OPERATION_DONE || !success)
+            WARN("pa_stream_flush operation failed\n");
+
+        pa_operation_unref(o);
+    }else{
+        const void *data;
+        size_t data_size;
+
+        This->pos_frames += This->held_frames;
+        This->held_frames = 0;
+        This->lcl_offs_frames = 0;
+
+        /* PA doesn't provide a method to empty the capture buffer,
+         * so do it the hard way. */
+        while(pa_stream_readable_size(This->stream) > 0){
+            pa_stream_peek(This->stream, &data, &data_size);
+            pa_stream_drop(This->stream);
+        }
+    }
+
+    LeaveCriticalSection(&This->lock);
+
+    pthread_mutex_unlock(&pulse_lock);
+
+    return S_OK;
+}
+
+static HRESULT WINAPI AudioClient_SetEventHandle(IAudioClient *iface,
+        HANDLE event)
+{
+    ACImpl *This = impl_from_IAudioClient(iface);
+
+    TRACE("(%p)->(%p)\n", This, event);
+
+    if(!event)
+        return E_INVALIDARG;
+
+    EnterCriticalSection(&This->lock);
+
+    if(!This->initted){
+        LeaveCriticalSection(&This->lock);
+        return AUDCLNT_E_NOT_INITIALIZED;
+    }
+
+    if(!(This->flags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK)){
+        LeaveCriticalSection(&This->lock);
+        return AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED;
+    }
+
+    This->event = event;
+
+    LeaveCriticalSection(&This->lock);
+
+    return S_OK;
+}
+
+static HRESULT WINAPI AudioClient_GetService(IAudioClient *iface, REFIID riid,
+        void **ppv)
+{
+    ACImpl *This = impl_from_IAudioClient(iface);
+
+    TRACE("(%p)->(%s, %p)\n", This, debugstr_guid(riid), ppv);
+
+    if(!ppv)
+        return E_POINTER;
+    *ppv = NULL;
+
+    EnterCriticalSection(&This->lock);
+
+    if(!This->initted){
+        LeaveCriticalSection(&This->lock);
+        return AUDCLNT_E_NOT_INITIALIZED;
+    }
+
+    if(IsEqualIID(riid, &IID_IAudioRenderClient)){
+        if(This->dataflow != eRender){
+            LeaveCriticalSection(&This->lock);
+            return AUDCLNT_E_WRONG_ENDPOINT_TYPE;
+        }
+        IAudioRenderClient_AddRef(&This->IAudioRenderClient_iface);
+        *ppv = &This->IAudioRenderClient_iface;
+    }else if(IsEqualIID(riid, &IID_IAudioCaptureClient)){
+        if(This->dataflow != eCapture){
+            LeaveCriticalSection(&This->lock);
+            return AUDCLNT_E_WRONG_ENDPOINT_TYPE;
+        }
+        IAudioCaptureClient_AddRef(&This->IAudioCaptureClient_iface);
+        *ppv = &This->IAudioCaptureClient_iface;
+    }else if(IsEqualIID(riid, &IID_IAudioClock)){
+        IAudioClock_AddRef(&This->IAudioClock_iface);
+        *ppv = &This->IAudioClock_iface;
+    }else if(IsEqualIID(riid, &IID_IAudioStreamVolume)){
+        IAudioStreamVolume_AddRef(&This->IAudioStreamVolume_iface);
+        *ppv = &This->IAudioStreamVolume_iface;
+    }else if(IsEqualIID(riid, &IID_IAudioSessionControl)){
+        if(!This->session_wrapper){
+            This->session_wrapper = AudioSessionWrapper_Create(This);
+            if(!This->session_wrapper){
+                LeaveCriticalSection(&This->lock);
+                return E_OUTOFMEMORY;
+            }
+        }else
+            IAudioSessionControl2_AddRef(&This->session_wrapper->IAudioSessionControl2_iface);
+
+        *ppv = &This->session_wrapper->IAudioSessionControl2_iface;
+    }else if(IsEqualIID(riid, &IID_IChannelAudioVolume)){
+        if(!This->session_wrapper){
+            This->session_wrapper = AudioSessionWrapper_Create(This);
+            if(!This->session_wrapper){
+                LeaveCriticalSection(&This->lock);
+                return E_OUTOFMEMORY;
+            }
+        }else
+            IChannelAudioVolume_AddRef(&This->session_wrapper->IChannelAudioVolume_iface);
+
+        *ppv = &This->session_wrapper->IChannelAudioVolume_iface;
+    }else if(IsEqualIID(riid, &IID_ISimpleAudioVolume)){
+        if(!This->session_wrapper){
+            This->session_wrapper = AudioSessionWrapper_Create(This);
+            if(!This->session_wrapper){
+                LeaveCriticalSection(&This->lock);
+                return E_OUTOFMEMORY;
+            }
+        }else
+            ISimpleAudioVolume_AddRef(&This->session_wrapper->ISimpleAudioVolume_iface);
+
+        *ppv = &This->session_wrapper->ISimpleAudioVolume_iface;
+    }
+
+    if(*ppv){
+        LeaveCriticalSection(&This->lock);
+        return S_OK;
+    }
+
+    LeaveCriticalSection(&This->lock);
+
+    TRACE("No interface: %s\n", debugstr_guid(riid));
+    return E_NOINTERFACE;
+}
+
+static const IAudioClientVtbl AudioClient_Vtbl =
+{
+    AudioClient_QueryInterface,
+    AudioClient_AddRef,
+    AudioClient_Release,
+    AudioClient_Initialize,
+    AudioClient_GetBufferSize,
+    AudioClient_GetStreamLatency,
+    AudioClient_GetCurrentPadding,
+    AudioClient_IsFormatSupported,
+    AudioClient_GetMixFormat,
+    AudioClient_GetDevicePeriod,
+    AudioClient_Start,
+    AudioClient_Stop,
+    AudioClient_Reset,
+    AudioClient_SetEventHandle,
+    AudioClient_GetService
+};
+
+static HRESULT WINAPI AudioRenderClient_QueryInterface(
+        IAudioRenderClient *iface, REFIID riid, void **ppv)
+{
+    TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv);
+
+    if(!ppv)
+        return E_POINTER;
+    *ppv = NULL;
+
+    if(IsEqualIID(riid, &IID_IUnknown) ||
+            IsEqualIID(riid, &IID_IAudioRenderClient))
+        *ppv = iface;
+    if(*ppv){
+        IUnknown_AddRef((IUnknown*)*ppv);
+        return S_OK;
+    }
+
+    WARN("Unknown interface %s\n", debugstr_guid(riid));
+    return E_NOINTERFACE;
+}
+
+static ULONG WINAPI AudioRenderClient_AddRef(IAudioRenderClient *iface)
+{
+    ACImpl *This = impl_from_IAudioRenderClient(iface);
+    return AudioClient_AddRef(&This->IAudioClient_iface);
+}
+
+static ULONG WINAPI AudioRenderClient_Release(IAudioRenderClient *iface)
+{
+    ACImpl *This = impl_from_IAudioRenderClient(iface);
+    return AudioClient_Release(&This->IAudioClient_iface);
+}
+
+static HRESULT WINAPI AudioRenderClient_GetBuffer(IAudioRenderClient *iface,
+        UINT32 frames, BYTE **data)
+{
+    ACImpl *This = impl_from_IAudioRenderClient(iface);
+    UINT32 write_pos;
+
+    TRACE("(%p)->(%u, %p)\n", This, frames, data);
+
+    if(!data)
+        return E_POINTER;
+    *data = NULL;
+
+    EnterCriticalSection(&This->lock);
+
+    if(This->getbuf_last){
+        LeaveCriticalSection(&This->lock);
+        return AUDCLNT_E_OUT_OF_ORDER;
+    }
+
+    if(!frames){
+        LeaveCriticalSection(&This->lock);
+        return S_OK;
+    }
+
+    if(This->held_frames + This->in_pulse_frames + frames > This->bufsize_frames){
+        LeaveCriticalSection(&This->lock);
+        return AUDCLNT_E_BUFFER_TOO_LARGE;
+    }
+
+    write_pos =
+        (This->lcl_offs_frames + This->held_frames) % This->bufsize_frames;
+    if(write_pos + frames > This->bufsize_frames){
+        if(This->tmp_buffer_frames < frames){
+            HeapFree(GetProcessHeap(), 0, This->tmp_buffer);
+            This->tmp_buffer = HeapAlloc(GetProcessHeap(), 0,
+                    frames * This->fmt->nBlockAlign);
+            if(!This->tmp_buffer){
+                LeaveCriticalSection(&This->lock);
+                return E_OUTOFMEMORY;
+            }
+            This->tmp_buffer_frames = frames;
+        }
+        *data = This->tmp_buffer;
+        This->getbuf_last = -frames;
+    }else{
+        *data = This->local_buffer + write_pos * This->fmt->nBlockAlign;
+        This->getbuf_last = frames;
+    }
+
+    LeaveCriticalSection(&This->lock);
+
+    return S_OK;
+}
+
+static HRESULT WINAPI AudioRenderClient_ReleaseBuffer(
+        IAudioRenderClient *iface, UINT32 written_frames, DWORD flags)
+{
+    ACImpl *This = impl_from_IAudioRenderClient(iface);
+    DWORD written_bytes;
+    BYTE *buffer;
+
+    TRACE("(%p)->(%u, %x)\n", This, written_frames, flags);
+
+    pthread_mutex_lock(&pulse_lock);
+
+    EnterCriticalSection(&This->lock);
+
+    if(!written_frames){
+        This->getbuf_last = 0;
+        LeaveCriticalSection(&This->lock);
+        pthread_mutex_unlock(&pulse_lock);
+        return S_OK;
+    }
+
+    if(!This->getbuf_last){
+        LeaveCriticalSection(&This->lock);
+        pthread_mutex_unlock(&pulse_lock);
+        return AUDCLNT_E_OUT_OF_ORDER;
+    }
+
+    if(written_frames > (This->getbuf_last >= 0 ? This->getbuf_last : -This->getbuf_last)){
+        LeaveCriticalSection(&This->lock);
+        pthread_mutex_unlock(&pulse_lock);
+        return AUDCLNT_E_INVALID_SIZE;
+    }
+
+    written_bytes = written_frames * This->fmt->nBlockAlign;
+
+    if(This->getbuf_last >= 0)
+        buffer = This->local_buffer + This->fmt->nBlockAlign *
+          ((This->lcl_offs_frames + This->held_frames) % This->bufsize_frames);
+    else
+        buffer = This->tmp_buffer;
+
+    if(flags & AUDCLNT_BUFFERFLAGS_SILENT){
+        if(This->ss.format == PA_SAMPLE_U8)
+            memset(buffer, 128, written_bytes);
+        else
+            memset(buffer, 0, written_bytes);
+    }
+
+    if(This->getbuf_last < 0){
+        DWORD write_offs_frames = This->lcl_offs_frames + This->held_frames % This->bufsize_frames;
+        DWORD write_offs_bytes = write_offs_frames * This->fmt->nBlockAlign;
+        DWORD chunk_bytes = (This->bufsize_frames - write_offs_frames) * This->fmt->nBlockAlign;
+        if(written_bytes < chunk_bytes)
+            memcpy(This->local_buffer + write_offs_bytes, buffer, written_bytes);
+        else{
+            memcpy(This->local_buffer + write_offs_bytes, buffer, chunk_bytes);
+            memcpy(This->local_buffer, buffer + chunk_bytes, written_bytes - chunk_bytes);
+        }
+    }
+
+    This->written_frames += written_frames;
+    This->held_frames += written_frames;
+    This->getbuf_last = 0;
+
+    if(!This->in_pulse_frames)
+        This->in_pulse_frames = write_data_to_pulse(This,
+                pa_stream_writable_size(This->stream) / This->fmt->nBlockAlign);
+
+    TRACE("in_pulse_frames: %u, pos_frames: 0x%s, written_frames: 0x%s, held_frames: %u\n",
+            This->in_pulse_frames, wine_dbgstr_longlong(This->pos_frames),
+            wine_dbgstr_longlong(This->written_frames), This->held_frames);
+
+    LeaveCriticalSection(&This->lock);
+
+    pthread_mutex_unlock(&pulse_lock);
+
+    return S_OK;
+}
+
+static const IAudioRenderClientVtbl AudioRenderClient_Vtbl = {
+    AudioRenderClient_QueryInterface,
+    AudioRenderClient_AddRef,
+    AudioRenderClient_Release,
+    AudioRenderClient_GetBuffer,
+    AudioRenderClient_ReleaseBuffer
+};
+
+static HRESULT WINAPI AudioCaptureClient_QueryInterface(
+        IAudioCaptureClient *iface, REFIID riid, void **ppv)
+{
+    TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv);
+
+    if(!ppv)
+        return E_POINTER;
+    *ppv = NULL;
+
+    if(IsEqualIID(riid, &IID_IUnknown) ||
+            IsEqualIID(riid, &IID_IAudioCaptureClient))
+        *ppv = iface;
+    if(*ppv){
+        IUnknown_AddRef((IUnknown*)*ppv);
+        return S_OK;
+    }
+
+    WARN("Unknown interface %s\n", debugstr_guid(riid));
+    return E_NOINTERFACE;
+}
+
+static ULONG WINAPI AudioCaptureClient_AddRef(IAudioCaptureClient *iface)
+{
+    ACImpl *This = impl_from_IAudioCaptureClient(iface);
+    return IAudioClient_AddRef(&This->IAudioClient_iface);
+}
+
+static ULONG WINAPI AudioCaptureClient_Release(IAudioCaptureClient *iface)
+{
+    ACImpl *This = impl_from_IAudioCaptureClient(iface);
+    return IAudioClient_Release(&This->IAudioClient_iface);
+}
+
+static HRESULT WINAPI AudioCaptureClient_GetBuffer(IAudioCaptureClient *iface,
+        BYTE **data, UINT32 *frames, DWORD *flags, UINT64 *devpos,
+        UINT64 *qpcpos)
+{
+    ACImpl *This = impl_from_IAudioCaptureClient(iface);
+
+    TRACE("(%p)->(%p, %p, %p, %p, %p)\n", This, data, frames, flags,
+            devpos, qpcpos);
+
+    if(!data || !frames || !flags)
+        return E_POINTER;
+
+    EnterCriticalSection(&This->lock);
+
+    if(This->getbuf_last){
+        LeaveCriticalSection(&This->lock);
+        return AUDCLNT_E_OUT_OF_ORDER;
+    }
+
+    if(This->held_frames < This->mmdev_period_frames){
+        *frames = 0;
+        LeaveCriticalSection(&This->lock);
+        return AUDCLNT_S_BUFFER_EMPTY;
+    }
+
+    *flags = 0;
+
+    *frames = This->mmdev_period_frames;
+
+    if(This->bufsize_frames - This->lcl_offs_frames >= *frames)
+        *data = This->local_buffer + This->lcl_offs_frames * This->fmt->nBlockAlign;
+    else{
+        DWORD copy_bytes = (This->bufsize_frames - This->lcl_offs_frames) * This->fmt->nBlockAlign;
+
+        if(This->tmp_buffer_frames < *frames){
+            This->tmp_buffer_frames = *frames;
+            if(This->tmp_buffer)
+                This->tmp_buffer = HeapReAlloc(GetProcessHeap(), 0, This->tmp_buffer, *frames * This->fmt->nBlockAlign);
+            else
+                This->tmp_buffer = HeapAlloc(GetProcessHeap(), 0, *frames * This->fmt->nBlockAlign);
+        }
+
+        memcpy(This->tmp_buffer, This->local_buffer + This->lcl_offs_frames * This->fmt->nBlockAlign, copy_bytes);
+
+        memcpy(This->tmp_buffer + copy_bytes, This->local_buffer, (*frames * This->fmt->nBlockAlign) - copy_bytes);
+
+        *data = This->tmp_buffer;
+    }
+
+    This->getbuf_last = *frames;
+
+    if(devpos)
+      *devpos = This->pos_frames - This->held_frames;
+    if(qpcpos){ /* fixme: qpc of recording time */
+        LARGE_INTEGER stamp, freq;
+        QueryPerformanceCounter(&stamp);
+        QueryPerformanceFrequency(&freq);
+        *qpcpos = (stamp.QuadPart * (INT64)10000000) / freq.QuadPart;
+    }
+
+    LeaveCriticalSection(&This->lock);
+
+    return S_OK;
+}
+
+static HRESULT WINAPI AudioCaptureClient_ReleaseBuffer(
+        IAudioCaptureClient *iface, UINT32 done)
+{
+    ACImpl *This = impl_from_IAudioCaptureClient(iface);
+
+    TRACE("(%p)->(%u)\n", This, done);
+
+    EnterCriticalSection(&This->lock);
+
+    if(!done){
+        This->getbuf_last = 0;
+        LeaveCriticalSection(&This->lock);
+        return S_OK;
+    }
+
+    if(!This->getbuf_last){
+        LeaveCriticalSection(&This->lock);
+        return AUDCLNT_E_OUT_OF_ORDER;
+    }
+
+    if(This->getbuf_last != done){
+        LeaveCriticalSection(&This->lock);
+        return AUDCLNT_E_INVALID_SIZE;
+    }
+
+    This->held_frames -= done;
+    This->lcl_offs_frames += done;
+    This->lcl_offs_frames %= This->bufsize_frames;
+    This->getbuf_last = 0;
+
+    LeaveCriticalSection(&This->lock);
+
+    return S_OK;
+}
+
+static HRESULT WINAPI AudioCaptureClient_GetNextPacketSize(
+        IAudioCaptureClient *iface, UINT32 *frames)
+{
+    ACImpl *This = impl_from_IAudioCaptureClient(iface);
+
+    TRACE("(%p)->(%p)\n", This, frames);
+
+    if(!frames)
+        return E_POINTER;
+
+    EnterCriticalSection(&This->lock);
+
+    *frames = This->held_frames < This->mmdev_period_frames ? 0 : This->mmdev_period_frames;
+
+    LeaveCriticalSection(&This->lock);
+
+    return S_OK;
+}
+
+static const IAudioCaptureClientVtbl AudioCaptureClient_Vtbl =
+{
+    AudioCaptureClient_QueryInterface,
+    AudioCaptureClient_AddRef,
+    AudioCaptureClient_Release,
+    AudioCaptureClient_GetBuffer,
+    AudioCaptureClient_ReleaseBuffer,
+    AudioCaptureClient_GetNextPacketSize
+};
+
+static HRESULT WINAPI AudioClock_QueryInterface(IAudioClock *iface,
+        REFIID riid, void **ppv)
+{
+    ACImpl *This = impl_from_IAudioClock(iface);
+
+    TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv);
+
+    if(!ppv)
+        return E_POINTER;
+    *ppv = NULL;
+
+    if(IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IAudioClock))
+        *ppv = iface;
+    else if(IsEqualIID(riid, &IID_IAudioClock2))
+        *ppv = &This->IAudioClock2_iface;
+    if(*ppv){
+        IUnknown_AddRef((IUnknown*)*ppv);
+        return S_OK;
+    }
+
+    WARN("Unknown interface %s\n", debugstr_guid(riid));
+    return E_NOINTERFACE;
+}
+
+static ULONG WINAPI AudioClock_AddRef(IAudioClock *iface)
+{
+    ACImpl *This = impl_from_IAudioClock(iface);
+    return IAudioClient_AddRef(&This->IAudioClient_iface);
+}
+
+static ULONG WINAPI AudioClock_Release(IAudioClock *iface)
+{
+    ACImpl *This = impl_from_IAudioClock(iface);
+    return IAudioClient_Release(&This->IAudioClient_iface);
+}
+
+static HRESULT WINAPI AudioClock_GetFrequency(IAudioClock *iface, UINT64 *freq)
+{
+    ACImpl *This = impl_from_IAudioClock(iface);
+
+    TRACE("(%p)->(%p)\n", This, freq);
+
+    *freq = This->fmt->nSamplesPerSec;
+
+    return S_OK;
+}
+
+static HRESULT WINAPI AudioClock_GetPosition(IAudioClock *iface, UINT64 *pos,
+        UINT64 *qpctime)
+{
+    ACImpl *This = impl_from_IAudioClock(iface);
+
+    TRACE("(%p)->(%p, %p)\n", This, pos, qpctime);
+
+    if(!pos)
+        return E_POINTER;
+
+    EnterCriticalSection(&This->lock);
+
+    *pos = This->pos_frames;
+
+    LeaveCriticalSection(&This->lock);
+
+    TRACE("pos: %u, pos: %u\n", (UINT32)(*pos%1000000000),
+            (UINT32)(This->pos_frames%1000000000));
+
+    if(qpctime){
+        LARGE_INTEGER stamp, freq;
+        QueryPerformanceCounter(&stamp);
+        QueryPerformanceFrequency(&freq);
+        *qpctime = (stamp.QuadPart * (INT64)10000000) / freq.QuadPart;
+    }
+
+    return S_OK;
+}
+
+static HRESULT WINAPI AudioClock_GetCharacteristics(IAudioClock *iface,
+        DWORD *chars)
+{
+    ACImpl *This = impl_from_IAudioClock(iface);
+
+    TRACE("(%p)->(%p)\n", This, chars);
+
+    if(!chars)
+        return E_POINTER;
+
+    *chars = AUDIOCLOCK_CHARACTERISTIC_FIXED_FREQ;
+
+    return S_OK;
+}
+
+static const IAudioClockVtbl AudioClock_Vtbl =
+{
+    AudioClock_QueryInterface,
+    AudioClock_AddRef,
+    AudioClock_Release,
+    AudioClock_GetFrequency,
+    AudioClock_GetPosition,
+    AudioClock_GetCharacteristics
+};
+
+static HRESULT WINAPI AudioClock2_QueryInterface(IAudioClock2 *iface,
+        REFIID riid, void **ppv)
+{
+    ACImpl *This = impl_from_IAudioClock2(iface);
+    return IAudioClock_QueryInterface(&This->IAudioClock_iface, riid, ppv);
+}
+
+static ULONG WINAPI AudioClock2_AddRef(IAudioClock2 *iface)
+{
+    ACImpl *This = impl_from_IAudioClock2(iface);
+    return IAudioClient_AddRef(&This->IAudioClient_iface);
+}
+
+static ULONG WINAPI AudioClock2_Release(IAudioClock2 *iface)
+{
+    ACImpl *This = impl_from_IAudioClock2(iface);
+    return IAudioClient_Release(&This->IAudioClient_iface);
+}
+
+static HRESULT WINAPI AudioClock2_GetDevicePosition(IAudioClock2 *iface,
+        UINT64 *pos, UINT64 *qpctime)
+{
+    ACImpl *This = impl_from_IAudioClock2(iface);
+
+    FIXME("(%p)->(%p, %p)\n", This, pos, qpctime);
+
+    return E_NOTIMPL;
+}
+
+static const IAudioClock2Vtbl AudioClock2_Vtbl =
+{
+    AudioClock2_QueryInterface,
+    AudioClock2_AddRef,
+    AudioClock2_Release,
+    AudioClock2_GetDevicePosition
+};
+
+static AudioSessionWrapper *AudioSessionWrapper_Create(ACImpl *client)
+{
+    AudioSessionWrapper *ret;
+
+    ret = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
+            sizeof(AudioSessionWrapper));
+    if(!ret)
+        return NULL;
+
+    ret->IAudioSessionControl2_iface.lpVtbl = &AudioSessionControl2_Vtbl;
+    ret->ISimpleAudioVolume_iface.lpVtbl = &SimpleAudioVolume_Vtbl;
+    ret->IChannelAudioVolume_iface.lpVtbl = &ChannelAudioVolume_Vtbl;
+
+    ret->ref = 1;
+
+    ret->client = client;
+    if(client){
+        ret->session = client->session;
+        AudioClient_AddRef(&client->IAudioClient_iface);
+    }
+
+    return ret;
+}
+
+static HRESULT WINAPI AudioSessionControl_QueryInterface(
+        IAudioSessionControl2 *iface, REFIID riid, void **ppv)
+{
+    TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv);
+
+    if(!ppv)
+        return E_POINTER;
+    *ppv = NULL;
+
+    if(IsEqualIID(riid, &IID_IUnknown) ||
+            IsEqualIID(riid, &IID_IAudioSessionControl) ||
+            IsEqualIID(riid, &IID_IAudioSessionControl2))
+        *ppv = iface;
+    if(*ppv){
+        IUnknown_AddRef((IUnknown*)*ppv);
+        return S_OK;
+    }
+
+    WARN("Unknown interface %s\n", debugstr_guid(riid));
+    return E_NOINTERFACE;
+}
+
+static ULONG WINAPI AudioSessionControl_AddRef(IAudioSessionControl2 *iface)
+{
+    AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface);
+    ULONG ref;
+    ref = InterlockedIncrement(&This->ref);
+    TRACE("(%p) Refcount now %u\n", This, ref);
+    return ref;
+}
+
+static ULONG WINAPI AudioSessionControl_Release(IAudioSessionControl2 *iface)
+{
+    AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface);
+    ULONG ref;
+    ref = InterlockedDecrement(&This->ref);
+    TRACE("(%p) Refcount now %u\n", This, ref);
+    if(!ref){
+        if(This->client){
+            EnterCriticalSection(&This->client->lock);
+            This->client->session_wrapper = NULL;
+            LeaveCriticalSection(&This->client->lock);
+            AudioClient_Release(&This->client->IAudioClient_iface);
+        }
+        HeapFree(GetProcessHeap(), 0, This);
+    }
+    return ref;
+}
+
+static HRESULT WINAPI AudioSessionControl_GetState(IAudioSessionControl2 *iface,
+        AudioSessionState *state)
+{
+    AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface);
+    ACImpl *client;
+
+    TRACE("(%p)->(%p)\n", This, state);
+
+    if(!state)
+        return NULL_PTR_ERR;
+
+    EnterCriticalSection(&g_sessions_lock);
+
+    if(list_empty(&This->session->clients)){
+        *state = AudioSessionStateExpired;
+        LeaveCriticalSection(&g_sessions_lock);
+        return S_OK;
+    }
+
+    LIST_FOR_EACH_ENTRY(client, &This->session->clients, ACImpl, entry){
+        EnterCriticalSection(&client->lock);
+        if(client->started){
+            *state = AudioSessionStateActive;
+            LeaveCriticalSection(&client->lock);
+            LeaveCriticalSection(&g_sessions_lock);
+            return S_OK;
+        }
+        LeaveCriticalSection(&client->lock);
+    }
+
+    LeaveCriticalSection(&g_sessions_lock);
+
+    *state = AudioSessionStateInactive;
+
+    return S_OK;
+}
+
+static HRESULT WINAPI AudioSessionControl_GetDisplayName(
+        IAudioSessionControl2 *iface, WCHAR **name)
+{
+    AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface);
+
+    FIXME("(%p)->(%p) - stub\n", This, name);
+
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI AudioSessionControl_SetDisplayName(
+        IAudioSessionControl2 *iface, const WCHAR *name, const GUID *session)
+{
+    AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface);
+
+    FIXME("(%p)->(%p, %s) - stub\n", This, name, debugstr_guid(session));
+
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI AudioSessionControl_GetIconPath(
+        IAudioSessionControl2 *iface, WCHAR **path)
+{
+    AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface);
+
+    FIXME("(%p)->(%p) - stub\n", This, path);
+
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI AudioSessionControl_SetIconPath(
+        IAudioSessionControl2 *iface, const WCHAR *path, const GUID *session)
+{
+    AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface);
+
+    FIXME("(%p)->(%p, %s) - stub\n", This, path, debugstr_guid(session));
+
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI AudioSessionControl_GetGroupingParam(
+        IAudioSessionControl2 *iface, GUID *group)
+{
+    AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface);
+
+    FIXME("(%p)->(%p) - stub\n", This, group);
+
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI AudioSessionControl_SetGroupingParam(
+        IAudioSessionControl2 *iface, const GUID *group, const GUID *session)
+{
+    AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface);
+
+    FIXME("(%p)->(%s, %s) - stub\n", This, debugstr_guid(group),
+            debugstr_guid(session));
+
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI AudioSessionControl_RegisterAudioSessionNotification(
+        IAudioSessionControl2 *iface, IAudioSessionEvents *events)
+{
+    AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface);
+
+    FIXME("(%p)->(%p) - stub\n", This, events);
+
+    return S_OK;
+}
+
+static HRESULT WINAPI AudioSessionControl_UnregisterAudioSessionNotification(
+        IAudioSessionControl2 *iface, IAudioSessionEvents *events)
+{
+    AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface);
+
+    FIXME("(%p)->(%p) - stub\n", This, events);
+
+    return S_OK;
+}
+
+static HRESULT WINAPI AudioSessionControl_GetSessionIdentifier(
+        IAudioSessionControl2 *iface, WCHAR **id)
+{
+    AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface);
+
+    FIXME("(%p)->(%p) - stub\n", This, id);
+
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI AudioSessionControl_GetSessionInstanceIdentifier(
+        IAudioSessionControl2 *iface, WCHAR **id)
+{
+    AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface);
+
+    FIXME("(%p)->(%p) - stub\n", This, id);
+
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI AudioSessionControl_GetProcessId(
+        IAudioSessionControl2 *iface, DWORD *pid)
+{
+    AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface);
+
+    TRACE("(%p)->(%p)\n", This, pid);
+
+    if(!pid)
+        return E_POINTER;
+
+    *pid = GetCurrentProcessId();
+
+    return S_OK;
+}
+
+static HRESULT WINAPI AudioSessionControl_IsSystemSoundsSession(
+        IAudioSessionControl2 *iface)
+{
+    AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface);
+
+    TRACE("(%p)\n", This);
+
+    return S_FALSE;
+}
+
+static HRESULT WINAPI AudioSessionControl_SetDuckingPreference(
+        IAudioSessionControl2 *iface, BOOL optout)
+{
+    AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface);
+
+    TRACE("(%p)->(%d)\n", This, optout);
+
+    return S_OK;
+}
+
+static const IAudioSessionControl2Vtbl AudioSessionControl2_Vtbl =
+{
+    AudioSessionControl_QueryInterface,
+    AudioSessionControl_AddRef,
+    AudioSessionControl_Release,
+    AudioSessionControl_GetState,
+    AudioSessionControl_GetDisplayName,
+    AudioSessionControl_SetDisplayName,
+    AudioSessionControl_GetIconPath,
+    AudioSessionControl_SetIconPath,
+    AudioSessionControl_GetGroupingParam,
+    AudioSessionControl_SetGroupingParam,
+    AudioSessionControl_RegisterAudioSessionNotification,
+    AudioSessionControl_UnregisterAudioSessionNotification,
+    AudioSessionControl_GetSessionIdentifier,
+    AudioSessionControl_GetSessionInstanceIdentifier,
+    AudioSessionControl_GetProcessId,
+    AudioSessionControl_IsSystemSoundsSession,
+    AudioSessionControl_SetDuckingPreference
+};
+
+static HRESULT WINAPI SimpleAudioVolume_QueryInterface(
+        ISimpleAudioVolume *iface, REFIID riid, void **ppv)
+{
+    TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv);
+
+    if(!ppv)
+        return E_POINTER;
+    *ppv = NULL;
+
+    if(IsEqualIID(riid, &IID_IUnknown) ||
+            IsEqualIID(riid, &IID_ISimpleAudioVolume))
+        *ppv = iface;
+    if(*ppv){
+        IUnknown_AddRef((IUnknown*)*ppv);
+        return S_OK;
+    }
+
+    WARN("Unknown interface %s\n", debugstr_guid(riid));
+    return E_NOINTERFACE;
+}
+
+static ULONG WINAPI SimpleAudioVolume_AddRef(ISimpleAudioVolume *iface)
+{
+    AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface);
+    return AudioSessionControl_AddRef(&This->IAudioSessionControl2_iface);
+}
+
+static ULONG WINAPI SimpleAudioVolume_Release(ISimpleAudioVolume *iface)
+{
+    AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface);
+    return AudioSessionControl_Release(&This->IAudioSessionControl2_iface);
+}
+
+static HRESULT WINAPI SimpleAudioVolume_SetMasterVolume(
+        ISimpleAudioVolume *iface, float level, const GUID *context)
+{
+    AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface);
+    AudioSession *session = This->session;
+
+    TRACE("(%p)->(%f, %s)\n", session, level, wine_dbgstr_guid(context));
+
+    if(level < 0.f || level > 1.f)
+        return E_INVALIDARG;
+
+    if(context)
+        FIXME("Notifications not supported yet\n");
+
+    TRACE("PulseAudio does not support volume control\n");
+
+    EnterCriticalSection(&session->lock);
+
+    session->master_vol = level;
+
+    LeaveCriticalSection(&session->lock);
+
+    return S_OK;
+}
+
+static HRESULT WINAPI SimpleAudioVolume_GetMasterVolume(
+        ISimpleAudioVolume *iface, float *level)
+{
+    AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface);
+    AudioSession *session = This->session;
+
+    TRACE("(%p)->(%p)\n", session, level);
+
+    if(!level)
+        return NULL_PTR_ERR;
+
+    *level = session->master_vol;
+
+    return S_OK;
+}
+
+static HRESULT WINAPI SimpleAudioVolume_SetMute(ISimpleAudioVolume *iface,
+        BOOL mute, const GUID *context)
+{
+    AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface);
+    AudioSession *session = This->session;
+
+    TRACE("(%p)->(%u, %p)\n", session, mute, context);
+
+    if(context)
+        FIXME("Notifications not supported yet\n");
+
+    TRACE("PulseAudio render direction doesn't support mute\n");
+    session->mute = mute;
+
+    return S_OK;
+}
+
+static HRESULT WINAPI SimpleAudioVolume_GetMute(ISimpleAudioVolume *iface,
+        BOOL *mute)
+{
+    AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface);
+    AudioSession *session = This->session;
+
+    TRACE("(%p)->(%p)\n", session, mute);
+
+    if(!mute)
+        return NULL_PTR_ERR;
+
+    *mute = session->mute;
+
+    return S_OK;
+}
+
+static const ISimpleAudioVolumeVtbl SimpleAudioVolume_Vtbl  =
+{
+    SimpleAudioVolume_QueryInterface,
+    SimpleAudioVolume_AddRef,
+    SimpleAudioVolume_Release,
+    SimpleAudioVolume_SetMasterVolume,
+    SimpleAudioVolume_GetMasterVolume,
+    SimpleAudioVolume_SetMute,
+    SimpleAudioVolume_GetMute
+};
+
+static HRESULT WINAPI AudioStreamVolume_QueryInterface(
+        IAudioStreamVolume *iface, REFIID riid, void **ppv)
+{
+    TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv);
+
+    if(!ppv)
+        return E_POINTER;
+    *ppv = NULL;
+
+    if(IsEqualIID(riid, &IID_IUnknown) ||
+            IsEqualIID(riid, &IID_IAudioStreamVolume))
+        *ppv = iface;
+    if(*ppv){
+        IUnknown_AddRef((IUnknown*)*ppv);
+        return S_OK;
+    }
+
+    WARN("Unknown interface %s\n", debugstr_guid(riid));
+    return E_NOINTERFACE;
+}
+
+static ULONG WINAPI AudioStreamVolume_AddRef(IAudioStreamVolume *iface)
+{
+    ACImpl *This = impl_from_IAudioStreamVolume(iface);
+    return IAudioClient_AddRef(&This->IAudioClient_iface);
+}
+
+static ULONG WINAPI AudioStreamVolume_Release(IAudioStreamVolume *iface)
+{
+    ACImpl *This = impl_from_IAudioStreamVolume(iface);
+    return IAudioClient_Release(&This->IAudioClient_iface);
+}
+
+static HRESULT WINAPI AudioStreamVolume_GetChannelCount(
+        IAudioStreamVolume *iface, UINT32 *out)
+{
+    ACImpl *This = impl_from_IAudioStreamVolume(iface);
+
+    TRACE("(%p)->(%p)\n", This, out);
+
+    if(!out)
+        return E_POINTER;
+
+    *out = This->fmt->nChannels;
+
+    return S_OK;
+}
+
+static HRESULT WINAPI AudioStreamVolume_SetChannelVolume(
+        IAudioStreamVolume *iface, UINT32 index, float level)
+{
+    ACImpl *This = impl_from_IAudioStreamVolume(iface);
+
+    TRACE("(%p)->(%d, %f)\n", This, index, level);
+
+    if(level < 0.f || level > 1.f)
+        return E_INVALIDARG;
+
+    if(index >= This->fmt->nChannels)
+        return E_INVALIDARG;
+
+    TRACE("PulseAudio does not support volume control\n");
+
+    EnterCriticalSection(&This->lock);
+
+    This->vols[index] = level;
+
+    LeaveCriticalSection(&This->lock);
+
+    return S_OK;
+}
+
+static HRESULT WINAPI AudioStreamVolume_GetChannelVolume(
+        IAudioStreamVolume *iface, UINT32 index, float *level)
+{
+    ACImpl *This = impl_from_IAudioStreamVolume(iface);
+
+    TRACE("(%p)->(%d, %p)\n", This, index, level);
+
+    if(!level)
+        return E_POINTER;
+
+    if(index >= This->fmt->nChannels)
+        return E_INVALIDARG;
+
+    *level = This->vols[index];
+
+    return S_OK;
+}
+
+static HRESULT WINAPI AudioStreamVolume_SetAllVolumes(
+        IAudioStreamVolume *iface, UINT32 count, const float *levels)
+{
+    ACImpl *This = impl_from_IAudioStreamVolume(iface);
+    int i;
+
+    TRACE("(%p)->(%d, %p)\n", This, count, levels);
+
+    if(!levels)
+        return E_POINTER;
+
+    if(count != This->fmt->nChannels)
+        return E_INVALIDARG;
+
+    TRACE("PulseAudio does not support volume control\n");
+
+    EnterCriticalSection(&This->lock);
+
+    for(i = 0; i < count; ++i)
+        This->vols[i] = levels[i];
+
+    LeaveCriticalSection(&This->lock);
+
+    return S_OK;
+}
+
+static HRESULT WINAPI AudioStreamVolume_GetAllVolumes(
+        IAudioStreamVolume *iface, UINT32 count, float *levels)
+{
+    ACImpl *This = impl_from_IAudioStreamVolume(iface);
+    int i;
+
+    TRACE("(%p)->(%d, %p)\n", This, count, levels);
+
+    if(!levels)
+        return E_POINTER;
+
+    if(count != This->fmt->nChannels)
+        return E_INVALIDARG;
+
+    EnterCriticalSection(&This->lock);
+
+    for(i = 0; i < count; ++i)
+        levels[i] = This->vols[i];
+
+    LeaveCriticalSection(&This->lock);
+
+    return S_OK;
+}
+
+static const IAudioStreamVolumeVtbl AudioStreamVolume_Vtbl =
+{
+    AudioStreamVolume_QueryInterface,
+    AudioStreamVolume_AddRef,
+    AudioStreamVolume_Release,
+    AudioStreamVolume_GetChannelCount,
+    AudioStreamVolume_SetChannelVolume,
+    AudioStreamVolume_GetChannelVolume,
+    AudioStreamVolume_SetAllVolumes,
+    AudioStreamVolume_GetAllVolumes
+};
+
+static HRESULT WINAPI ChannelAudioVolume_QueryInterface(
+        IChannelAudioVolume *iface, REFIID riid, void **ppv)
+{
+    TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv);
+
+    if(!ppv)
+        return E_POINTER;
+    *ppv = NULL;
+
+    if(IsEqualIID(riid, &IID_IUnknown) ||
+            IsEqualIID(riid, &IID_IChannelAudioVolume))
+        *ppv = iface;
+    if(*ppv){
+        IUnknown_AddRef((IUnknown*)*ppv);
+        return S_OK;
+    }
+
+    WARN("Unknown interface %s\n", debugstr_guid(riid));
+    return E_NOINTERFACE;
+}
+
+static ULONG WINAPI ChannelAudioVolume_AddRef(IChannelAudioVolume *iface)
+{
+    AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface);
+    return AudioSessionControl_AddRef(&This->IAudioSessionControl2_iface);
+}
+
+static ULONG WINAPI ChannelAudioVolume_Release(IChannelAudioVolume *iface)
+{
+    AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface);
+    return AudioSessionControl_Release(&This->IAudioSessionControl2_iface);
+}
+
+static HRESULT WINAPI ChannelAudioVolume_GetChannelCount(
+        IChannelAudioVolume *iface, UINT32 *out)
+{
+    AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface);
+    AudioSession *session = This->session;
+
+    TRACE("(%p)->(%p)\n", session, out);
+
+    if(!out)
+        return NULL_PTR_ERR;
+
+    *out = session->channel_count;
+
+    return S_OK;
+}
+
+static HRESULT WINAPI ChannelAudioVolume_SetChannelVolume(
+        IChannelAudioVolume *iface, UINT32 index, float level,
+        const GUID *context)
+{
+    AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface);
+    AudioSession *session = This->session;
+
+    TRACE("(%p)->(%d, %f, %s)\n", session, index, level,
+            wine_dbgstr_guid(context));
+
+    if(level < 0.f || level > 1.f)
+        return E_INVALIDARG;
+
+    if(index >= session->channel_count)
+        return E_INVALIDARG;
+
+    if(context)
+        FIXME("Notifications not supported yet\n");
+
+    TRACE("PulseAudio does not support volume control\n");
+
+    EnterCriticalSection(&session->lock);
+
+    session->channel_vols[index] = level;
+
+    LeaveCriticalSection(&session->lock);
+
+    return S_OK;
+}
+
+static HRESULT WINAPI ChannelAudioVolume_GetChannelVolume(
+        IChannelAudioVolume *iface, UINT32 index, float *level)
+{
+    AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface);
+    AudioSession *session = This->session;
+
+    TRACE("(%p)->(%d, %p)\n", session, index, level);
+
+    if(!level)
+        return NULL_PTR_ERR;
+
+    if(index >= session->channel_count)
+        return E_INVALIDARG;
+
+    *level = session->channel_vols[index];
+
+    return S_OK;
+}
+
+static HRESULT WINAPI ChannelAudioVolume_SetAllVolumes(
+        IChannelAudioVolume *iface, UINT32 count, const float *levels,
+        const GUID *context)
+{
+    AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface);
+    AudioSession *session = This->session;
+    int i;
+
+    TRACE("(%p)->(%d, %p, %s)\n", session, count, levels,
+            wine_dbgstr_guid(context));
+
+    if(!levels)
+        return NULL_PTR_ERR;
+
+    if(count != session->channel_count)
+        return E_INVALIDARG;
+
+    if(context)
+        FIXME("Notifications not supported yet\n");
+
+    TRACE("PulseAudio does not support volume control\n");
+
+    EnterCriticalSection(&session->lock);
+
+    for(i = 0; i < count; ++i)
+        session->channel_vols[i] = levels[i];
+
+    LeaveCriticalSection(&session->lock);
+
+    return S_OK;
+}
+
+static HRESULT WINAPI ChannelAudioVolume_GetAllVolumes(
+        IChannelAudioVolume *iface, UINT32 count, float *levels)
+{
+    AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface);
+    AudioSession *session = This->session;
+    int i;
+
+    TRACE("(%p)->(%d, %p)\n", session, count, levels);
+
+    if(!levels)
+        return NULL_PTR_ERR;
+
+    if(count != session->channel_count)
+        return E_INVALIDARG;
+
+    for(i = 0; i < count; ++i)
+        levels[i] = session->channel_vols[i];
+
+    return S_OK;
+}
+
+static const IChannelAudioVolumeVtbl ChannelAudioVolume_Vtbl =
+{
+    ChannelAudioVolume_QueryInterface,
+    ChannelAudioVolume_AddRef,
+    ChannelAudioVolume_Release,
+    ChannelAudioVolume_GetChannelCount,
+    ChannelAudioVolume_SetChannelVolume,
+    ChannelAudioVolume_GetChannelVolume,
+    ChannelAudioVolume_SetAllVolumes,
+    ChannelAudioVolume_GetAllVolumes
+};
+
+static HRESULT WINAPI AudioSessionManager_QueryInterface(IAudioSessionManager2 *iface,
+        REFIID riid, void **ppv)
+{
+    TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv);
+
+    if(!ppv)
+        return E_POINTER;
+    *ppv = NULL;
+
+    if(IsEqualIID(riid, &IID_IUnknown) ||
+            IsEqualIID(riid, &IID_IAudioSessionManager) ||
+            IsEqualIID(riid, &IID_IAudioSessionManager2))
+        *ppv = iface;
+    if(*ppv){
+        IUnknown_AddRef((IUnknown*)*ppv);
+        return S_OK;
+    }
+
+    WARN("Unknown interface %s\n", debugstr_guid(riid));
+    return E_NOINTERFACE;
+}
+
+static ULONG WINAPI AudioSessionManager_AddRef(IAudioSessionManager2 *iface)
+{
+    SessionMgr *This = impl_from_IAudioSessionManager2(iface);
+    ULONG ref;
+    ref = InterlockedIncrement(&This->ref);
+    TRACE("(%p) Refcount now %u\n", This, ref);
+    return ref;
+}
+
+static ULONG WINAPI AudioSessionManager_Release(IAudioSessionManager2 *iface)
+{
+    SessionMgr *This = impl_from_IAudioSessionManager2(iface);
+    ULONG ref;
+    ref = InterlockedDecrement(&This->ref);
+    TRACE("(%p) Refcount now %u\n", This, ref);
+    if(!ref)
+        HeapFree(GetProcessHeap(), 0, This);
+    return ref;
+}
+
+static HRESULT WINAPI AudioSessionManager_GetAudioSessionControl(
+        IAudioSessionManager2 *iface, const GUID *session_guid, DWORD flags,
+        IAudioSessionControl **out)
+{
+    SessionMgr *This = impl_from_IAudioSessionManager2(iface);
+    AudioSession *session;
+    AudioSessionWrapper *wrapper;
+    HRESULT hr;
+
+    TRACE("(%p)->(%s, %x, %p)\n", This, debugstr_guid(session_guid),
+            flags, out);
+
+    hr = get_audio_session(session_guid, This->device, 0, &session);
+    if(FAILED(hr))
+        return hr;
+
+    wrapper = AudioSessionWrapper_Create(NULL);
+    if(!wrapper)
+        return E_OUTOFMEMORY;
+
+    wrapper->session = session;
+
+    *out = (IAudioSessionControl*)&wrapper->IAudioSessionControl2_iface;
+
+    return S_OK;
+}
+
+static HRESULT WINAPI AudioSessionManager_GetSimpleAudioVolume(
+        IAudioSessionManager2 *iface, const GUID *session_guid, DWORD flags,
+        ISimpleAudioVolume **out)
+{
+    SessionMgr *This = impl_from_IAudioSessionManager2(iface);
+    AudioSession *session;
+    AudioSessionWrapper *wrapper;
+    HRESULT hr;
+
+    TRACE("(%p)->(%s, %x, %p)\n", This, debugstr_guid(session_guid),
+            flags, out);
+
+    hr = get_audio_session(session_guid, This->device, 0, &session);
+    if(FAILED(hr))
+        return hr;
+
+    wrapper = AudioSessionWrapper_Create(NULL);
+    if(!wrapper)
+        return E_OUTOFMEMORY;
+
+    wrapper->session = session;
+
+    *out = &wrapper->ISimpleAudioVolume_iface;
+
+    return S_OK;
+}
+
+static HRESULT WINAPI AudioSessionManager_GetSessionEnumerator(
+        IAudioSessionManager2 *iface, IAudioSessionEnumerator **out)
+{
+    SessionMgr *This = impl_from_IAudioSessionManager2(iface);
+    FIXME("(%p)->(%p) - stub\n", This, out);
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI AudioSessionManager_RegisterSessionNotification(
+        IAudioSessionManager2 *iface, IAudioSessionNotification *notification)
+{
+    SessionMgr *This = impl_from_IAudioSessionManager2(iface);
+    FIXME("(%p)->(%p) - stub\n", This, notification);
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI AudioSessionManager_UnregisterSessionNotification(
+        IAudioSessionManager2 *iface, IAudioSessionNotification *notification)
+{
+    SessionMgr *This = impl_from_IAudioSessionManager2(iface);
+    FIXME("(%p)->(%p) - stub\n", This, notification);
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI AudioSessionManager_RegisterDuckNotification(
+        IAudioSessionManager2 *iface, const WCHAR *session_id,
+        IAudioVolumeDuckNotification *notification)
+{
+    SessionMgr *This = impl_from_IAudioSessionManager2(iface);
+    FIXME("(%p)->(%p) - stub\n", This, notification);
+    return E_NOTIMPL;
+}
+
+static HRESULT WINAPI AudioSessionManager_UnregisterDuckNotification(
+        IAudioSessionManager2 *iface,
+        IAudioVolumeDuckNotification *notification)
+{
+    SessionMgr *This = impl_from_IAudioSessionManager2(iface);
+    FIXME("(%p)->(%p) - stub\n", This, notification);
+    return E_NOTIMPL;
+}
+
+static const IAudioSessionManager2Vtbl AudioSessionManager2_Vtbl =
+{
+    AudioSessionManager_QueryInterface,
+    AudioSessionManager_AddRef,
+    AudioSessionManager_Release,
+    AudioSessionManager_GetAudioSessionControl,
+    AudioSessionManager_GetSimpleAudioVolume,
+    AudioSessionManager_GetSessionEnumerator,
+    AudioSessionManager_RegisterSessionNotification,
+    AudioSessionManager_UnregisterSessionNotification,
+    AudioSessionManager_RegisterDuckNotification,
+    AudioSessionManager_UnregisterDuckNotification
+};
+
+HRESULT WINAPI AUDDRV_GetAudioSessionManager(IMMDevice *device,
+        IAudioSessionManager2 **out)
+{
+    SessionMgr *This;
+
+    This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(SessionMgr));
+    if(!This)
+        return E_OUTOFMEMORY;
+
+    This->IAudioSessionManager2_iface.lpVtbl = &AudioSessionManager2_Vtbl;
+    This->device = device;
+    This->ref = 1;
+
+    *out = &This->IAudioSessionManager2_iface;
+
+    return S_OK;
+}
diff --git a/dlls/winepulse.drv/winepulse.drv.spec b/dlls/winepulse.drv/winepulse.drv.spec
new file mode 100644
index 0000000..612bf46
--- /dev/null
+++ b/dlls/winepulse.drv/winepulse.drv.spec
@@ -0,0 +1,5 @@
+# MMDevAPI driver functions
+@ stdcall -private GetPriority() AUDDRV_GetPriority
+@ stdcall -private GetEndpointIDs(long ptr ptr ptr ptr) AUDDRV_GetEndpointIDs
+@ stdcall -private GetAudioEndpoint(ptr ptr ptr) AUDDRV_GetAudioEndpoint
+@ stdcall -private GetAudioSessionManager(ptr ptr) AUDDRV_GetAudioSessionManager