diff --git a/configure b/configure index 6bf0887acc1..1e7e28f5b79 100755 --- a/configure +++ b/configure @@ -654,6 +654,8 @@ OSS4_CFLAGS ALSA_LIBS GSTREAMER_LIBS GSTREAMER_CFLAGS +PULSE_CFLAGS +PULSE_LIBS GETTEXTPO_LIBS Z_LIBS FREETYPE_LIBS @@ -830,6 +832,7 @@ with_oss with_pcap with_png with_pthread +with_pulse with_sane with_tiff with_v4l @@ -1341,6 +1344,7 @@ enable_winemapi enable_winemp3_acm enable_wineoss_drv enable_wineps_drv +enable_winepulse_drv enable_wineqtdecoder enable_winex11_drv enable_wing32 @@ -1523,6 +1527,8 @@ LCMS2_CFLAGS LCMS2_LIBS FREETYPE_CFLAGS FREETYPE_LIBS +PULSE_CFLAGS +PULSE_LIBS GSTREAMER_CFLAGS GSTREAMER_LIBS CAPI20_CFLAGS @@ -2205,6 +2211,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) @@ -2265,6 +2272,9 @@ Some influential environment variables: C compiler flags for freetype2, overriding pkg-config FREETYPE_LIBS Linker flags for freetype2, overriding pkg-config + PULSE_CFLAGS + C compiler flags for libpulse, overriding pkg-config + PULSE_LIBS Linker flags for libpulse, overriding pkg-config GSTREAMER_CFLAGS C compiler flags for gstreamer-app-0.10, overriding pkg-config GSTREAMER_LIBS @@ -3452,6 +3462,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; @@ -12576,6 +12592,94 @@ esac fi fi +PULSE_LIBS="" + +PULSE_CFLAGS="" + +if test "x$with_pulse" != "xno"; +then + if ${PULSE_CFLAGS:+false} :; then : + if ${PKG_CONFIG+:} false; then : + PULSE_CFLAGS=`$PKG_CONFIG --cflags libpulse 2>/dev/null` +fi +fi + +if ${PULSE_LIBS:+false} :; then : + if ${PKG_CONFIG+:} false; then : + PULSE_LIBS=`$PKG_CONFIG --libs libpulse 2>/dev/null` +fi +fi + + +$as_echo "$as_me:${as_lineno-$LINENO}: libpulse cflags: $PULSE_CFLAGS" >&5 +$as_echo "$as_me:${as_lineno-$LINENO}: libpulse libs: $PULSE_LIBS" >&5 +ac_save_CPPFLAGS=$CPPFLAGS +CPPFLAGS="$CPPFLAGS $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 $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 : + : +fi + +fi + +done + +CPPFLAGS=$ac_save_CPPFLAGS +test -z "$PULSE_CFLAGS" || PULSE_CFLAGS=`echo " $PULSE_CFLAGS" | sed 's/ -I\([^/]\)/ -I\$(top_builddir)\/\1/g'` +test -z "$PULSE_LIBS" || PULSE_LIBS=`echo " $PULSE_LIBS" | sed 's/ -L\([^/]\)/ -L\$(top_builddir)\/\1/g'` + +fi +if test "$ac_cv_lib_pulse_pa_stream_is_corked" != "yes"; then : + case "x$with_pulse" in + x) as_fn_append wine_notices "|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 : @@ -13922,12 +14026,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 "$PULSE_LIBS" || 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" -o "$ac_cv_header_IOKit_hid_IOHIDLib_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$PULSE_LIBS" = "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 @@ -17177,6 +17282,8 @@ FREETYPE_CFLAGS = $FREETYPE_CFLAGS FREETYPE_LIBS = $FREETYPE_LIBS Z_LIBS = $Z_LIBS GETTEXTPO_LIBS = $GETTEXTPO_LIBS +PULSE_LIBS = $PULSE_LIBS +PULSE_CFLAGS = $PULSE_CFLAGS GSTREAMER_CFLAGS = $GSTREAMER_CFLAGS GSTREAMER_LIBS = $GSTREAMER_LIBS ALSA_LIBS = $ALSA_LIBS @@ -17954,6 +18061,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 bb879b07b69..722230d21d9 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)])) @@ -1547,6 +1548,18 @@ then [GetText ${notice_platform}development files not found (or too old), po files can't be rebuilt.]) fi +dnl **** Check for PulseAudio **** +AC_SUBST(PULSE_LIBS,"") +AC_SUBST(PULSE_CFLAGS,"") +if test "x$with_pulse" != "xno"; +then + WINE_PACKAGE_FLAGS(PULSE,[libpulse],,,, + [AC_CHECK_HEADERS(pulse/pulseaudio.h, + [AC_CHECK_LIB(pulse, pa_stream_is_corked,[:],,[$PULSE_LIBS])])]) +fi +WINE_NOTICE_WITH(pulse, [test "$ac_cv_lib_pulse_pa_stream_is_corked" != "yes"], + [libpulse ${notice_platform}development files not found or too old, Pulse won't be supported.]) + dnl **** Check for gstreamer **** if test "x$with_gstreamer" != "xno" then @@ -1779,13 +1792,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 "$PULSE_LIBS" || 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" -o "$ac_cv_header_IOKit_hid_IOHIDLib_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$PULSE_LIBS" = "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 @@ -3402,6 +3416,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 52cf6f146e7..aa4baa56c6e 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 00000000000..d6600632360 --- /dev/null +++ b/dlls/winepulse.drv/Makefile.in @@ -0,0 +1,7 @@ +MODULE = winepulse.drv +IMPORTS = dxguid uuid winmm user32 advapi32 ole32 +EXTRALIBS = $(PULSE_LIBS) $(PTHREAD_LIBS) +EXTRAINCL = $(PULSE_CFLAGS) + +C_SRCS = \ + mmdevdrv.c diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c new file mode 100644 index 00000000000..28db9be57cd --- /dev/null +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -0,0 +1,282 @@ +/* + * 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 "mmdeviceapi.h" +#include "audioclient.h" +#include "endpointvolume.h" +#include "audiopolicy.h" + +WINE_DEFAULT_DEBUG_CHANNEL(pulse); + +#define NULL_PTR_ERR MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, RPC_X_NULL_REF_POINTER) + +/* 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 pthread_mutex_t pulse_lock; + +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_ctx) { + pa_context_disconnect(pulse_ctx); + pa_context_unref(pulse_ctx); + } + if (pulse_ml) + pa_mainloop_quit(pulse_ml, 0); + } + return TRUE; +} + + +static const WCHAR defaultW[] = {'P','u','l','s','e','a','u','d','i','o',0}; + +/* 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 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; + } +} + + +/* some poorly-behaved applications call audio functions during DllMain, so we + * have to do as much as possible without creating a new thread. this function + * sets up a synchronous connection to verify the server is running and query + * static data. */ +static HRESULT pulse_test_connect(void) +{ + int len, ret; + WCHAR path[PATH_MAX], *name; + char *str; + + pulse_ml = pa_mainloop_new(); + + pa_mainloop_set_poll_func(pulse_ml, pulse_poll_func, NULL); + + 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"); + pa_mainloop_free(pulse_ml); + pulse_ml = NULL; + 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 (pa_mainloop_iterate(pulse_ml, 1, &ret) >= 0) { + 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("Test-connected to server %s with protocol version: %i.\n", + pa_context_get_server(pulse_ctx), + pa_context_get_server_protocol_version(pulse_ctx)); + + pa_context_unref(pulse_ctx); + pulse_ctx = NULL; + pa_mainloop_free(pulse_ml); + pulse_ml = NULL; + + return S_OK; + +fail: + pa_context_unref(pulse_ctx); + pulse_ctx = NULL; + pa_mainloop_free(pulse_ml); + pulse_ml = NULL; + + return E_FAIL; +} + +HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, const WCHAR ***ids, GUID **keys, + UINT *num, UINT *def_index) +{ + WCHAR *id; + + TRACE("%d %p %p %p\n", flow, ids, num, def_index); + + *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_test_connect(); + pthread_mutex_unlock(&pulse_lock); + return SUCCEEDED(hr) ? Priority_Low : Priority_Unavailable; +} + +HRESULT WINAPI AUDDRV_GetAudioEndpoint(GUID *guid, IMMDevice *dev, IAudioClient **out) +{ + TRACE("%s %p %p\n", debugstr_guid(guid), dev, out); + *out = NULL; + return E_NOTIMPL; +} + +HRESULT WINAPI AUDDRV_GetAudioSessionManager(IMMDevice *device, + IAudioSessionManager2 **out) +{ + *out = NULL; + return E_NOTIMPL; +} diff --git a/dlls/winepulse.drv/winepulse.drv.spec b/dlls/winepulse.drv/winepulse.drv.spec new file mode 100644 index 00000000000..612bf4634be --- /dev/null +++ b/dlls/winepulse.drv/winepulse.drv.spec @@ -0,0 +1,5 @@ +# MMDevAPI driver functions +@ stdcall -private GetPriority() AUDDRV_GetPriority +@ stdcall -private GetEndpointIDs(long ptr ptr ptr ptr) AUDDRV_GetEndpointIDs +@ stdcall -private GetAudioEndpoint(ptr ptr ptr) AUDDRV_GetAudioEndpoint +@ stdcall -private GetAudioSessionManager(ptr ptr) AUDDRV_GetAudioSessionManager diff --git a/include/config.h.in b/include/config.h.in index eb61a940a5f..61817f43841 100644 --- a/include/config.h.in +++ b/include/config.h.in @@ -696,6 +696,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_PTHREAD_NP_H +/* 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