From d10e1f620b180d2bf480cc21f5d58a1c915a1489 Mon Sep 17 00:00:00 2001 From: Stefan Reimer Date: Sun, 29 Apr 2012 14:10:12 -0700 Subject: [PATCH] Pulseaudio V17 --- configure | 97 +- configure.ac | 31 +- dlls/mmdevapi/main.c | 2 +- dlls/winepulse.drv/Makefile.in | 9 + dlls/winepulse.drv/mmdevdrv.c | 3101 +++++++++++++++++++++++++++++++++ dlls/winepulse.drv/winepulse.drv.spec | 5 + 6 files changed, 3240 insertions(+), 5 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 b/configure index 46827c1..a9f0b1e 100755 --- a/configure +++ b/configure @@ -629,6 +629,8 @@ OSS4INCL ALSALIBS GSTREAMER_INCL GSTREAMER_LIBS +PULSEINCL +PULSELIBS LIBGETTEXTPO ZLIB FREETYPEINCL @@ -805,6 +807,7 @@ with_openssl with_oss with_png with_pthread +with_pulse with_sane with_tiff with_v4l @@ -1495,6 +1498,7 @@ Optional Packages: --without-oss do not use the OSS sound support --without-png do not use PNG --without-pthread do not use the pthread library + --without-pulse do not use PulseAudio sound support --without-sane do not use SANE (scanner support) --without-tiff do not use TIFF --without-v4l do not use v4l1 (v4l support) @@ -2679,6 +2683,12 @@ if test "${with_pthread+set}" = set; then : fi +# Check whether --with-pulse was given. +if test "${with_pulse+set}" = set; then : + withval=$with_pulse; +fi + + # Check whether --with-sane was given. if test "${with_sane+set}" = set; then : withval=$with_sane; @@ -10613,6 +10623,87 @@ esac fi fi +PULSELIBS="" + +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" + for ac_header in pulse/pulseaudio.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "pulse/pulseaudio.h" "ac_cv_header_pulse_pulseaudio_h" "$ac_includes_default" +if test "x$ac_cv_header_pulse_pulseaudio_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_PULSE_PULSEAUDIO_H 1 +_ACEOF + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pa_stream_is_corked in -lpulse" >&5 +$as_echo_n "checking for pa_stream_is_corked in -lpulse... " >&6; } +if ${ac_cv_lib_pulse_pa_stream_is_corked+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lpulse $ac_pulse_libs $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char pa_stream_is_corked (); +int +main () +{ +return pa_stream_is_corked (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_pulse_pa_stream_is_corked=yes +else + ac_cv_lib_pulse_pa_stream_is_corked=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pulse_pa_stream_is_corked" >&5 +$as_echo "$ac_cv_lib_pulse_pa_stream_is_corked" >&6; } +if test "x$ac_cv_lib_pulse_pa_stream_is_corked" = xyes; then : + +$as_echo "#define HAVE_PULSEAUDIO 1" >>confdefs.h + + PULSELIBS="$ac_pulse_libs" + PULSEINCL="$ac_pulse_cflags" +fi + + +fi + +done + + fi + CPPFLAGS="$ac_save_CPPFLAGS" +fi +if test "$ac_cv_lib_pulse_pa_stream_is_corked" != "yes"; then : + case "x$with_pulse" in + x) as_fn_append wine_warnings "|libpulse ${notice_platform}development files not found or too old, Pulse won't be supported." ;; + xno) ;; + *) as_fn_error $? "libpulse ${notice_platform}development files not found or too old, Pulse won't be supported. +This is an error since --with-pulse was requested." "$LINENO" 5 ;; +esac +fi + if test "x$with_gstreamer" != "xno" then ac_save_CPPFLAGS="$CPPFLAGS" @@ -11872,12 +11963,13 @@ fi 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} -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 as_fn_append wine_warnings "|No sound system was found. Windows applications will be silent." fi @@ -15402,6 +15494,7 @@ wine_fn_config_dll winemp3.acm enable_winemp3_acm wine_fn_config_dll wineoss.drv enable_wineoss_drv wine_fn_config_dll wineps.drv enable_wineps_drv install-lib,po wine_fn_config_dll wineps16.drv16 enable_win16 +wine_fn_config_dll winepulse.drv enable_winepulse_drv wine_fn_config_dll wineqtdecoder enable_wineqtdecoder wine_fn_config_dll winequartz.drv enable_winequartz_drv wine_fn_config_dll winex11.drv enable_winex11_drv diff --git a/configure.ac b/configure.ac index efc03af..d9bd62b 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, AC_HELP_STRING([--without-pulse],[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]) @@ -1474,6 +1475,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_WARNING_WITH(pulse, [test "$ac_cv_lib_pulse_pa_stream_is_corked" != "yes"], + [libpulse ${notice_platform}development files not found or too old, Pulse won't be supported.]) + dnl **** Check for gstreamer **** if test "x$with_gstreamer" != "xno" then @@ -1680,13 +1705,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 @@ -2908,6 +2934,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..5755ea8 100644 --- a/dlls/mmdevapi/main.c +++ b/dlls/mmdevapi/main.c @@ -111,7 +111,7 @@ 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',',', + 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; diff --git a/dlls/winepulse.drv/Makefile.in b/dlls/winepulse.drv/Makefile.in new file mode 100644 index 0000000..0f595f1 --- /dev/null +++ b/dlls/winepulse.drv/Makefile.in @@ -0,0 +1,9 @@ +MODULE = winepulse.drv +IMPORTS = dxguid uuid winmm user32 advapi32 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..5cc460f --- /dev/null +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -0,0 +1,3101 @@ +/* + * Copyright 2011-2012 Maarten Lankhorst + * Copyright 2010-2011 Maarten Lankhorst for CodeWeavers + * Copyright 2011 Andrew Eikum 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 + * + * Pulseaudio driver support.. hell froze over + */ + +#define NONAMELESSUNION +#define COBJMACROS +#include "config.h" +#include +#include + +#include +#include +#include +#include + +#include + +#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 "dshow.h" +#include "dsound.h" +#include "propsys.h" + +#include "initguid.h" +#include "ks.h" +#include "ksmedia.h" +#include "mmdeviceapi.h" +#include "audioclient.h" +#include "endpointvolume.h" +#include "audiopolicy.h" + +#include "wine/list.h" + +#define NULL_PTR_ERR MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, RPC_X_NULL_REF_POINTER) + +WINE_DEFAULT_DEBUG_CHANNEL(pulse); +WINE_DECLARE_DEBUG_CHANNEL(winediag); + +static const REFERENCE_TIME MinimumPeriod = 30000; +static const REFERENCE_TIME DefaultPeriod = 100000; + +static pa_context *pulse_ctx; +static pa_mainloop *pulse_ml; + +static HANDLE pulse_thread; +static pthread_mutex_t pulse_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t pulse_cond = PTHREAD_COND_INITIALIZER; +static struct list g_sessions = LIST_INIT(g_sessions); + +/* Mixer format + period times */ +static WAVEFORMATEXTENSIBLE pulse_fmt[2]; +static REFERENCE_TIME pulse_min_period[2], pulse_def_period[2]; + +static DWORD pulse_stream_volume; + +const WCHAR pulse_keyW[] = {'S','o','f','t','w','a','r','e','\\', + 'W','i','n','e','\\','P','u','l','s','e',0}; +const WCHAR pulse_streamW[] = { 'S','t','r','e','a','m','V','o','l',0 }; + +static GUID pulse_render_guid = +{ 0xfd47d9cc, 0x4218, 0x4135, { 0x9c, 0xe2, 0x0c, 0x19, 0x5c, 0x87, 0x40, 0x5b } }; +static GUID pulse_capture_guid = +{ 0x25da76d0, 0x033c, 0x4235, { 0x90, 0x02, 0x19, 0xf4, 0x88, 0x94, 0xac, 0x6f } }; + +static HANDLE warn_once; + +BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, void *reserved) +{ + if (reason == DLL_PROCESS_ATTACH) { + HKEY key; + if (RegOpenKeyW(HKEY_CURRENT_USER, pulse_keyW, &key) == ERROR_SUCCESS) { + DWORD size = sizeof(pulse_stream_volume); + RegQueryValueExW(key, pulse_streamW, 0, NULL, + (BYTE*)&pulse_stream_volume, &size); + RegCloseKey(key); + } + DisableThreadLibraryCalls(dll); + } else if (reason == DLL_PROCESS_DETACH) { + if (pulse_ctx) { + pa_context_disconnect(pulse_ctx); + pa_context_unref(pulse_ctx); + } + if (pulse_ml) + pa_mainloop_quit(pulse_ml, 0); + if (pulse_thread) + CloseHandle(pulse_thread); + if (warn_once) + CloseHandle(warn_once); + } + return TRUE; +} + +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; + + struct list entry; +} AudioSession; + +typedef struct _AudioSessionWrapper { + IAudioSessionControl2 IAudioSessionControl2_iface; + IChannelAudioVolume IChannelAudioVolume_iface; + ISimpleAudioVolume ISimpleAudioVolume_iface; + + LONG ref; + + ACImpl *client; + AudioSession *session; +} AudioSessionWrapper; + +typedef struct _ACPacket { + struct list entry; + UINT64 qpcpos; + BYTE *data; + UINT32 discont; +} ACPacket; + +struct ACImpl { + IAudioClient IAudioClient_iface; + IAudioRenderClient IAudioRenderClient_iface; + IAudioCaptureClient IAudioCaptureClient_iface; + IAudioClock IAudioClock_iface; + IAudioClock2 IAudioClock2_iface; + IAudioStreamVolume IAudioStreamVolume_iface; + IMMDevice *parent; + struct list entry; + float vol[PA_CHANNELS_MAX]; + + LONG ref; + EDataFlow dataflow; + DWORD flags; + AUDCLNT_SHAREMODE share; + HANDLE event; + + UINT32 bufsize_frames, bufsize_bytes, locked, capture_period, pad, started, peek_ofs; + void *locked_ptr, *tmp_buffer; + + pa_stream *stream; + pa_sample_spec ss; + pa_channel_map map; + + INT64 clock_lastpos, clock_written; + pa_usec_t clock_pulse; + + AudioSession *session; + AudioSessionWrapper *session_wrapper; + struct list packet_free_head; + struct list packet_filled_head; +}; + +static const WCHAR defaultW[] = {'P','u','l','s','e','a','u','d','i','o',0}; + +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 IChannelAudioVolumeVtbl ChannelAudioVolume_Vtbl; +static const IAudioClockVtbl AudioClock_Vtbl; +static const IAudioClock2Vtbl AudioClock2_Vtbl; +static const IAudioStreamVolumeVtbl AudioStreamVolume_Vtbl; + +static AudioSessionWrapper *AudioSessionWrapper_Create(ACImpl *client); + +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); +} + +/* Following pulseaudio design here, mainloop has the lock taken whenever + * it is handling something for pulse, and the lock is required whenever + * doing any pa_* call that can affect the state in any way + * + * pa_cond_wait is used when waiting on results, because the mainloop needs + * the same lock taken to affect the state + * + * This is basically the same as the pa_threaded_mainloop implementation, + * but that cannot be used because it uses pthread_create directly + * + * pa_threaded_mainloop_(un)lock -> pthread_mutex_(un)lock + * pa_threaded_mainloop_signal -> pthread_cond_signal + * pa_threaded_mainloop_wait -> pthread_cond_wait + */ + +static int pulse_poll_func(struct pollfd *ufds, unsigned long nfds, int timeout, void *userdata) { + 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 *tmp) { + int ret; + pulse_ml = pa_mainloop_new(); + pa_mainloop_set_poll_func(pulse_ml, pulse_poll_func, NULL); + pthread_mutex_lock(&pulse_lock); + pthread_cond_signal(&pulse_cond); + pa_mainloop_run(pulse_ml, &ret); + pthread_mutex_unlock(&pulse_lock); + pa_mainloop_free(pulse_ml); + CloseHandle(pulse_thread); + return ret; +} + +static void pulse_contextcallback(pa_context *c, void *userdata); +static void pulse_stream_state(pa_stream *s, void *user); + +static const enum pa_channel_position pulse_pos_from_wfx[] = { + PA_CHANNEL_POSITION_FRONT_LEFT, + PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_FRONT_CENTER, + PA_CHANNEL_POSITION_LFE, + PA_CHANNEL_POSITION_REAR_LEFT, + PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, + PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, + PA_CHANNEL_POSITION_REAR_CENTER, + PA_CHANNEL_POSITION_SIDE_LEFT, + PA_CHANNEL_POSITION_SIDE_RIGHT, + PA_CHANNEL_POSITION_TOP_CENTER, + PA_CHANNEL_POSITION_TOP_FRONT_LEFT, + PA_CHANNEL_POSITION_TOP_FRONT_CENTER, + PA_CHANNEL_POSITION_TOP_FRONT_RIGHT, + PA_CHANNEL_POSITION_TOP_REAR_LEFT, + PA_CHANNEL_POSITION_TOP_REAR_CENTER, + PA_CHANNEL_POSITION_TOP_REAR_RIGHT +}; + +static void pulse_probe_settings(int render, WAVEFORMATEXTENSIBLE *fmt) { + WAVEFORMATEX *wfx = &fmt->Format; + pa_stream *stream; + pa_channel_map map; + pa_sample_spec ss; + pa_buffer_attr attr; + int ret, i; + unsigned int length = 0; + + pa_channel_map_init_auto(&map, 2, PA_CHANNEL_MAP_ALSA); + ss.rate = 48000; + ss.format = PA_SAMPLE_FLOAT32LE; + ss.channels = map.channels; + + attr.maxlength = -1; + attr.tlength = -1; + attr.minreq = attr.fragsize = pa_frame_size(&ss); + attr.prebuf = 0; + + stream = pa_stream_new(pulse_ctx, "format test stream", &ss, &map); + if (stream) + pa_stream_set_state_callback(stream, pulse_stream_state, NULL); + if (!stream) + ret = -1; + else if (render) + ret = pa_stream_connect_playback(stream, NULL, &attr, + PA_STREAM_START_CORKED|PA_STREAM_FIX_RATE|PA_STREAM_FIX_CHANNELS|PA_STREAM_EARLY_REQUESTS, NULL, NULL); + else + ret = pa_stream_connect_record(stream, NULL, &attr, PA_STREAM_START_CORKED|PA_STREAM_FIX_RATE|PA_STREAM_FIX_CHANNELS|PA_STREAM_EARLY_REQUESTS); + if (ret >= 0) { + while (pa_stream_get_state(stream) == PA_STREAM_CREATING) + pthread_cond_wait(&pulse_cond, &pulse_lock); + if (pa_stream_get_state(stream) == PA_STREAM_READY) { + ss = *pa_stream_get_sample_spec(stream); + map = *pa_stream_get_channel_map(stream); + if (render) + length = pa_stream_get_buffer_attr(stream)->minreq; + else + length = pa_stream_get_buffer_attr(stream)->fragsize; + pa_stream_disconnect(stream); + while (pa_stream_get_state(stream) == PA_STREAM_READY) + pthread_cond_wait(&pulse_cond, &pulse_lock); + } + } + if (stream) + pa_stream_unref(stream); + if (length) + pulse_def_period[!render] = pulse_min_period[!render] = pa_bytes_to_usec(10 * length, &ss); + else + pulse_min_period[!render] = MinimumPeriod; + if (pulse_def_period[!render] <= DefaultPeriod) + pulse_def_period[!render] = DefaultPeriod; + + wfx->wFormatTag = WAVE_FORMAT_EXTENSIBLE; + wfx->cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); + wfx->nChannels = ss.channels; + wfx->wBitsPerSample = 8 * pa_sample_size_of_format(ss.format); + wfx->nSamplesPerSec = ss.rate; + wfx->nBlockAlign = pa_frame_size(&ss); + wfx->nAvgBytesPerSec = wfx->nSamplesPerSec * wfx->nBlockAlign; + if (ss.format != PA_SAMPLE_S24_32LE) + fmt->Samples.wValidBitsPerSample = wfx->wBitsPerSample; + else + fmt->Samples.wValidBitsPerSample = 24; + if (ss.format == PA_SAMPLE_FLOAT32LE) + fmt->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + else + fmt->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + + fmt->dwChannelMask = 0; + for (i = 0; i < map.channels; ++i) + switch (map.map[i]) { + default: FIXME("Unhandled channel %s\n", pa_channel_position_to_string(map.map[i])); break; + case PA_CHANNEL_POSITION_FRONT_LEFT: fmt->dwChannelMask |= SPEAKER_FRONT_LEFT; break; + case PA_CHANNEL_POSITION_MONO: + case PA_CHANNEL_POSITION_FRONT_CENTER: fmt->dwChannelMask |= SPEAKER_FRONT_CENTER; break; + case PA_CHANNEL_POSITION_FRONT_RIGHT: fmt->dwChannelMask |= SPEAKER_FRONT_RIGHT; break; + case PA_CHANNEL_POSITION_REAR_LEFT: fmt->dwChannelMask |= SPEAKER_BACK_LEFT; break; + case PA_CHANNEL_POSITION_REAR_CENTER: fmt->dwChannelMask |= SPEAKER_BACK_CENTER; break; + case PA_CHANNEL_POSITION_REAR_RIGHT: fmt->dwChannelMask |= SPEAKER_BACK_RIGHT; break; + case PA_CHANNEL_POSITION_LFE: fmt->dwChannelMask |= SPEAKER_LOW_FREQUENCY; break; + case PA_CHANNEL_POSITION_SIDE_LEFT: fmt->dwChannelMask |= SPEAKER_SIDE_LEFT; break; + case PA_CHANNEL_POSITION_SIDE_RIGHT: fmt->dwChannelMask |= SPEAKER_SIDE_RIGHT; break; + case PA_CHANNEL_POSITION_TOP_CENTER: fmt->dwChannelMask |= SPEAKER_TOP_CENTER; break; + case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: fmt->dwChannelMask |= SPEAKER_TOP_FRONT_LEFT; break; + case PA_CHANNEL_POSITION_TOP_FRONT_CENTER: fmt->dwChannelMask |= SPEAKER_TOP_FRONT_CENTER; break; + case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: fmt->dwChannelMask |= SPEAKER_TOP_FRONT_RIGHT; break; + case PA_CHANNEL_POSITION_TOP_REAR_LEFT: fmt->dwChannelMask |= SPEAKER_TOP_BACK_LEFT; break; + case PA_CHANNEL_POSITION_TOP_REAR_CENTER: fmt->dwChannelMask |= SPEAKER_TOP_BACK_CENTER; break; + case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: fmt->dwChannelMask |= SPEAKER_TOP_BACK_RIGHT; break; + } +} + +static HRESULT pulse_connect(void) +{ + int len; + WCHAR path[PATH_MAX], *name; + char *str; + + if (!pulse_thread) + { + if (!(pulse_thread = CreateThread(NULL, 0, pulse_mainloop_thread, NULL, 0, NULL))) + { + ERR("Failed to create mainloop thread."); + return E_FAIL; + } + SetThreadPriority(pulse_thread, THREAD_PRIORITY_TIME_CRITICAL); + pthread_cond_wait(&pulse_cond, &pulse_lock); + } + + if (pulse_ctx && PA_CONTEXT_IS_GOOD(pa_context_get_state(pulse_ctx))) + return S_OK; + if (pulse_ctx) + pa_context_unref(pulse_ctx); + + GetModuleFileNameW(NULL, path, sizeof(path)/sizeof(*path)); + name = strrchrW(path, '\\'); + if (!name) + name = path; + else + name++; + len = WideCharToMultiByte(CP_UNIXCP, 0, name, -1, NULL, 0, NULL, NULL); + str = pa_xmalloc(len); + WideCharToMultiByte(CP_UNIXCP, 0, name, -1, str, len, NULL, NULL); + TRACE("Name: %s\n", str); + pulse_ctx = pa_context_new(pa_mainloop_get_api(pulse_ml), str); + pa_xfree(str); + if (!pulse_ctx) { + ERR("Failed to create context\n"); + return E_FAIL; + } + + pa_context_set_state_callback(pulse_ctx, pulse_contextcallback, NULL); + + TRACE("libpulse protocol version: %u. API Version %u\n", pa_context_get_protocol_version(pulse_ctx), PA_API_VERSION); + if (pa_context_connect(pulse_ctx, NULL, 0, NULL) < 0) + goto fail; + + /* Wait for connection */ + while (pthread_cond_wait(&pulse_cond, &pulse_lock)) { + pa_context_state_t state = pa_context_get_state(pulse_ctx); + + if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) + goto fail; + + if (state == PA_CONTEXT_READY) + break; + } + + TRACE("Connected to server %s with protocol version: %i.\n", + pa_context_get_server(pulse_ctx), + pa_context_get_server_protocol_version(pulse_ctx)); + pulse_probe_settings(1, &pulse_fmt[0]); + pulse_probe_settings(0, &pulse_fmt[1]); + return S_OK; + +fail: + pa_context_unref(pulse_ctx); + pulse_ctx = NULL; + return E_FAIL; +} + +static void pulse_contextcallback(pa_context *c, void *userdata) { + switch (pa_context_get_state(c)) { + default: + FIXME("Unhandled state: %i\n", pa_context_get_state(c)); + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + case PA_CONTEXT_TERMINATED: + TRACE("State change to %i\n", pa_context_get_state(c)); + return; + + case PA_CONTEXT_READY: + TRACE("Ready\n"); + break; + + case PA_CONTEXT_FAILED: + ERR("Context failed: %s\n", pa_strerror(pa_context_errno(c))); + break; + } + pthread_cond_signal(&pulse_cond); +} + +static HRESULT pulse_stream_valid(ACImpl *This) { + if (!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + if (!This->stream || pa_stream_get_state(This->stream) != PA_STREAM_READY) + return AUDCLNT_E_DEVICE_INVALIDATED; + return S_OK; +} + +static void dump_attr(const pa_buffer_attr *attr) { + TRACE("maxlength: %u\n", attr->maxlength); + TRACE("minreq: %u\n", attr->minreq); + TRACE("fragsize: %u\n", attr->fragsize); + TRACE("tlength: %u\n", attr->tlength); + TRACE("prebuf: %u\n", attr->prebuf); +} + +static void pulse_op_cb(pa_stream *s, int success, void *user) { + TRACE("Success: %i\n", success); + *(int*)user = success; + pthread_cond_signal(&pulse_cond); +} + +static void pulse_ctx_op_cb(pa_context *c, int success, void *user) { + TRACE("Success: %i\n", success); + *(int*)user = success; + pthread_cond_signal(&pulse_cond); +} + +static void pulse_attr_update(pa_stream *s, void *user) { + const pa_buffer_attr *attr = pa_stream_get_buffer_attr(s); + TRACE("New attributes or device moved:\n"); + dump_attr(attr); +} + +static void pulse_wr_callback(pa_stream *s, size_t bytes, void *userdata) +{ + ACImpl *This = userdata; + pa_usec_t time; + UINT32 oldpad = This->pad; + + if (bytes < This->bufsize_bytes) + This->pad = This->bufsize_bytes - bytes; + else + This->pad = 0; + + assert(oldpad >= This->pad); + + if (0 && This->pad && pa_stream_get_time(This->stream, &time) >= 0) + This->clock_pulse = time; + else + This->clock_pulse = PA_USEC_INVALID; + + This->clock_written += oldpad - This->pad; + TRACE("New pad: %u (-%u)\n", This->pad / pa_frame_size(&This->ss), (oldpad - This->pad) / pa_frame_size(&This->ss)); + + if (This->event) + SetEvent(This->event); +} + +static void pulse_underflow_callback(pa_stream *s, void *userdata) +{ + ACImpl *This = userdata; + This->clock_pulse = PA_USEC_INVALID; + WARN("Underflow\n"); +} + +/* Latency is periodically updated even when nothing is played, + * because of PA_STREAM_AUTO_TIMING_UPDATE so use it as timer + * + * Perfect for passing all tests :) + */ +static void pulse_latency_callback(pa_stream *s, void *userdata) +{ + ACImpl *This = userdata; + if (!This->pad && This->event) + SetEvent(This->event); +} + +static void pulse_started_callback(pa_stream *s, void *userdata) +{ + ACImpl *This = userdata; + pa_usec_t time; + + TRACE("(Re)started playing\n"); + assert(This->clock_pulse == PA_USEC_INVALID); + if (0 && pa_stream_get_time(This->stream, &time) >= 0) + This->clock_pulse = time; + if (This->event) + SetEvent(This->event); +} + +static void pulse_rd_loop(ACImpl *This, size_t bytes) +{ + while (bytes >= This->capture_period) { + ACPacket *p, *next; + LARGE_INTEGER stamp, freq; + BYTE *dst, *src; + UINT32 src_len, copy, rem = This->capture_period; + if (!(p = (ACPacket*)list_head(&This->packet_free_head))) { + p = (ACPacket*)list_head(&This->packet_filled_head); + if (!p->discont) { + next = (ACPacket*)p->entry.next; + next->discont = 1; + } else + p = (ACPacket*)list_tail(&This->packet_filled_head); + assert(This->pad == This->bufsize_bytes); + } else { + assert(This->pad < This->bufsize_bytes); + This->pad += This->capture_period; + assert(This->pad <= This->bufsize_bytes); + } + QueryPerformanceCounter(&stamp); + QueryPerformanceFrequency(&freq); + p->qpcpos = (stamp.QuadPart * (INT64)10000000) / freq.QuadPart; + p->discont = 0; + list_remove(&p->entry); + list_add_tail(&This->packet_filled_head, &p->entry); + + dst = p->data; + while (rem) { + pa_stream_peek(This->stream, (const void**)&src, &src_len); + assert(src_len); + assert(This->peek_ofs < src_len); + src += This->peek_ofs; + src_len -= This->peek_ofs; + assert(src_len <= bytes); + + copy = rem; + if (copy > src_len) + copy = src_len; + memcpy(dst, src, rem); + src += copy; + src_len -= copy; + dst += copy; + rem -= copy; + + if (!src_len) { + This->peek_ofs = 0; + pa_stream_drop(This->stream); + } else + This->peek_ofs += copy; + } + bytes -= This->capture_period; + } +} + +static void pulse_rd_drop(ACImpl *This, size_t bytes) +{ + while (bytes >= This->capture_period) { + UINT32 src_len, copy, rem = This->capture_period; + while (rem) { + const void *src; + pa_stream_peek(This->stream, &src, &src_len); + assert(src_len); + assert(This->peek_ofs < src_len); + src_len -= This->peek_ofs; + assert(src_len <= bytes); + + copy = rem; + if (copy > src_len) + copy = src_len; + + src_len -= copy; + rem -= copy; + + if (!src_len) { + This->peek_ofs = 0; + pa_stream_drop(This->stream); + } else + This->peek_ofs += copy; + bytes -= copy; + } + } +} + +static void pulse_rd_callback(pa_stream *s, size_t bytes, void *userdata) +{ + ACImpl *This = userdata; + + TRACE("Readable total: %u, fragsize: %u\n", bytes, pa_stream_get_buffer_attr(s)->fragsize); + assert(bytes >= This->peek_ofs); + bytes -= This->peek_ofs; + if (bytes < This->capture_period) + return; + + if (This->started) + pulse_rd_loop(This, bytes); + else + pulse_rd_drop(This, bytes); + + if (This->event) + SetEvent(This->event); +} + +static void pulse_stream_state(pa_stream *s, void *user) +{ + pa_stream_state_t state = pa_stream_get_state(s); + TRACE("Stream state changed to %i\n", state); + pthread_cond_signal(&pulse_cond); +} + +static HRESULT pulse_stream_connect(ACImpl *This, UINT32 period_bytes) { + int ret; + char buffer[64]; + static LONG number; + pa_buffer_attr attr; + if (This->stream) { + pa_stream_disconnect(This->stream); + while (pa_stream_get_state(This->stream) == PA_STREAM_READY) + pthread_cond_wait(&pulse_cond, &pulse_lock); + pa_stream_unref(This->stream); + } + ret = InterlockedIncrement(&number); + sprintf(buffer, "audio stream #%i", ret); + This->stream = pa_stream_new(pulse_ctx, buffer, &This->ss, &This->map); + pa_stream_set_state_callback(This->stream, pulse_stream_state, This); + pa_stream_set_buffer_attr_callback(This->stream, pulse_attr_update, This); + pa_stream_set_moved_callback(This->stream, pulse_attr_update, This); + + /* Pulseaudio will fill in correct values */ + attr.minreq = attr.fragsize = period_bytes; + attr.maxlength = attr.tlength = This->bufsize_bytes; + attr.prebuf = pa_frame_size(&This->ss); + dump_attr(&attr); + if (This->dataflow == eRender) + ret = pa_stream_connect_playback(This->stream, NULL, &attr, + PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_AUTO_TIMING_UPDATE|PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_EARLY_REQUESTS, NULL, NULL); + else + ret = pa_stream_connect_record(This->stream, NULL, &attr, + PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_AUTO_TIMING_UPDATE|PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_EARLY_REQUESTS); + if (ret < 0) { + WARN("Returns %i\n", ret); + return AUDCLNT_E_ENDPOINT_CREATE_FAILED; + } + 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) + return AUDCLNT_E_ENDPOINT_CREATE_FAILED; + + if (This->dataflow == eRender) { + pa_stream_set_write_callback(This->stream, pulse_wr_callback, This); + pa_stream_set_underflow_callback(This->stream, pulse_underflow_callback, This); + pa_stream_set_started_callback(This->stream, pulse_started_callback, This); + } else + pa_stream_set_read_callback(This->stream, pulse_rd_callback, This); + return S_OK; +} + +HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, const WCHAR ***ids, GUID **keys, + UINT *num, UINT *def_index) +{ + HRESULT hr = S_OK; + TRACE("%d %p %p %p\n", flow, ids, num, def_index); + + pthread_mutex_lock(&pulse_lock); + hr = pulse_connect(); + pthread_mutex_unlock(&pulse_lock); + if (FAILED(hr)) + return hr; + *num = 1; + *def_index = 0; + + *ids = HeapAlloc(GetProcessHeap(), 0, sizeof(**ids)); + if (!*ids) + return E_OUTOFMEMORY; + (*ids)[0] = defaultW; + + *keys = HeapAlloc(GetProcessHeap(), 0, sizeof(**keys)); + if (!*keys) { + HeapFree(GetProcessHeap(), 0, *ids); + *ids = NULL; + return E_OUTOFMEMORY; + } + if (flow == eRender) + (*keys)[0] = pulse_render_guid; + else + (*keys)[0] = pulse_capture_guid; + + return S_OK; +} + +int WINAPI AUDDRV_GetPriority(void) +{ + HRESULT hr; + if (getenv("WINENOPULSE")) { + FIXME_(winediag)("winepulse has been temporarily disabled through the environment\n"); + return 0; + } + pthread_mutex_lock(&pulse_lock); + hr = pulse_connect(); + pthread_mutex_unlock(&pulse_lock); + return SUCCEEDED(hr) ? 3 : 0; +} + +HRESULT WINAPI AUDDRV_GetAudioEndpoint(GUID *guid, IMMDevice *dev, IAudioClient **out) +{ + HRESULT hr; + ACImpl *This; + int i; + EDataFlow dataflow; + + /* Give one visible warning per session + * Sadly wine has chosen not to accept the winepulse patch, so support ourselves + */ + if (!warn_once && (warn_once = CreateEventA(0, 0, 0, "__winepulse_warn_event")) && GetLastError() != ERROR_ALREADY_EXISTS) { + FIXME_(winediag)("Winepulse is not officially supported by the wine project\n"); + FIXME_(winediag)("For sound related feedback and support, please visit http://ubuntuforums.org/showthread.php?t=1960599\n"); + } else { + WARN_(winediag)("Winepulse is not officially supported by the wine project\n"); + WARN_(winediag)("For sound related feedback and support, please visit http://ubuntuforums.org/showthread.php?t=1960599\n"); + } + + TRACE("%s %p %p\n", debugstr_guid(guid), dev, out); + if (IsEqualGUID(guid, &pulse_render_guid)) + dataflow = eRender; + else if (IsEqualGUID(guid, &pulse_capture_guid)) + dataflow = eCapture; + else + return E_UNEXPECTED; + + *out = NULL; + pthread_mutex_lock(&pulse_lock); + hr = pulse_connect(); + pthread_mutex_unlock(&pulse_lock); + if (FAILED(hr)) + return hr; + + This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*This)); + 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; + This->parent = dev; + This->clock_pulse = PA_USEC_INVALID; + for (i = 0; i < PA_CHANNELS_MAX; ++i) + This->vol[i] = 1.f; + 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) { + if (This->stream) { + pthread_mutex_lock(&pulse_lock); + if (PA_STREAM_IS_GOOD(pa_stream_get_state(This->stream))) { + pa_stream_disconnect(This->stream); + while (PA_STREAM_IS_GOOD(pa_stream_get_state(This->stream))) + pthread_cond_wait(&pulse_cond, &pulse_lock); + } + pa_stream_unref(This->stream); + This->stream = NULL; + list_remove(&This->entry); + pthread_mutex_unlock(&pulse_lock); + } + IMMDevice_Release(This->parent); + HeapFree(GetProcessHeap(), 0, This->tmp_buffer); + 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 DWORD get_channel_mask(unsigned int channels) +{ + switch(channels) { + case 0: + return 0; + case 1: + return SPEAKER_FRONT_CENTER; + case 2: + return SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; + case 3: + return SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | + SPEAKER_LOW_FREQUENCY; + case 4: + return SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | + SPEAKER_BACK_RIGHT; + case 5: + return SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | + SPEAKER_BACK_RIGHT | SPEAKER_LOW_FREQUENCY; + case 6: + return SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | + SPEAKER_BACK_RIGHT | SPEAKER_LOW_FREQUENCY | SPEAKER_FRONT_CENTER; + case 7: + return SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | + SPEAKER_BACK_RIGHT | SPEAKER_LOW_FREQUENCY | SPEAKER_FRONT_CENTER | + SPEAKER_BACK_CENTER; + case 8: + return SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | + SPEAKER_BACK_RIGHT | SPEAKER_LOW_FREQUENCY | SPEAKER_FRONT_CENTER | + SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT; + } + FIXME("Unknown speaker configuration: %u\n", channels); + return 0; +} + +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); + + 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 HRESULT pulse_spec_from_waveformat(ACImpl *This, const WAVEFORMATEX *fmt) +{ + pa_channel_map_init(&This->map); + This->ss.rate = fmt->nSamplesPerSec; + This->ss.format = PA_SAMPLE_INVALID; + switch(fmt->wFormatTag) { + case WAVE_FORMAT_IEEE_FLOAT: + if (!fmt->nChannels || fmt->nChannels > 2 || fmt->wBitsPerSample != 32) + break; + This->ss.format = PA_SAMPLE_FLOAT32LE; + pa_channel_map_init_auto(&This->map, fmt->nChannels, PA_CHANNEL_MAP_ALSA); + break; + case WAVE_FORMAT_PCM: + if (!fmt->nChannels || fmt->nChannels > 2) + break; + if (fmt->wBitsPerSample == 8) + This->ss.format = PA_SAMPLE_U8; + else if (fmt->wBitsPerSample == 16) + This->ss.format = PA_SAMPLE_S16LE; + else + return AUDCLNT_E_UNSUPPORTED_FORMAT; + pa_channel_map_init_auto(&This->map, fmt->nChannels, PA_CHANNEL_MAP_ALSA); + break; + case WAVE_FORMAT_EXTENSIBLE: { + WAVEFORMATEXTENSIBLE *wfe = (WAVEFORMATEXTENSIBLE*)fmt; + DWORD mask = wfe->dwChannelMask; + DWORD i = 0, j; + if (fmt->cbSize != (sizeof(*wfe) - sizeof(*fmt)) && fmt->cbSize != sizeof(*wfe)) + break; + if (IsEqualGUID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) && + (!wfe->Samples.wValidBitsPerSample || wfe->Samples.wValidBitsPerSample == 32) && + fmt->wBitsPerSample == 32) + This->ss.format = PA_SAMPLE_FLOAT32LE; + else if (IsEqualGUID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM)) { + DWORD valid = wfe->Samples.wValidBitsPerSample; + if (!valid) + valid = fmt->wBitsPerSample; + if (!valid || valid > fmt->wBitsPerSample) + break; + switch (fmt->wBitsPerSample) { + case 8: + if (valid == 8) + This->ss.format = PA_SAMPLE_U8; + break; + case 16: + if (valid == 16) + This->ss.format = PA_SAMPLE_S16LE; + break; + case 24: + if (valid == 24) + This->ss.format = PA_SAMPLE_S24LE; + break; + case 32: + if (valid == 24) + This->ss.format = PA_SAMPLE_S24_32LE; + else if (valid == 32) + This->ss.format = PA_SAMPLE_S32LE; + break; + default: + return AUDCLNT_E_UNSUPPORTED_FORMAT; + } + } + This->map.channels = fmt->nChannels; + if (!mask || mask == SPEAKER_ALL) + mask = get_channel_mask(fmt->nChannels); + else if (mask == ~0U && fmt->nChannels == 1) + mask = SPEAKER_FRONT_CENTER; + for (j = 0; j < sizeof(pulse_pos_from_wfx)/sizeof(*pulse_pos_from_wfx) && i < fmt->nChannels; ++j) { + if (mask & (1 << j)) + This->map.map[i++] = pulse_pos_from_wfx[j]; + } + + /* Special case for mono since pulse appears to map it differently */ + if (mask == SPEAKER_FRONT_CENTER) + This->map.map[0] = PA_CHANNEL_POSITION_MONO; + + if (i < fmt->nChannels || (mask & SPEAKER_RESERVED)) { + This->map.channels = 0; + ERR("Invalid channel mask: %i/%i and %x(%x)\n", i, fmt->nChannels, mask, wfe->dwChannelMask); + break; + } + break; + } + case WAVE_FORMAT_ALAW: + case WAVE_FORMAT_MULAW: + if (fmt->wBitsPerSample != 8) { + FIXME("Unsupported bpp %u for LAW\n", fmt->wBitsPerSample); + return AUDCLNT_E_UNSUPPORTED_FORMAT; + } + if (fmt->nChannels != 1 && fmt->nChannels != 2) { + FIXME("Unsupported channels %u for LAW\n", fmt->nChannels); + return AUDCLNT_E_UNSUPPORTED_FORMAT; + } + This->ss.format = fmt->wFormatTag == WAVE_FORMAT_MULAW ? PA_SAMPLE_ULAW : PA_SAMPLE_ALAW; + pa_channel_map_init_auto(&This->map, fmt->nChannels, PA_CHANNEL_MAP_ALSA); + break; + default: + WARN("Unhandled tag %x\n", fmt->wFormatTag); + return AUDCLNT_E_UNSUPPORTED_FORMAT; + } + This->ss.channels = This->map.channels; + if (!pa_channel_map_valid(&This->map) || This->ss.format == PA_SAMPLE_INVALID) { + ERR("Invalid format! Channel spec valid: %i, format: %i\n", pa_channel_map_valid(&This->map), This->ss.format); + dump_fmt(fmt); + return AUDCLNT_E_UNSUPPORTED_FORMAT; + } + return S_OK; +} + +static HRESULT WINAPI AudioClient_Initialize(IAudioClient *iface, + AUDCLNT_SHAREMODE mode, DWORD flags, REFERENCE_TIME duration, + REFERENCE_TIME period, const WAVEFORMATEX *fmt, + const GUID *sessionguid) +{ + ACImpl *This = impl_from_IAudioClient(iface); + HRESULT hr = S_OK; + UINT period_bytes; + + TRACE("(%p)->(%x, %x, %s, %s, %p, %s)\n", This, mode, flags, + wine_dbgstr_longlong(duration), wine_dbgstr_longlong(period), fmt, debugstr_guid(sessionguid)); + + if (!fmt) + return E_POINTER; + + if (mode != AUDCLNT_SHAREMODE_SHARED && mode != AUDCLNT_SHAREMODE_EXCLUSIVE) + return AUDCLNT_E_NOT_INITIALIZED; + if (mode == AUDCLNT_SHAREMODE_EXCLUSIVE) + return AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED; + + 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; + } + + pthread_mutex_lock(&pulse_lock); + if (This->stream) { + pthread_mutex_unlock(&pulse_lock); + return AUDCLNT_E_ALREADY_INITIALIZED; + } + + hr = pulse_spec_from_waveformat(This, fmt); + if (FAILED(hr)) + goto exit; + + if (mode == AUDCLNT_SHAREMODE_SHARED) { + period = pulse_def_period[This->dataflow == eCapture]; + if (duration < 2 * period) + duration = 2 * period; + } + period_bytes = pa_frame_size(&This->ss) * MulDiv(period, This->ss.rate, 10000000); + + if (duration < 20000000) + This->bufsize_frames = ceil((duration / 10000000.) * fmt->nSamplesPerSec); + else + This->bufsize_frames = 2 * fmt->nSamplesPerSec; + This->bufsize_bytes = This->bufsize_frames * pa_frame_size(&This->ss); + + This->share = mode; + This->flags = flags; + hr = pulse_stream_connect(This, period_bytes); + if (SUCCEEDED(hr)) { + UINT32 unalign; + const pa_buffer_attr *attr = pa_stream_get_buffer_attr(This->stream); + /* Update frames according to new size */ + dump_attr(attr); + if (This->dataflow == eRender) + This->bufsize_bytes = attr->tlength; + else { + This->capture_period = period_bytes = attr->fragsize; + if ((unalign = This->bufsize_bytes % period_bytes)) + This->bufsize_bytes += period_bytes - unalign; + } + This->bufsize_frames = This->bufsize_bytes / pa_frame_size(&This->ss); + } + if (SUCCEEDED(hr)) { + UINT32 i, capture_packets = This->capture_period ? This->bufsize_bytes / This->capture_period : 0; + This->tmp_buffer = HeapAlloc(GetProcessHeap(), 0, This->bufsize_bytes + capture_packets * sizeof(ACPacket)); + if (!This->tmp_buffer) + hr = E_OUTOFMEMORY; + else { + ACPacket *cur_packet = (ACPacket*)((char*)This->tmp_buffer + This->bufsize_bytes); + BYTE *data = This->tmp_buffer; + memset(This->tmp_buffer, This->ss.format == PA_SAMPLE_U8 ? 0x80 : 0, This->bufsize_bytes); + list_init(&This->packet_free_head); + list_init(&This->packet_filled_head); + for (i = 0; i < capture_packets; ++i, ++cur_packet) { + list_add_tail(&This->packet_free_head, &cur_packet->entry); + cur_packet->data = data; + data += This->capture_period; + } + assert(!This->capture_period || This->bufsize_bytes == This->capture_period * capture_packets); + assert(!capture_packets || data - This->bufsize_bytes == This->tmp_buffer); + } + } + if (SUCCEEDED(hr)) + hr = get_audio_session(sessionguid, This->parent, fmt->nChannels, &This->session); + if (SUCCEEDED(hr)) + list_add_tail(&This->session->clients, &This->entry); + +exit: + if (FAILED(hr)) { + HeapFree(GetProcessHeap(), 0, This->tmp_buffer); + This->tmp_buffer = NULL; + if (This->stream) { + pa_stream_disconnect(This->stream); + pa_stream_unref(This->stream); + This->stream = NULL; + } + } + pthread_mutex_unlock(&pulse_lock); + return hr; +} + +static HRESULT WINAPI AudioClient_GetBufferSize(IAudioClient *iface, + UINT32 *out) +{ + ACImpl *This = impl_from_IAudioClient(iface); + HRESULT hr; + + TRACE("(%p)->(%p)\n", This, out); + + if (!out) + return E_POINTER; + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + if (SUCCEEDED(hr)) + *out = This->bufsize_frames; + pthread_mutex_unlock(&pulse_lock); + + return hr; +} + +static HRESULT WINAPI AudioClient_GetStreamLatency(IAudioClient *iface, + REFERENCE_TIME *latency) +{ + ACImpl *This = impl_from_IAudioClient(iface); + const pa_buffer_attr *attr; + REFERENCE_TIME lat; + HRESULT hr; + + TRACE("(%p)->(%p)\n", This, latency); + + if (!latency) + return E_POINTER; + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + if (FAILED(hr)) { + pthread_mutex_unlock(&pulse_lock); + return hr; + } + attr = pa_stream_get_buffer_attr(This->stream); + if (This->dataflow == eRender) + lat = attr->minreq / pa_frame_size(&This->ss); + else + lat = attr->fragsize / pa_frame_size(&This->ss); + *latency = 10000000; + *latency *= lat; + *latency /= This->ss.rate; + pthread_mutex_unlock(&pulse_lock); + TRACE("Latency: %u ms\n", (DWORD)(*latency / 10000)); + return S_OK; +} + +static void ACImpl_GetRenderPad(ACImpl *This, UINT32 *out) +{ + *out = This->pad / pa_frame_size(&This->ss); +} + +static void ACImpl_GetCapturePad(ACImpl *This, UINT32 *out) +{ + ACPacket *packet = This->locked_ptr; + if (!packet && !list_empty(&This->packet_filled_head)) { + packet = (ACPacket*)list_head(&This->packet_filled_head); + This->locked_ptr = packet; + list_remove(&packet->entry); + } + if (out) + *out = This->pad / pa_frame_size(&This->ss); +} + +static HRESULT WINAPI AudioClient_GetCurrentPadding(IAudioClient *iface, + UINT32 *out) +{ + ACImpl *This = impl_from_IAudioClient(iface); + HRESULT hr; + + TRACE("(%p)->(%p)\n", This, out); + + if (!out) + return E_POINTER; + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + if (FAILED(hr)) { + pthread_mutex_unlock(&pulse_lock); + return hr; + } + + if (This->dataflow == eRender) + ACImpl_GetRenderPad(This, out); + else + ACImpl_GetCapturePad(This, out); + pthread_mutex_unlock(&pulse_lock); + + TRACE("%p Pad: %u ms (%u)\n", This, MulDiv(*out, 1000, This->ss.rate), *out); + return S_OK; +} + +static HRESULT WINAPI AudioClient_IsFormatSupported(IAudioClient *iface, + AUDCLNT_SHAREMODE mode, const WAVEFORMATEX *fmt, + WAVEFORMATEX **out) +{ + ACImpl *This = impl_from_IAudioClient(iface); + HRESULT hr = S_OK; + WAVEFORMATEX *closest = NULL; + + TRACE("(%p)->(%x, %p, %p)\n", This, mode, fmt, out); + + if (!fmt || (mode == AUDCLNT_SHAREMODE_SHARED && !out)) + return E_POINTER; + + if (out) + *out = NULL; + if (mode != AUDCLNT_SHAREMODE_SHARED && mode != AUDCLNT_SHAREMODE_EXCLUSIVE) + return E_INVALIDARG; + if (mode == AUDCLNT_SHAREMODE_EXCLUSIVE) + return This->dataflow == eCapture ? AUDCLNT_E_UNSUPPORTED_FORMAT : AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED; + switch (fmt->wFormatTag) { + case WAVE_FORMAT_EXTENSIBLE: + if (fmt->cbSize < sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX)) + return E_INVALIDARG; + dump_fmt(fmt); + break; + case WAVE_FORMAT_ALAW: + case WAVE_FORMAT_MULAW: + case WAVE_FORMAT_IEEE_FLOAT: + case WAVE_FORMAT_PCM: + dump_fmt(fmt); + break; + default: + dump_fmt(fmt); + return AUDCLNT_E_UNSUPPORTED_FORMAT; + } + if (fmt->nChannels == 0) + return AUDCLNT_E_UNSUPPORTED_FORMAT; + closest = clone_format(fmt); + if (!closest) { + if (out) + *out = NULL; + return E_OUTOFMEMORY; + } + + if (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { + UINT32 mask = 0, i, channels = 0; + WAVEFORMATEXTENSIBLE *ext = (WAVEFORMATEXTENSIBLE*)closest; + + if ((fmt->nChannels > 1 && ext->dwChannelMask == SPEAKER_ALL) || + (fmt->nChannels == 1 && ext->dwChannelMask == ~0U)) { + mask = ext->dwChannelMask; + channels = fmt->nChannels; + } else if (ext->dwChannelMask) { + for (i = 1; !(i & SPEAKER_RESERVED); i <<= 1) { + if (i & ext->dwChannelMask) { + mask |= i; + channels++; + } + } + if (channels < fmt->nChannels) + mask = get_channel_mask(fmt->nChannels); + } else + mask = ext->dwChannelMask; + if (ext->dwChannelMask != mask) { + ext->dwChannelMask = mask; + hr = S_FALSE; + } + } + + if (hr == S_OK || !out) { + CoTaskMemFree(closest); + if (out) + *out = NULL; + } else if (closest) { + closest->nBlockAlign = + closest->nChannels * closest->wBitsPerSample / 8; + closest->nAvgBytesPerSec = + closest->nBlockAlign * closest->nSamplesPerSec; + *out = closest; + } + + TRACE("returning: %08x %p\n", hr, out ? *out : NULL); + return hr; +} + +static HRESULT WINAPI AudioClient_GetMixFormat(IAudioClient *iface, + WAVEFORMATEX **pwfx) +{ + ACImpl *This = impl_from_IAudioClient(iface); + WAVEFORMATEXTENSIBLE *fmt = &pulse_fmt[This->dataflow == eCapture]; + + TRACE("(%p)->(%p)\n", This, pwfx); + + if (!pwfx) + return E_POINTER; + + *pwfx = clone_format(&fmt->Format); + if (!*pwfx) + return E_OUTOFMEMORY; + dump_fmt(*pwfx); + 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 = pulse_def_period[This->dataflow == eCapture]; + if (minperiod) + *minperiod = pulse_min_period[This->dataflow == eCapture]; + + return S_OK; +} + +static HRESULT WINAPI AudioClient_Start(IAudioClient *iface) +{ + ACImpl *This = impl_from_IAudioClient(iface); + HRESULT hr = S_OK; + int success; + pa_operation *o; + + TRACE("(%p)\n", This); + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + if (FAILED(hr)) { + pthread_mutex_unlock(&pulse_lock); + return hr; + } + + if ((This->flags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) && !This->event) { + pthread_mutex_unlock(&pulse_lock); + return AUDCLNT_E_EVENTHANDLE_NOT_SET; + } + + if (This->started) { + pthread_mutex_unlock(&pulse_lock); + return AUDCLNT_E_NOT_STOPPED; + } + This->clock_pulse = PA_USEC_INVALID; + + if (pa_stream_is_corked(This->stream)) { + o = pa_stream_cork(This->stream, 0, pulse_op_cb, &success); + if (o) { + while(pa_operation_get_state(o) == PA_OPERATION_RUNNING) + pthread_cond_wait(&pulse_cond, &pulse_lock); + pa_operation_unref(o); + } else + success = 0; + if (!success) + hr = E_FAIL; + } + if (SUCCEEDED(hr)) { + This->started = TRUE; + if (This->dataflow == eRender && This->event) + pa_stream_set_latency_update_callback(This->stream, pulse_latency_callback, This); + } + pthread_mutex_unlock(&pulse_lock); + return hr; +} + +static HRESULT WINAPI AudioClient_Stop(IAudioClient *iface) +{ + ACImpl *This = impl_from_IAudioClient(iface); + HRESULT hr = S_OK; + pa_operation *o; + int success; + + TRACE("(%p)\n", This); + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + if (FAILED(hr)) { + pthread_mutex_unlock(&pulse_lock); + return hr; + } + + if (!This->started) { + pthread_mutex_unlock(&pulse_lock); + return S_FALSE; + } + + if (This->dataflow == eRender) { + o = pa_stream_cork(This->stream, 1, pulse_op_cb, &success); + if (o) { + while(pa_operation_get_state(o) == PA_OPERATION_RUNNING) + pthread_cond_wait(&pulse_cond, &pulse_lock); + pa_operation_unref(o); + } else + success = 0; + if (!success) + hr = E_FAIL; + } + if (SUCCEEDED(hr)) { + This->started = FALSE; + This->clock_pulse = PA_USEC_INVALID; + } + pthread_mutex_unlock(&pulse_lock); + return hr; +} + +static HRESULT WINAPI AudioClient_Reset(IAudioClient *iface) +{ + ACImpl *This = impl_from_IAudioClient(iface); + HRESULT hr = S_OK; + + TRACE("(%p)\n", This); + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + if (FAILED(hr)) { + pthread_mutex_unlock(&pulse_lock); + return hr; + } + + if (This->started) { + pthread_mutex_unlock(&pulse_lock); + return AUDCLNT_E_NOT_STOPPED; + } + + if (This->locked) { + pthread_mutex_unlock(&pulse_lock); + return AUDCLNT_E_BUFFER_OPERATION_PENDING; + } + + if (This->dataflow == eRender) { + /* If there is still data in the render buffer it needs to be removed from the server */ + int success = 0; + if (This->pad) { + pa_operation *o = pa_stream_flush(This->stream, pulse_op_cb, &success); + if (o) { + while(pa_operation_get_state(o) == PA_OPERATION_RUNNING) + pthread_cond_wait(&pulse_cond, &pulse_lock); + pa_operation_unref(o); + } + } + if (success || !This->pad) + This->clock_lastpos = This->clock_written = This->pad = 0; + } else { + ACPacket *p; + This->clock_written += This->pad; + This->pad = 0; + + if ((p = This->locked_ptr)) { + This->locked_ptr = NULL; + list_add_tail(&This->packet_free_head, &p->entry); + } + list_move_tail(&This->packet_free_head, &This->packet_filled_head); + } + pthread_mutex_unlock(&pulse_lock); + + return hr; +} + +static HRESULT WINAPI AudioClient_SetEventHandle(IAudioClient *iface, + HANDLE event) +{ + ACImpl *This = impl_from_IAudioClient(iface); + HRESULT hr; + + TRACE("(%p)->(%p)\n", This, event); + + if (!event) + return E_INVALIDARG; + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + if (FAILED(hr)) { + pthread_mutex_unlock(&pulse_lock); + return hr; + } + + if (!(This->flags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK)) + hr = AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED; + else if (This->event) + hr = HRESULT_FROM_WIN32(ERROR_INVALID_NAME); + else + This->event = event; + pthread_mutex_unlock(&pulse_lock); + return hr; +} + +static HRESULT WINAPI AudioClient_GetService(IAudioClient *iface, REFIID riid, + void **ppv) +{ + ACImpl *This = impl_from_IAudioClient(iface); + HRESULT hr; + + TRACE("(%p)->(%s, %p)\n", This, debugstr_guid(riid), ppv); + + if (!ppv) + return E_POINTER; + *ppv = NULL; + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + pthread_mutex_unlock(&pulse_lock); + if (FAILED(hr)) + return hr; + + if (IsEqualIID(riid, &IID_IAudioRenderClient)) { + if (This->dataflow != eRender) + return AUDCLNT_E_WRONG_ENDPOINT_TYPE; + *ppv = &This->IAudioRenderClient_iface; + } else if (IsEqualIID(riid, &IID_IAudioCaptureClient)) { + if (This->dataflow != eCapture) + return AUDCLNT_E_WRONG_ENDPOINT_TYPE; + *ppv = &This->IAudioCaptureClient_iface; + } else if (IsEqualIID(riid, &IID_IAudioClock)) { + *ppv = &This->IAudioClock_iface; + } else if (IsEqualIID(riid, &IID_IAudioStreamVolume)) { + *ppv = &This->IAudioStreamVolume_iface; + } else if (IsEqualIID(riid, &IID_IAudioSessionControl) || + IsEqualIID(riid, &IID_IChannelAudioVolume) || + IsEqualIID(riid, &IID_ISimpleAudioVolume)) { + if (!This->session_wrapper) { + This->session_wrapper = AudioSessionWrapper_Create(This); + if (!This->session_wrapper) + return E_OUTOFMEMORY; + } + if (IsEqualIID(riid, &IID_IAudioSessionControl)) + *ppv = &This->session_wrapper->IAudioSessionControl2_iface; + else if (IsEqualIID(riid, &IID_IChannelAudioVolume)) + *ppv = &This->session_wrapper->IChannelAudioVolume_iface; + else if (IsEqualIID(riid, &IID_ISimpleAudioVolume)) + *ppv = &This->session_wrapper->ISimpleAudioVolume_iface; + } + + if (*ppv) { + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + FIXME("stub %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 avail, pad, req, bytes = frames * pa_frame_size(&This->ss); + HRESULT hr = S_OK; + int ret = -1; + + TRACE("(%p)->(%u, %p)\n", This, frames, data); + + if (!data) + return E_POINTER; + *data = NULL; + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + if (FAILED(hr) || This->locked) { + pthread_mutex_unlock(&pulse_lock); + return FAILED(hr) ? hr : AUDCLNT_E_OUT_OF_ORDER; + } + if (!frames) { + pthread_mutex_unlock(&pulse_lock); + return S_OK; + } + + ACImpl_GetRenderPad(This, &pad); + avail = This->bufsize_frames - pad; + if (avail < frames || bytes > This->bufsize_bytes) { + pthread_mutex_unlock(&pulse_lock); + WARN("Wanted to write %u, but only %u available\n", frames, avail); + return AUDCLNT_E_BUFFER_TOO_LARGE; + } + + This->locked = frames; + req = bytes; + ret = pa_stream_begin_write(This->stream, &This->locked_ptr, &req); + if (ret < 0 || req < bytes) { + FIXME("%p Not using pulse locked data: %i %u/%u %u/%u\n", This, ret, req/pa_frame_size(&This->ss), frames, pad, This->bufsize_frames); + if (ret >= 0) + pa_stream_cancel_write(This->stream); + *data = This->tmp_buffer; + This->locked_ptr = NULL; + } else + *data = This->locked_ptr; + pthread_mutex_unlock(&pulse_lock); + return hr; +} + +static HRESULT WINAPI AudioRenderClient_ReleaseBuffer( + IAudioRenderClient *iface, UINT32 written_frames, DWORD flags) +{ + ACImpl *This = impl_from_IAudioRenderClient(iface); + UINT32 written_bytes = written_frames * pa_frame_size(&This->ss); + + TRACE("(%p)->(%u, %x)\n", This, written_frames, flags); + + pthread_mutex_lock(&pulse_lock); + if (!This->locked || !written_frames) { + if (This->locked_ptr) + pa_stream_cancel_write(This->stream); + This->locked = 0; + This->locked_ptr = NULL; + pthread_mutex_unlock(&pulse_lock); + return written_frames ? AUDCLNT_E_OUT_OF_ORDER : S_OK; + } + + if (This->locked < written_frames) { + pthread_mutex_unlock(&pulse_lock); + return AUDCLNT_E_INVALID_SIZE; + } + + if (flags & AUDCLNT_BUFFERFLAGS_SILENT) { + if (This->ss.format == PA_SAMPLE_U8) + memset(This->tmp_buffer, 128, written_bytes); + else + memset(This->tmp_buffer, 0, written_bytes); + } + + This->locked = 0; + if (This->locked_ptr) + pa_stream_write(This->stream, This->locked_ptr, written_bytes, NULL, 0, PA_SEEK_RELATIVE); + else + pa_stream_write(This->stream, This->tmp_buffer, written_bytes, NULL, 0, PA_SEEK_RELATIVE); + This->pad += written_bytes; + This->locked_ptr = NULL; + TRACE("Released %u, pad %u\n", written_frames, This->pad / pa_frame_size(&This->ss)); + assert(This->pad <= This->bufsize_bytes); + 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); + HRESULT hr; + ACPacket *packet; + + TRACE("(%p)->(%p, %p, %p, %p, %p)\n", This, data, frames, flags, + devpos, qpcpos); + + if (!data || !frames || !flags) + return E_POINTER; + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + if (FAILED(hr) || This->locked) { + pthread_mutex_unlock(&pulse_lock); + return FAILED(hr) ? hr : AUDCLNT_E_OUT_OF_ORDER; + } + + ACImpl_GetCapturePad(This, NULL); + if ((packet = This->locked_ptr)) { + *frames = This->capture_period / pa_frame_size(&This->ss); + *flags = 0; + if (packet->discont) + *flags |= AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY; + if (devpos) { + if (packet->discont) + *devpos = (This->clock_written + This->capture_period) / pa_frame_size(&This->ss); + else + *devpos = This->clock_written / pa_frame_size(&This->ss); + } + if (qpcpos) + *qpcpos = packet->qpcpos; + *data = packet->data; + } + else + *frames = 0; + This->locked = *frames; + pthread_mutex_unlock(&pulse_lock); + return *frames ? S_OK : AUDCLNT_S_BUFFER_EMPTY; +} + +static HRESULT WINAPI AudioCaptureClient_ReleaseBuffer( + IAudioCaptureClient *iface, UINT32 done) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + + TRACE("(%p)->(%u)\n", This, done); + + pthread_mutex_lock(&pulse_lock); + if (!This->locked && done) { + pthread_mutex_unlock(&pulse_lock); + return AUDCLNT_E_OUT_OF_ORDER; + } + if (done && This->locked != done) { + pthread_mutex_unlock(&pulse_lock); + return AUDCLNT_E_INVALID_SIZE; + } + if (done) { + ACPacket *packet = This->locked_ptr; + This->locked_ptr = NULL; + This->pad -= This->capture_period; + if (packet->discont) + This->clock_written += 2 * This->capture_period; + else + This->clock_written += This->capture_period; + list_add_tail(&This->packet_free_head, &packet->entry); + } + This->locked = 0; + pthread_mutex_unlock(&pulse_lock); + return S_OK; +} + +static HRESULT WINAPI AudioCaptureClient_GetNextPacketSize( + IAudioCaptureClient *iface, UINT32 *frames) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + ACPacket *p; + + TRACE("(%p)->(%p)\n", This, frames); + if (!frames) + return E_POINTER; + + pthread_mutex_lock(&pulse_lock); + ACImpl_GetCapturePad(This, NULL); + p = This->locked_ptr; + if (p) + *frames = This->capture_period / pa_frame_size(&This->ss); + else + *frames = 0; + pthread_mutex_unlock(&pulse_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); + HRESULT hr; + + TRACE("(%p)->(%p)\n", This, freq); + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + if (SUCCEEDED(hr)) + *freq = This->ss.rate * pa_frame_size(&This->ss); + pthread_mutex_unlock(&pulse_lock); + return hr; +} + +static HRESULT WINAPI AudioClock_GetPosition(IAudioClock *iface, UINT64 *pos, + UINT64 *qpctime) +{ + ACImpl *This = impl_from_IAudioClock(iface); + pa_usec_t time; + HRESULT hr; + + TRACE("(%p)->(%p, %p)\n", This, pos, qpctime); + + if (!pos) + return E_POINTER; + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + if (FAILED(hr)) { + pthread_mutex_unlock(&pulse_lock); + return hr; + } + + *pos = This->clock_written; + if (This->clock_pulse != PA_USEC_INVALID && pa_stream_get_time(This->stream, &time) >= 0) { + UINT32 delta = pa_usec_to_bytes(time - This->clock_pulse, &This->ss); + if (delta < This->pad) + *pos += delta; + else + *pos += This->pad; + } + + /* Make time never go backwards */ + if (*pos < This->clock_lastpos) + *pos = This->clock_lastpos; + else + This->clock_lastpos = *pos; + pthread_mutex_unlock(&pulse_lock); + + TRACE("%p Position: %u\n", This, (unsigned)*pos); + + 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); + HRESULT hr = AudioClock_GetPosition(&This->IAudioClock_iface, pos, qpctime); + if (SUCCEEDED(hr)) + *pos /= pa_frame_size(&This->ss); + return hr; +} + +static const IAudioClock2Vtbl AudioClock2_Vtbl = +{ + AudioClock2_QueryInterface, + AudioClock2_AddRef, + AudioClock2_Release, + AudioClock2_GetDevicePosition +}; + +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->ss.channels; + + return S_OK; +} + +struct pulse_info_cb_data { + UINT32 n; + float *levels; +}; + +static void pulse_sink_input_info_cb(pa_context *c, const pa_sink_input_info *info, int eol, void *data) +{ + struct pulse_info_cb_data *d = data; + int i; + if (eol) + return; + for (i = 0; i < d->n; ++i) + d->levels[i] = (float)info->volume.values[i] / (float)PA_VOLUME_NORM; + pthread_cond_signal(&pulse_cond); +} + +static void pulse_source_info_cb(pa_context *c, const pa_source_info *info, int eol, void *data) +{ + struct pulse_info_cb_data *d = data; + int i; + if (eol) + return; + for (i = 0; i < d->n; ++i) + d->levels[i] = (float)info->volume.values[i] / (float)PA_VOLUME_NORM; + pthread_cond_signal(&pulse_cond); +} + +static HRESULT WINAPI AudioStreamVolume_SetAllVolumes( + IAudioStreamVolume *iface, UINT32 count, const float *levels) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + pa_operation *o; + HRESULT hr; + int success = 0, i; + pa_cvolume cv; + + TRACE("(%p)->(%d, %p)\n", This, count, levels); + + if (!levels) + return E_POINTER; + + if (count != This->ss.channels) + return E_INVALIDARG; + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + if (FAILED(hr)) + goto out; + + if (pulse_stream_volume) { + cv.channels = count; + for (i = 0; i < cv.channels; ++i) + cv.values[i] = levels[i] * (float)PA_VOLUME_NORM; + if (This->dataflow == eRender) + o = pa_context_set_sink_input_volume(pulse_ctx, pa_stream_get_index(This->stream), &cv, pulse_ctx_op_cb, &success); + else + o = pa_context_set_source_volume_by_index(pulse_ctx, pa_stream_get_device_index(This->stream), &cv, pulse_ctx_op_cb, &success); + if (o) { + while(pa_operation_get_state(o) == PA_OPERATION_RUNNING) + pthread_cond_wait(&pulse_cond, &pulse_lock); + pa_operation_unref(o); + } + if (!success) + hr = AUDCLNT_E_BUFFER_ERROR; + } else { + int i; + for (i = 0; i < count; ++i) + This->vol[i] = levels[i]; + } + +out: + pthread_mutex_unlock(&pulse_lock); + return hr; +} + +static HRESULT WINAPI AudioStreamVolume_GetAllVolumes( + IAudioStreamVolume *iface, UINT32 count, float *levels) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + pa_operation *o; + HRESULT hr; + struct pulse_info_cb_data info; + + TRACE("(%p)->(%d, %p)\n", This, count, levels); + + if (!levels) + return E_POINTER; + + if (count != This->ss.channels) + return E_INVALIDARG; + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + if (FAILED(hr)) + goto out; + + if (pulse_stream_volume) { + info.n = count; + info.levels = levels; + if (This->dataflow == eRender) + o = pa_context_get_sink_input_info(pulse_ctx, pa_stream_get_index(This->stream), pulse_sink_input_info_cb, &info); + else + o = pa_context_get_source_info_by_index(pulse_ctx, pa_stream_get_device_index(This->stream), pulse_source_info_cb, &info); + if (o) { + while(pa_operation_get_state(o) == PA_OPERATION_RUNNING) + pthread_cond_wait(&pulse_cond, &pulse_lock); + pa_operation_unref(o); + } else + hr = AUDCLNT_E_BUFFER_ERROR; + } else { + int i; + for (i = 0; i < count; ++i) + levels[i] = This->vol[i]; + } + +out: + pthread_mutex_unlock(&pulse_lock); + return hr; +} + +static HRESULT WINAPI AudioStreamVolume_SetChannelVolume( + IAudioStreamVolume *iface, UINT32 index, float level) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + HRESULT hr; + float volumes[PA_CHANNELS_MAX]; + + TRACE("(%p)->(%d, %f)\n", This, index, level); + + if (level < 0.f || level > 1.f) + return E_INVALIDARG; + + if (index >= This->ss.channels) + return E_INVALIDARG; + + hr = AudioStreamVolume_GetAllVolumes(iface, This->ss.channels, volumes); + volumes[index] = level; + if (SUCCEEDED(hr)) + hr = AudioStreamVolume_SetAllVolumes(iface, This->ss.channels, volumes); + return hr; +} + +static HRESULT WINAPI AudioStreamVolume_GetChannelVolume( + IAudioStreamVolume *iface, UINT32 index, float *level) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + float volumes[PA_CHANNELS_MAX]; + HRESULT hr; + + TRACE("(%p)->(%d, %p)\n", This, index, level); + + if (!level) + return E_POINTER; + + if (index >= This->ss.channels) + return E_INVALIDARG; + + hr = AudioStreamVolume_GetAllVolumes(iface, This->ss.channels, volumes); + if (SUCCEEDED(hr)) + *level = volumes[index]; + return hr; +} + +static const IAudioStreamVolumeVtbl AudioStreamVolume_Vtbl = +{ + AudioStreamVolume_QueryInterface, + AudioStreamVolume_AddRef, + AudioStreamVolume_Release, + AudioStreamVolume_GetChannelCount, + AudioStreamVolume_SetChannelVolume, + AudioStreamVolume_GetChannelVolume, + AudioStreamVolume_SetAllVolumes, + AudioStreamVolume_GetAllVolumes +}; + +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 = !client; + + 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) { + This->client->session_wrapper = NULL; + 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; + + pthread_mutex_lock(&pulse_lock); + if (list_empty(&This->session->clients)) { + *state = AudioSessionStateExpired; + goto out; + } + LIST_FOR_EACH_ENTRY(client, &This->session->clients, ACImpl, entry) { + if (client->started) { + *state = AudioSessionStateActive; + goto out; + } + } + *state = AudioSessionStateInactive; + +out: + pthread_mutex_unlock(&pulse_lock); + 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 +}; + +typedef struct _SessionMgr { + IAudioSessionManager2 IAudioSessionManager2_iface; + + LONG ref; + + IMMDevice *device; +} SessionMgr; + +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 inline SessionMgr *impl_from_IAudioSessionManager2(IAudioSessionManager2 *iface) +{ + return CONTAINING_RECORD(iface, SessionMgr, IAudioSessionManager2_iface); +} + +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 +}; + +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 session volume control\n"); + + pthread_mutex_lock(&pulse_lock); + session->master_vol = level; + pthread_mutex_unlock(&pulse_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"); + + 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 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 session volume control\n"); + + pthread_mutex_lock(&pulse_lock); + session->channel_vols[index] = level; + pthread_mutex_unlock(&pulse_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 session volume control\n"); + + pthread_mutex_lock(&pulse_lock); + for(i = 0; i < count; ++i) + session->channel_vols[i] = levels[i]; + pthread_mutex_unlock(&pulse_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 +}; + +HRESULT WINAPI AUDDRV_GetAudioSessionManager(IMMDevice *device, + IAudioSessionManager2 **out) +{ + SessionMgr *This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(SessionMgr)); + *out = NULL; + 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 -- 1.7.3.4