From: Maarten Lankhorst Subject: [PATCH] winepulse: add audio driver Message-Id: <54DB1DB0.7030708@mblankhorst.nl> Date: Wed, 11 Feb 2015 10:15:28 +0100 This is a squash of all the winepulse related patches in repo.or.cz/w/wine/multimedia.git Signed-off-by: Maarten Lankhorst --- diff --git a/configure b/configure index abef0c3..51a420f 100755 --- a/configure +++ b/configure @@ -653,6 +653,8 @@ OSS4_CFLAGS ALSA_LIBS GSTREAMER_LIBS GSTREAMER_CFLAGS +PULSEINCL +PULSELIBS GETTEXTPO_LIBS Z_LIBS FREETYPE_LIBS @@ -827,6 +829,7 @@ with_oss with_pcap with_png with_pthread +with_pulse with_sane with_tiff with_v4l @@ -1296,6 +1299,7 @@ enable_winemapi enable_winemp3_acm enable_wineoss_drv enable_wineps_drv +enable_winepulse_drv enable_wineqtdecoder enable_winex11_drv enable_wing32 @@ -2130,6 +2134,7 @@ Optional Packages: --without-pcap do not use the Packet Capture library --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) @@ -3377,6 +3382,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; @@ -12363,6 +12374,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 if ${GSTREAMER_CFLAGS:+false} :; then : @@ -13674,12 +13766,13 @@ fi test -n "$ALSA_LIBS" || enable_winealsa_drv=${enable_winealsa_drv:-no} test -n "$COREAUDIO_LIBS" || 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$ALSA_LIBS$COREAUDIO_LIBS" = "x" -a \ +if test "x$ALSA_LIBS$COREAUDIO_LIBS$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 @@ -17616,7 +17709,7 @@ wine_fn_config_dll windowscodecs enable_windowscodecs clean,implib wine_fn_config_test dlls/windowscodecs/tests windowscodecs_test wine_fn_config_dll windowscodecsext enable_windowscodecsext implib wine_fn_config_test dlls/windowscodecsext/tests windowscodecsext_test -wine_fn_config_dll winealsa.drv enable_winealsa_drv +wine_fn_config_dll winealsa.drv enable_winealsa_drv implib wine_fn_config_dll winecoreaudio.drv enable_winecoreaudio_drv wine_fn_config_lib winecrt0 wine_fn_config_dll wined3d enable_wined3d implib @@ -17628,6 +17721,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 clean,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 winex11.drv enable_winex11_drv wine_fn_config_dll wing.dll16 enable_win16 diff --git a/configure.ac b/configure.ac index 2e6cb4c..4146e8f 100644 --- a/configure.ac +++ b/configure.ac @@ -72,6 +72,7 @@ AC_ARG_WITH(pcap, AS_HELP_STRING([--without-pcap],[do not use the Packet Ca AC_ARG_WITH(png, AS_HELP_STRING([--without-png],[do not use PNG])) 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])) AC_ARG_WITH(v4l, AS_HELP_STRING([--without-v4l],[do not use v4l1 (v4l support)])) @@ -1548,6 +1549,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 @@ -1766,13 +1791,14 @@ fi dnl **** Disable unsupported winmm drivers **** test -n "$ALSA_LIBS" || enable_winealsa_drv=${enable_winealsa_drv:-no} test -n "$COREAUDIO_LIBS" || 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$ALSA_LIBS$COREAUDIO_LIBS" = "x" -a \ +if test "x$ALSA_LIBS$COREAUDIO_LIBS$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 @@ -3327,7 +3353,7 @@ WINE_CONFIG_DLL(windowscodecs,,[clean,implib]) WINE_CONFIG_TEST(dlls/windowscodecs/tests) WINE_CONFIG_DLL(windowscodecsext,,[implib]) WINE_CONFIG_TEST(dlls/windowscodecsext/tests) -WINE_CONFIG_DLL(winealsa.drv) +WINE_CONFIG_DLL(winealsa.drv,,[implib]) WINE_CONFIG_DLL(winecoreaudio.drv) WINE_CONFIG_LIB(winecrt0) WINE_CONFIG_DLL(wined3d,,[implib]) @@ -3339,6 +3365,7 @@ WINE_CONFIG_DLL(winemp3.acm) WINE_CONFIG_DLL(wineoss.drv) WINE_CONFIG_DLL(wineps.drv,,[clean,po]) WINE_CONFIG_DLL(wineps16.drv16,enable_win16) +WINE_CONFIG_DLL(winepulse.drv) WINE_CONFIG_DLL(wineqtdecoder) WINE_CONFIG_DLL(winex11.drv) WINE_CONFIG_DLL(wing.dll16,enable_win16) diff --git a/dlls/mmdevapi/main.c b/dlls/mmdevapi/main.c index 52cf6f1..aa4baa5 100644 --- a/dlls/mmdevapi/main.c +++ b/dlls/mmdevapi/main.c @@ -113,7 +113,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..3428329 --- /dev/null +++ b/dlls/winepulse.drv/Makefile.in @@ -0,0 +1,8 @@ +MODULE = winepulse.drv +IMPORTS = dxguid uuid winmm user32 advapi32 ole32 +DELAYIMPORTS = winealsa.drv +EXTRALIBS = @PULSELIBS@ $(PTHREAD_LIBS) +EXTRAINCL = @PULSEINCL@ + +C_SRCS = \ + mmdevdrv.c diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c new file mode 100644 index 0000000..ab78d5a --- /dev/null +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -0,0 +1,3182 @@ +/* + * 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 + */ + +#define NONAMELESSUNION +#define COBJMACROS +#define _GNU_SOURCE + +#include "config.h" +#include +#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 "propkey.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); + +/* From */ +enum DriverPriority { + Priority_Unavailable = 0, + Priority_Low, + Priority_Neutral, + Priority_Preferred +}; + +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; +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 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 } }; + +BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, void *reserved) +{ + if (reason == DLL_PROCESS_ATTACH) { + pthread_mutexattr_t attr; + + DisableThreadLibraryCalls(dll); + + pthread_mutexattr_init(&attr); + pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT); + + if (pthread_mutex_init(&pulse_lock, &attr) != 0) + pthread_mutex_init(&pulse_lock, NULL); + } else if (reason == DLL_PROCESS_DETACH) { + if (pulse_thread) + SetThreadPriority(pulse_thread, 0); + 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); + } + 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; + IUnknown *marshal; + 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; + + 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 DWORD pulse_channel_map_to_channel_mask(const pa_channel_map *map) { + int i; + DWORD mask = 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: mask |= SPEAKER_FRONT_LEFT; break; + case PA_CHANNEL_POSITION_MONO: + case PA_CHANNEL_POSITION_FRONT_CENTER: mask |= SPEAKER_FRONT_CENTER; break; + case PA_CHANNEL_POSITION_FRONT_RIGHT: mask |= SPEAKER_FRONT_RIGHT; break; + case PA_CHANNEL_POSITION_REAR_LEFT: mask |= SPEAKER_BACK_LEFT; break; + case PA_CHANNEL_POSITION_REAR_CENTER: mask |= SPEAKER_BACK_CENTER; break; + case PA_CHANNEL_POSITION_REAR_RIGHT: mask |= SPEAKER_BACK_RIGHT; break; + case PA_CHANNEL_POSITION_LFE: mask |= SPEAKER_LOW_FREQUENCY; break; + case PA_CHANNEL_POSITION_SIDE_LEFT: mask |= SPEAKER_SIDE_LEFT; break; + case PA_CHANNEL_POSITION_SIDE_RIGHT: mask |= SPEAKER_SIDE_RIGHT; break; + case PA_CHANNEL_POSITION_TOP_CENTER: mask |= SPEAKER_TOP_CENTER; break; + case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: mask |= SPEAKER_TOP_FRONT_LEFT; break; + case PA_CHANNEL_POSITION_TOP_FRONT_CENTER: mask |= SPEAKER_TOP_FRONT_CENTER; break; + case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: mask |= SPEAKER_TOP_FRONT_RIGHT; break; + case PA_CHANNEL_POSITION_TOP_REAR_LEFT: mask |= SPEAKER_TOP_BACK_LEFT; break; + case PA_CHANNEL_POSITION_TOP_REAR_CENTER: mask |= SPEAKER_TOP_BACK_CENTER; break; + case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: mask |= SPEAKER_TOP_BACK_RIGHT; break; + case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: mask |= SPEAKER_FRONT_LEFT_OF_CENTER; break; + case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: mask |= SPEAKER_FRONT_RIGHT_OF_CENTER; break; + } + + return mask; +} + +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; + 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 = pulse_channel_map_to_channel_mask(&map); +} + +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 silence_buffer(pa_sample_format_t format, BYTE *buffer, UINT32 bytes) +{ + memset(buffer, format == PA_SAMPLE_U8 ? 0x80 : 0, bytes); +} + +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_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; + UINT32 oldpad = This->pad; + + if (bytes < This->bufsize_bytes) + This->pad = This->bufsize_bytes - bytes; + else + This->pad = 0; + + if (oldpad == This->pad) + return; + + assert(oldpad > This->pad); + + This->clock_written += oldpad - This->pad; + TRACE("New pad: %zu (-%zu)\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) +{ + 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) +{ + TRACE("(Re)started playing\n"); +} + +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; + size_t 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) { + size_t 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: %zu, 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); + + if (!This->stream) { + WARN("pa_stream_new returned error %i\n", pa_context_errno(pulse_ctx)); + return AUDCLNT_E_ENDPOINT_CREATE_FAILED; + } + + 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; + WCHAR *id; + + 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)); + *keys = NULL; + if (!*ids) + return E_OUTOFMEMORY; + + (*ids)[0] = id = HeapAlloc(GetProcessHeap(), 0, sizeof(defaultW)); + *keys = HeapAlloc(GetProcessHeap(), 0, sizeof(**keys)); + if (!*keys || !id) { + HeapFree(GetProcessHeap(), 0, id); + HeapFree(GetProcessHeap(), 0, *keys); + HeapFree(GetProcessHeap(), 0, *ids); + *ids = NULL; + *keys = NULL; + return E_OUTOFMEMORY; + } + memcpy(id, defaultW, sizeof(defaultW)); + + if (flow == eRender) + (*keys)[0] = pulse_render_guid; + else + (*keys)[0] = pulse_capture_guid; + + return S_OK; +} + +int WINAPI AUDDRV_GetPriority(void) +{ + HRESULT hr; + pthread_mutex_lock(&pulse_lock); + hr = pulse_connect(); + pthread_mutex_unlock(&pulse_lock); + return SUCCEEDED(hr) ? Priority_Preferred : Priority_Unavailable; +} + +HRESULT WINAPI AUDDRV_GetAudioEndpoint(GUID *guid, IMMDevice *dev, IAudioClient **out) +{ + HRESULT hr; + ACImpl *This; + int i; + EDataFlow dataflow; + + 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; + for (i = 0; i < PA_CHANNELS_MAX; ++i) + This->vol[i] = 1.f; + + hr = CoCreateFreeThreadedMarshaler((IUnknown*)This, &This->marshal); + if (hr) { + HeapFree(GetProcessHeap(), 0, This); + return hr; + } + 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) +{ + ACImpl *This = impl_from_IAudioClient(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_IAudioClient)) + *ppv = iface; + if (*ppv) { + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + if (IsEqualIID(riid, &IID_IMarshal)) + return IUnknown_QueryInterface(This->marshal, riid, ppv); + + 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); + } + IUnknown_Release(This->marshal); + 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 KSAUDIO_SPEAKER_MONO; + case 2: + return KSAUDIO_SPEAKER_STEREO; + case 3: + return KSAUDIO_SPEAKER_STEREO | SPEAKER_LOW_FREQUENCY; + case 4: + return KSAUDIO_SPEAKER_QUAD; /* not _SURROUND */ + case 5: + return KSAUDIO_SPEAKER_QUAD | SPEAKER_LOW_FREQUENCY; + case 6: + return KSAUDIO_SPEAKER_5POINT1; /* not 5POINT1_SURROUND */ + case 7: + return KSAUDIO_SPEAKER_5POINT1 | SPEAKER_BACK_CENTER; + case 8: + return KSAUDIO_SPEAKER_7POINT1_SURROUND; /* Vista deprecates 7POINT1 */ + } + 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|SPEAKER_RESERVED))) + mask = get_channel_mask(fmt->nChannels); + 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); + 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); + TRACE("Obtaining format returns %08x\n", hr); + dump_fmt(fmt); + + if (FAILED(hr)) + goto exit; + + if (mode == AUDCLNT_SHAREMODE_SHARED) { + REFERENCE_TIME def = pulse_def_period[This->dataflow == eCapture]; + REFERENCE_TIME min = pulse_min_period[This->dataflow == eCapture]; + + /* Switch to low latency mode if below 2 default periods, + * which is 20 ms by default, this will increase the amount + * of interrupts but allows very low latency. In dsound I + * managed to get a total latency of ~8ms, which is well below + * default + */ + if (duration < 2 * def) + period = min; + else + period = def; + if (duration < 2 * period) + duration = 2 * period; + + /* Uh oh, really low latency requested.. */ + if (duration <= 2 * period) + period /= 2; + } + 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; + silence_buffer(This->ss.format, This->tmp_buffer, 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; + BOOL exclusive; + + TRACE("(%p)->(%x, %p, %p)\n", This, mode, fmt, out); + + if (!fmt) + return E_POINTER; + + if (out) + *out = NULL; + + if (mode == AUDCLNT_SHAREMODE_EXCLUSIVE) { + exclusive = 1; + out = NULL; + } else if (mode == AUDCLNT_SHAREMODE_SHARED) { + exclusive = 0; + if (!out) + return E_POINTER; + } else + return E_INVALIDARG; + + if (fmt->nChannels == 0) + return AUDCLNT_E_UNSUPPORTED_FORMAT; + + closest = clone_format(fmt); + if (!closest) + return E_OUTOFMEMORY; + + dump_fmt(fmt); + + switch (fmt->wFormatTag) { + case WAVE_FORMAT_EXTENSIBLE: { + WAVEFORMATEXTENSIBLE *ext = (WAVEFORMATEXTENSIBLE*)closest; + + if ((fmt->cbSize != sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX) && + fmt->cbSize != sizeof(WAVEFORMATEXTENSIBLE)) || + fmt->nBlockAlign != fmt->wBitsPerSample / 8 * fmt->nChannels || + ext->Samples.wValidBitsPerSample > fmt->wBitsPerSample || + fmt->nAvgBytesPerSec != fmt->nBlockAlign * fmt->nSamplesPerSec) { + hr = E_INVALIDARG; + break; + } + + if (exclusive) { + UINT32 mask = 0, i, channels = 0; + + if (!(ext->dwChannelMask & (SPEAKER_ALL | SPEAKER_RESERVED))) { + for (i = 1; !(i & SPEAKER_RESERVED); i <<= 1) { + if (i & ext->dwChannelMask) { + mask |= i; + channels++; + } + } + + if (channels != fmt->nChannels || (ext->dwChannelMask & ~mask)) { + hr = AUDCLNT_E_UNSUPPORTED_FORMAT; + break; + } + } else { + hr = AUDCLNT_E_UNSUPPORTED_FORMAT; + break; + } + } + + if (IsEqualGUID(&ext->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) { + if (fmt->wBitsPerSample != 32) { + hr = E_INVALIDARG; + break; + } + + if (ext->Samples.wValidBitsPerSample != fmt->wBitsPerSample) { + hr = S_FALSE; + ext->Samples.wValidBitsPerSample = fmt->wBitsPerSample; + } + } else if (IsEqualGUID(&ext->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM)) { + if (!fmt->wBitsPerSample || fmt->wBitsPerSample > 32 || fmt->wBitsPerSample % 8) { + hr = E_INVALIDARG; + break; + } + + if (ext->Samples.wValidBitsPerSample != fmt->wBitsPerSample && + !(fmt->wBitsPerSample == 32 && + ext->Samples.wValidBitsPerSample == 24)) { + hr = S_FALSE; + ext->Samples.wValidBitsPerSample = fmt->wBitsPerSample; + break; + } + } else { + hr = AUDCLNT_E_UNSUPPORTED_FORMAT; + break; + } + + break; + } + + case WAVE_FORMAT_ALAW: + case WAVE_FORMAT_MULAW: + if (fmt->wBitsPerSample != 8) { + hr = E_INVALIDARG; + break; + } + /* Fall-through */ + case WAVE_FORMAT_IEEE_FLOAT: + if (fmt->wFormatTag == WAVE_FORMAT_IEEE_FLOAT && fmt->wBitsPerSample != 32) { + hr = E_INVALIDARG; + break; + } + /* Fall-through */ + case WAVE_FORMAT_PCM: + if (fmt->wFormatTag == WAVE_FORMAT_PCM && + (!fmt->wBitsPerSample || fmt->wBitsPerSample > 32 || fmt->wBitsPerSample % 8)) { + hr = E_INVALIDARG; + break; + } + + if (fmt->nChannels > 2) { + hr = AUDCLNT_E_UNSUPPORTED_FORMAT; + break; + } + /* + * fmt->cbSize, fmt->nBlockAlign and fmt->nAvgBytesPerSec seem to be + * ignored, invalid values are happily accepted. + */ + break; + default: + hr = AUDCLNT_E_UNSUPPORTED_FORMAT; + break; + } + + if (exclusive && hr != S_OK) { + hr = AUDCLNT_E_UNSUPPORTED_FORMAT; + CoTaskMemFree(closest); + } else if (hr != S_FALSE) + CoTaskMemFree(closest); + else + *out = closest; + + /* Winepulse does not currently support exclusive mode, if you know of an + * application that uses it, I will correct this.. + */ + if (hr == S_OK && exclusive) + return This->dataflow == eCapture ? AUDCLNT_E_UNSUPPORTED_FORMAT : AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED; + + 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; + } + + 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; + } + 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) +{ + ACImpl *This = impl_from_IAudioRenderClient(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_IAudioRenderClient)) + *ppv = iface; + if (*ppv) { + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + if (IsEqualIID(riid, &IID_IMarshal)) + return IUnknown_QueryInterface(This->marshal, riid, ppv); + + 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); + size_t avail, req, bytes = frames * pa_frame_size(&This->ss); + UINT32 pad; + 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 %zu 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 %zu/%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 void pulse_free_noop(void *buf) +{ +} + +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; + } + + This->locked = 0; + if (This->locked_ptr) { + if (flags & AUDCLNT_BUFFERFLAGS_SILENT) + silence_buffer(This->ss.format, This->locked_ptr, written_bytes); + pa_stream_write(This->stream, This->locked_ptr, written_bytes, NULL, 0, PA_SEEK_RELATIVE); + } else { + if (flags & AUDCLNT_BUFFERFLAGS_SILENT) + silence_buffer(This->ss.format, This->tmp_buffer, written_bytes); + pa_stream_write(This->stream, This->tmp_buffer, written_bytes, pulse_free_noop, 0, PA_SEEK_RELATIVE); + } + + This->pad += written_bytes; + This->locked_ptr = NULL; + TRACE("Released %u, pad %zu\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) +{ + ACImpl *This = impl_from_IAudioCaptureClient(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_IAudioCaptureClient)) + *ppv = iface; + if (*ppv) { + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + if (IsEqualIID(riid, &IID_IMarshal)) + return IUnknown_QueryInterface(This->marshal, riid, ppv); + + 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); + + TRACE("(%p)->(%p)\n", This, frames); + if (!frames) + return E_POINTER; + + pthread_mutex_lock(&pulse_lock); + ACImpl_GetCapturePad(This, NULL); + if (This->locked_ptr) + *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; + } + + if (IsEqualIID(riid, &IID_IMarshal)) + return IUnknown_QueryInterface(This->marshal, riid, ppv); + + 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)) { + if (This->share == AUDCLNT_SHAREMODE_SHARED) + *freq = This->ss.rate * pa_frame_size(&This->ss); + else + *freq = This->ss.rate; + } + pthread_mutex_unlock(&pulse_lock); + return hr; +} + +static HRESULT WINAPI AudioClock_GetPosition(IAudioClock *iface, UINT64 *pos, + UINT64 *qpctime) +{ + ACImpl *This = impl_from_IAudioClock(iface); + 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->share == AUDCLNT_SHAREMODE_EXCLUSIVE) + *pos /= pa_frame_size(&This->ss); + + /* 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) && This->share == AUDCLNT_SHAREMODE_SHARED) + *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) +{ + ACImpl *This = impl_from_IAudioStreamVolume(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_IAudioStreamVolume)) + *ppv = iface; + if (*ppv) { + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + if (IsEqualIID(riid, &IID_IMarshal)) + return IUnknown_QueryInterface(This->marshal, riid, ppv); + + 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 HRESULT WINAPI AudioStreamVolume_SetAllVolumes( + IAudioStreamVolume *iface, UINT32 count, const float *levels) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + HRESULT hr; + int i; + + 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; + + 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); + HRESULT hr; + int i; + + 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; + + 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; +} + +static void pulse_phys_speakers_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) +{ + PROPVARIANT *pv = userdata; + + if (i) + pv->u.ulVal |= pulse_channel_map_to_channel_mask(&i->channel_map); + + pthread_cond_signal(&pulse_cond); +} + +HRESULT WINAPI AUDDRV_GetPropValue(GUID *guid, const PROPERTYKEY *prop, PROPVARIANT *out) +{ + pa_operation *o; + + TRACE("%s, (%s,%u), %p\n", wine_dbgstr_guid(guid), wine_dbgstr_guid(&prop->fmtid), prop->pid, out); + + if (IsEqualGUID(guid, &pulse_render_guid) && IsEqualPropertyKey(*prop, PKEY_AudioEndpoint_PhysicalSpeakers)) { + /* For default Pulseaudio render device, OR together all of the + * PKEY_AudioEndpoint_PhysicalSpeakers values of the sinks. */ + + out->vt = VT_UI4; + out->u.ulVal = 0; + + pthread_mutex_lock(&pulse_lock); + o = pa_context_get_sink_info_list(pulse_ctx, &pulse_phys_speakers_cb, out); + if (o) { + while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) + pthread_cond_wait(&pulse_cond, &pulse_lock); + pa_operation_unref(o); + } + pthread_mutex_unlock(&pulse_lock); + return out->u.ulVal ? S_OK : E_FAIL; + } + + return E_NOTIMPL; +} diff --git a/dlls/winepulse.drv/winepulse.drv.spec b/dlls/winepulse.drv/winepulse.drv.spec new file mode 100644 index 0000000..7aeb175 --- /dev/null +++ b/dlls/winepulse.drv/winepulse.drv.spec @@ -0,0 +1,11 @@ +# 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 +@ stdcall -private GetPropValue(ptr ptr ptr) AUDDRV_GetPropValue + +# WinMM driver functions +@ stdcall -private DriverProc(long long long long long) winealsa.drv.DriverProc +@ stdcall -private midMessage(long long long long long) winealsa.drv.midMessage +@ stdcall -private modMessage(long long long long long) winealsa.drv.modMessage diff --git a/include/config.h.in b/include/config.h.in index 4d7c7ac..0e44fd7 100644 --- a/include/config.h.in +++ b/include/config.h.in @@ -687,6 +687,12 @@ /* Define to 1 if you have the header file. */ #undef HAVE_PTHREAD_NP_H +/* Define if you have pulseaudio */ +#undef HAVE_PULSEAUDIO + +/* Define to 1 if you have the header file. */ +#undef HAVE_PULSE_PULSEAUDIO_H + /* Define to 1 if you have the header file. */ #undef HAVE_PWD_H