diff --git a/configure.ac b/configure.ac index 91850de..b21be92 100644 --- a/configure.ac +++ b/configure.ac @@ -55,6 +55,7 @@ AC_ARG_WITH(oss, AS_HELP_STRING([--without-oss],[do not use the OSS sound [if test "x$withval" = "xno"; then ac_cv_header_soundcard_h=no; ac_cv_header_sys_soundcard_h=no; ac_cv_header_machine_soundcard_h=no; fi]) AC_ARG_WITH(png, AS_HELP_STRING([--without-png],[do not use PNG]), [if test "x$withval" = "xno"; then ac_cv_header_png_h=no; fi]) +AC_ARG_WITH(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(xcomposite,AS_HELP_STRING([--without-xcomposite],[do not use the Xcomposite extension]), [if test "x$withval" = "xno"; then ac_cv_header_X11_extensions_Xcomposite_h=no; fi]) @@ -1117,6 +1118,29 @@ then CFLAGS="$save_CFLAGS" fi +dnl **** Check for PulseAudio **** +if test "x$with_pulse" != "xno"; then + if test "$PKG_CONFIG" != "false"; then + AC_MSG_CHECKING([for pulseaudio >= 0.9.8]) + if "$PKG_CONFIG" --atleast-version=0.9.8 libpulse; then + have_pulseaudio="yes" + else + have_pulseaudio="no" + fi + AC_MSG_RESULT([$have_pulseaudio]) + if test x"$have_pulseaudio" = xyes; then + if "$PKG_CONFIG" --atleast-version=0.9.11 libpulse; then + AC_DEFINE([HAVE_PULSEAUDIO_0_9_11], 1, [define this if pulseaudio is at least version 0.9.11]) + else + AC_DEFINE([HAVE_PULSEAUDIO_0_9_11], 0, [define this if pulseaudio is at least version 0.9.11]) + fi + ac_pulse_libs=`$PKG_CONFIG --libs libpulse` + AC_DEFINE([HAVE_PULSEAUDIO], 1, [define this if you have pulseaudio]) + AC_SUBST(PULSELIBS, "$ac_pulse_libs") + fi + fi +fi + dnl **** Check for ALSA 1.x **** AC_SUBST(ALSALIBS,"") if test "$ac_cv_header_sys_asoundlib_h" = "yes" -o "$ac_cv_header_alsa_asoundlib_h" = "yes" @@ -1222,7 +1246,7 @@ dnl **** Check for libodbc **** WINE_CHECK_SONAME(odbc,SQLConnect,,[AC_DEFINE_UNQUOTED(SONAME_LIBODBC,["libodbc.$LIBEXT"])]) dnl **** Check for any sound system **** -if test "x$ALSALIBS$AUDIOIOLIBS$COREAUDIO$NASLIBS$ESDLIBS$ac_cv_lib_soname_jack" = "x" -a \ +if test "x$ALSALIBS$AUDIOIOLIBS$COREAUDIO$NASLIBS$ESDLIBS$PULSELIBS$ac_cv_lib_soname_jack" = "x" -a \ "$ac_cv_header_sys_soundcard_h" != "yes" -a \ "$ac_cv_header_machine_soundcard_h" != "yes" -a \ "$ac_cv_header_soundcard_h" != "yes" -a \ @@ -2064,6 +2088,7 @@ WINE_CONFIG_MAKEFILE([dlls/winemp3.acm/Makefile],[dlls/Makedll.rules],[dlls],[AL WINE_CONFIG_MAKEFILE([dlls/winenas.drv/Makefile],[dlls/Makedll.rules],[dlls],[ALL_DLL_DIRS]) WINE_CONFIG_MAKEFILE([dlls/wineoss.drv/Makefile],[dlls/Makedll.rules],[dlls],[ALL_DLL_DIRS]) WINE_CONFIG_MAKEFILE([dlls/wineps.drv/Makefile],[dlls/Makedll.rules],[dlls],[ALL_DLL_DIRS]) +WINE_CONFIG_MAKEFILE([dlls/winepulse.drv/Makefile],[dlls/Makedll.rules],[dlls],[ALL_DLL_DIRS]) WINE_CONFIG_MAKEFILE([dlls/winequartz.drv/Makefile],[dlls/Makedll.rules],[dlls],[ALL_DLL_DIRS]) WINE_CONFIG_MAKEFILE([dlls/winex11.drv/Makefile],[dlls/Makedll.rules],[dlls],[ALL_DLL_DIRS]) WINE_CONFIG_MAKEFILE([dlls/wing32/Makefile],[dlls/Makedll.rules],[dlls],[ALL_DLL_DIRS]) diff --git a/dlls/winepulse.drv/Makefile.in b/dlls/winepulse.drv/Makefile.in new file mode 100644 index 0000000..52a6671 --- /dev/null +++ b/dlls/winepulse.drv/Makefile.in @@ -0,0 +1,15 @@ +TOPSRCDIR = @top_srcdir@ +TOPOBJDIR = ../.. +SRCDIR = @srcdir@ +VPATH = @srcdir@ +MODULE = winepulse.drv +IMPORTS = dxguid uuid winmm user32 advapi32 kernel32 +EXTRALIBS = @PULSELIBS@ + +C_SRCS = \ + waveout.c \ + pulse.c + +@MAKE_DLL_RULES@ + +@DEPENDENCIES@ # everything below this line is overwritten by make depend diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c new file mode 100644 index 0000000..3ef6a03 --- /dev/null +++ b/dlls/winepulse.drv/pulse.c @@ -0,0 +1,732 @@ +/* + * Wine Driver for PulseAudio + * http://pulseaudio.org/ + * + * Copyright 2008 Arthur Taylor + * + * Contains code from other wine sound drivers. + * + * 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 + */ + +#include "config.h" + +#include +#include + +#include "windef.h" +#include "winbase.h" +#include "wingdi.h" +#include "winuser.h" +#include "mmddk.h" + +#ifdef HAVE_UNISTD_H +# include +#endif +#include + +#ifdef HAVE_PULSEAUDIO + +#include "wine/unicode.h" +#include "wine/debug.h" +#include "wine/library.h" + +#include +#include +WINE_DEFAULT_DEBUG_CHANNEL(wave); + +/* + * - Need to subscribe to sink/source events and keep the WInDev and WOutDev + * structures updated + */ + +/* These strings used only for tracing */ +const char * PULSE_getCmdString(enum win_wm_message msg) { + static char unknown[32]; +#define MSG_TO_STR(x) case x: return #x + switch(msg) { + MSG_TO_STR(WINE_WM_PAUSING); + MSG_TO_STR(WINE_WM_RESTARTING); + MSG_TO_STR(WINE_WM_RESETTING); + MSG_TO_STR(WINE_WM_HEADER); + MSG_TO_STR(WINE_WM_BREAKLOOP); + MSG_TO_STR(WINE_WM_CLOSING); + MSG_TO_STR(WINE_WM_STARTING); + MSG_TO_STR(WINE_WM_STOPPING); + MSG_TO_STR(WINE_WM_XRUN); + MSG_TO_STR(WINE_WM_FEED); + } +#undef MSG_TO_STR + sprintf(unknown, "UNKNOWN(0x%08x)", msg); + return unknown; +} + +/*======================================================================* + * Ring Buffer Functions - copied from winealsa.drv * + *======================================================================*/ + +/* unless someone makes a wineserver kernel module, Unix pipes are faster than win32 events */ +#define USE_PIPE_SYNC + +#ifdef USE_PIPE_SYNC +#define INIT_OMR(omr) do { if (pipe(omr->msg_pipe) < 0) { omr->msg_pipe[0] = omr->msg_pipe[1] = -1; } } while (0) +#define CLOSE_OMR(omr) do { close(omr->msg_pipe[0]); close(omr->msg_pipe[1]); } while (0) +#define SIGNAL_OMR(omr) do { int x = 0; write((omr)->msg_pipe[1], &x, sizeof(x)); } while (0) +#define CLEAR_OMR(omr) do { int x = 0; read((omr)->msg_pipe[0], &x, sizeof(x)); } while (0) +#define RESET_OMR(omr) do { } while (0) +#define WAIT_OMR(omr, sleep) \ + do { struct pollfd pfd; pfd.fd = (omr)->msg_pipe[0]; \ + pfd.events = POLLIN; poll(&pfd, 1, sleep); } while (0) +#else +#define INIT_OMR(omr) do { omr->msg_event = CreateEventW(NULL, FALSE, FALSE, NULL); } while (0) +#define CLOSE_OMR(omr) do { CloseHandle(omr->msg_event); } while (0) +#define SIGNAL_OMR(omr) do { SetEvent((omr)->msg_event); } while (0) +#define CLEAR_OMR(omr) do { } while (0) +#define RESET_OMR(omr) do { ResetEvent((omr)->msg_event); } while (0) +#define WAIT_OMR(omr, sleep) \ + do { WaitForSingleObject((omr)->msg_event, sleep); } while (0) +#endif + +#define PULSE_RING_BUFFER_INCREMENT 64 + +/****************************************************************** + * PULSE_InitRingMessage + * + * Initialize the ring of messages for passing between driver's caller + * and playback/record thread + */ +int PULSE_InitRingMessage(PULSE_MSG_RING* omr) +{ + omr->msg_toget = 0; + omr->msg_tosave = 0; + INIT_OMR(omr); + omr->ring_buffer_size = PULSE_RING_BUFFER_INCREMENT; + omr->messages = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,omr->ring_buffer_size * sizeof(PULSE_MSG)); + + InitializeCriticalSection(&omr->msg_crst); + omr->msg_crst.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": PULSE_MSG_RING.msg_crst"); + return 0; +} + +/****************************************************************** + * PULSE_DestroyRingMessage + * + */ +int PULSE_DestroyRingMessage(PULSE_MSG_RING* omr) +{ + CLOSE_OMR(omr); + HeapFree(GetProcessHeap(),0,omr->messages); + omr->messages = NULL; + omr->ring_buffer_size = PULSE_RING_BUFFER_INCREMENT; + omr->msg_crst.DebugInfo->Spare[0] = 0; + DeleteCriticalSection(&omr->msg_crst); + return 0; +} +/****************************************************************** + * PULSE_ResetRingMessage + * + */ +void PULSE_ResetRingMessage(PULSE_MSG_RING* omr) +{ + RESET_OMR(omr); +} + +/****************************************************************** + * PULSE_WaitRingMessage + * + */ +void PULSE_WaitRingMessage(PULSE_MSG_RING* omr, DWORD sleep) +{ + WAIT_OMR(omr, sleep); +} + +/****************************************************************** + * PULSE_AddRingMessage + * + * Inserts a new message into the ring (should be called from DriverProc derived routines) + */ +int PULSE_AddRingMessage(PULSE_MSG_RING* omr, enum win_wm_message msg, DWORD param, BOOL wait) +{ + HANDLE hEvent = INVALID_HANDLE_VALUE; + + EnterCriticalSection(&omr->msg_crst); + if ((omr->msg_toget == ((omr->msg_tosave + 1) % omr->ring_buffer_size))) + { + int old_ring_buffer_size = omr->ring_buffer_size; + omr->ring_buffer_size += PULSE_RING_BUFFER_INCREMENT; + omr->messages = HeapReAlloc(GetProcessHeap(),0,omr->messages, omr->ring_buffer_size * sizeof(PULSE_MSG)); + /* Now we need to rearrange the ring buffer so that the new + buffers just allocated are in between omr->msg_tosave and + omr->msg_toget. + */ + if (omr->msg_tosave < omr->msg_toget) + { + memmove(&(omr->messages[omr->msg_toget + PULSE_RING_BUFFER_INCREMENT]), + &(omr->messages[omr->msg_toget]), + sizeof(PULSE_MSG)*(old_ring_buffer_size - omr->msg_toget) + ); + omr->msg_toget += PULSE_RING_BUFFER_INCREMENT; + } + } + if (wait) + { + hEvent = CreateEventW(NULL, FALSE, FALSE, NULL); + if (hEvent == INVALID_HANDLE_VALUE) + { + ERR("can't create event !?\n"); + LeaveCriticalSection(&omr->msg_crst); + return 0; + } + if (omr->msg_toget != omr->msg_tosave && omr->messages[omr->msg_toget].msg != WINE_WM_HEADER) + FIXME("two fast messages in the queue!!!!\n"); /* toget = %d(%s), tosave=%d(%s)\n", + omr->msg_toget,ALSA_getCmdString(omr->messages[omr->msg_toget].msg), + omr->msg_tosave,ALSA_getCmdString(omr->messages[omr->msg_tosave].msg)); */ + + /* fast messages have to be added at the start of the queue */ + omr->msg_toget = (omr->msg_toget + omr->ring_buffer_size - 1) % omr->ring_buffer_size; + + omr->messages[omr->msg_toget].msg = msg; + omr->messages[omr->msg_toget].param = param; + omr->messages[omr->msg_toget].hEvent = hEvent; + } + else + { + omr->messages[omr->msg_tosave].msg = msg; + omr->messages[omr->msg_tosave].param = param; + omr->messages[omr->msg_tosave].hEvent = INVALID_HANDLE_VALUE; + omr->msg_tosave = (omr->msg_tosave + 1) % omr->ring_buffer_size; + } + LeaveCriticalSection(&omr->msg_crst); + /* signal a new message */ + SIGNAL_OMR(omr); + if (wait) + { + /* wait for playback/record thread to have processed the message */ + WaitForSingleObject(hEvent, INFINITE); + CloseHandle(hEvent); + } + return 1; +} + +/****************************************************************** + * PULSE_RetrieveRingMessage + * + * Get a message from the ring. Should be called by the playback/record thread. + */ +int PULSE_RetrieveRingMessage(PULSE_MSG_RING* omr, + enum win_wm_message *msg, DWORD *param, HANDLE *hEvent) +{ + EnterCriticalSection(&omr->msg_crst); + + if (omr->msg_toget == omr->msg_tosave) /* buffer empty ? */ + { + LeaveCriticalSection(&omr->msg_crst); + return 0; + } + + *msg = omr->messages[omr->msg_toget].msg; + omr->messages[omr->msg_toget].msg = 0; + *param = omr->messages[omr->msg_toget].param; + *hEvent = omr->messages[omr->msg_toget].hEvent; + omr->msg_toget = (omr->msg_toget + 1) % omr->ring_buffer_size; + CLEAR_OMR(omr); + LeaveCriticalSection(&omr->msg_crst); + return 1; +} + +/************************************************************************** + * Utility Functions + */ + +/****************************************************************** + * PULSE_free_wavedevs [internal] + * + * Free and deallocated all the wavedevs in the array of size allocated + */ +static void PULSE_free_wavedevs(WINE_WAVEDEV *wd, DWORD *allocated) { + DWORD y, x = *allocated; + WINE_WAVEDEV *current_device; + WINE_WAVEINST *current_instance; + + TRACE("%i\n", *allocated); + + for (; x>0; ) { + current_device = &wd[--x]; + + pa_xfree(current_device->device_name); + + for (y = 0; y < PULSE_MAX_STREAM_INSTANCES; y++) { + if ((current_instance = current_device->instance[y])) { + if (current_instance->hThread != INVALID_HANDLE_VALUE && current_instance->msgRing.messages) { + PULSE_AddRingMessage(¤t_instance->msgRing, WINE_WM_CLOSING, 1, TRUE); + if (current_instance->hThread != INVALID_HANDLE_VALUE) { + /* Overkill? */ + TerminateThread(current_instance->hThread, 1); + current_instance->hThread = INVALID_HANDLE_VALUE; + } + if (current_instance->stream) + pa_stream_unref(current_instance->stream); + PULSE_DestroyRingMessage(¤t_instance->msgRing); + current_device->instance[current_instance->instance_ref] = NULL; + if (current_instance->buffer_attr) + pa_xfree(current_instance->buffer_attr); + HeapFree(GetProcessHeap(), 0, current_instance); + } + } + } + } + + HeapFree(GetProcessHeap(), 0, wd); + *allocated = 0; +} + +/****************************************************************** + * PULSE_wait_for_operation + * + * Waits for pa operations to complete, ensures success and dereferences the + * operations. + */ +void PULSE_wait_for_operation(pa_operation *o, WINE_WAVEINST *wd) { + assert(o && wd); + + TRACE("Waiting..."); + + for (;;) { + + if (!wd->stream || + !PULSE_context || + pa_context_get_state(PULSE_context) != PA_CONTEXT_READY || + pa_stream_get_state(wd->stream) != PA_STREAM_READY) { + wd->state = WINE_WS_FAILED; + if (wd->hThread != INVALID_HANDLE_VALUE && wd->msgRing.messages) + PULSE_AddRingMessage(&wd->msgRing, WINE_WM_CLOSING, 1, FALSE); + break; + } + + if (pa_operation_get_state(o) != PA_OPERATION_RUNNING) + break; + + pa_threaded_mainloop_wait(PULSE_ml); + } + TRACE(" done\n"); + pa_operation_unref(o); +} + +/************************************************************************** + * Common Callbacks + */ + +/****************************************************************** + * PULSE_stream_state_callback + * + * Called by pulse whenever the state of the stream changes. + */ +void PULSE_stream_state_callback(pa_stream *s, void *userdata) { + WINE_WAVEINST *wd = (WINE_WAVEINST*)userdata; + assert(s && wd); + + switch (pa_stream_get_state(s)) { + + case PA_STREAM_READY: + TRACE("Stream ready\n"); + break; + case PA_STREAM_TERMINATED: + TRACE("Stream terminated\n"); + break; + case PA_STREAM_FAILED: + WARN("Stream failed!\n"); + wd->state = WINE_WS_FAILED; + if (wd->hThread != INVALID_HANDLE_VALUE && wd->msgRing.messages) + PULSE_AddRingMessage(&wd->msgRing, WINE_WM_CLOSING, 1, FALSE); + break; + case PA_STREAM_UNCONNECTED: + case PA_STREAM_CREATING: + return; + } + pa_threaded_mainloop_signal(PULSE_ml, 0); +} + +/************************************************************************** + * PULSE_stream_sucess_callback + */ +void PULSE_stream_success_callback(pa_stream *s, int success, void *userdata) { + if (!success) + WARN("Stream operation failed: %s\n", pa_strerror(pa_context_errno(PULSE_context))); + + pa_threaded_mainloop_signal(PULSE_ml, 0); +} + +/************************************************************************** + * PULSE_context_success_callback + */ +void PULSE_context_success_callback(pa_context *c, int success, void *userdata) { + if (!success) + WARN("Context operation failed: %s\n", pa_strerror(pa_context_errno(c))); + + pa_threaded_mainloop_signal(PULSE_ml, 0); +} + +/************************************************************************** + * Connection management and sink / source management. + */ + +/************************************************************************** + * PULSE_context_state_callback [internal] + */ +static void PULSE_context_state_callback(pa_context *c, void *userdata) { + assert(c); + + switch (pa_context_get_state(c)) { + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + + case PA_CONTEXT_READY: + case PA_CONTEXT_TERMINATED: + pa_threaded_mainloop_signal(PULSE_ml, 0); + break; + + case PA_CONTEXT_FAILED: + default: + ERR("Conneciton failure: %s\n", pa_strerror(pa_context_errno(c))); + + if (PULSE_context) { + pa_context_disconnect(PULSE_context); + } + + PULSE_free_wavedevs(WOutDev, &PULSE_WodNumDevs); + PULSE_free_wavedevs(WInDev, &PULSE_WidNumDevs); + + pa_threaded_mainloop_signal(PULSE_ml, 0); + } +} + +/************************************************************************** + * PULSE_add_input_device [internal] + * + * Creates or adds a device to WInDev based on the pa_source_info, or if + * pa_source_info is null, a default. + */ +static void PULSE_add_input_device(pa_source_info *i, DWORD *allocated) { + WINE_WAVEDEV *wwi; + const char *description; + int x; + + if (WInDev) + wwi = HeapReAlloc(GetProcessHeap(), 0, WInDev, sizeof(WINE_WAVEDEV) * ((*allocated)+1)); + else + wwi = HeapAlloc(GetProcessHeap(), 0, sizeof(WINE_WAVEDEV)); + + if (!wwi) + return; + + WInDev = wwi; + wwi = &WInDev[(*allocated)++]; + + if (i) { + description = i->description; + wwi->device_name = pa_xstrdup(i->name); + strcpy(wwi->interface_name, "winepulse: "); + memcpy(wwi->interface_name + strlen(wwi->interface_name), + i->name, min(strlen(i->name), + sizeof(wwi->interface_name) - strlen("winepulse: "))); + } else { + description = pa_xstrdup("PulseAudio Server Default"); + wwi->device_name = NULL; + strcpy(wwi->interface_name, "winepulse: default"); + } + + memset(wwi, 0, sizeof(WINE_WAVEDEV)); + memset(&(wwi->caps.in), 0, sizeof(wwi->caps.in)); + MultiByteToWideChar(CP_ACP, 0, description, -1, wwi->caps.in.szPname, sizeof(wwi->caps.in.szPname)/sizeof(WCHAR)); + wwi->caps.in.szPname[sizeof(wwi->caps.in.szPname)/sizeof(WCHAR) - 1] = '\0'; + wwi->caps.in.wMid = MM_CREATIVE; + wwi->caps.in.wPid = MM_CREATIVE_SBP16_WAVEOUT; + wwi->caps.in.vDriverVersion = 0x0100; + wwi->caps.in.wChannels = 2; + wwi->caps.in.dwFormats |= + WAVE_FORMAT_1M08 | /* Mono 11025Hz 8-bit */ + WAVE_FORMAT_1M16 | /* Mono 11025Hz 16-bit */ + WAVE_FORMAT_1S08 | /* Stereo 11025Hz 8-bit */ + WAVE_FORMAT_1S16 | /* Stereo 11025Hz 16-bit */ + WAVE_FORMAT_2M08 | /* Mono 22050Hz 8-bit */ + WAVE_FORMAT_2M16 | /* Mono 22050Hz 16-bit */ + WAVE_FORMAT_2S08 | /* Stereo 22050Hz 8-bit */ + WAVE_FORMAT_2S16 | /* Stereo 22050Hz 16-bit */ + WAVE_FORMAT_4M08 | /* Mono 44100Hz 8-bit */ + WAVE_FORMAT_4M16 | /* Mono 44100Hz 16-bit */ + WAVE_FORMAT_4S08 | /* Stereo 44100Hz 8-bit */ + WAVE_FORMAT_4S16 | /* Stereo 44100Hz 16-bit */ + WAVE_FORMAT_48M08 | /* Mono 48000Hz 8-bit */ + WAVE_FORMAT_48S08 | /* Stereo 48000Hz 8-bit */ + WAVE_FORMAT_48M16 | /* Mono 48000Hz 16-bit */ + WAVE_FORMAT_48S16 | /* Stereo 48000Hz 16-bit */ + WAVE_FORMAT_96M08 | /* Mono 96000Hz 8-bit */ + WAVE_FORMAT_96S08 | /* Stereo 96000Hz 8-bit */ + WAVE_FORMAT_96M16 | /* Mono 96000Hz 16-bit */ + WAVE_FORMAT_96S16 ; /* Stereo 96000Hz 16-bit */ + + /* NULL out the instance pointers */ + for (x = 0; x < PULSE_MAX_STREAM_INSTANCES; x++) wwi->instance[x] = NULL; +} + +/************************************************************************** + * PULSE_add_output_device [internal] + * + * Creates or adds a device to WOutDev based on the pa_sinl_info, or if + * pa_sink_info is null, a default. + */ +static void PULSE_add_output_device(pa_sink_info *i, DWORD *allocated) { + WINE_WAVEDEV *wwo; + const char *description; + int x; + + if (WOutDev) + wwo = HeapReAlloc(GetProcessHeap(), 0, WOutDev, sizeof(WINE_WAVEDEV) * ((*allocated)+1)); + else + wwo = HeapAlloc(GetProcessHeap(), 0, sizeof(WINE_WAVEDEV)); + + if (!wwo) + return; + + WOutDev = wwo; + wwo = &WOutDev[(*allocated)++]; + + if (i) { + description = i->description; + wwo->device_name = pa_xstrdup(i->name); + strcpy(wwo->interface_name, "winepulse: "); + memcpy(wwo->interface_name + strlen(wwo->interface_name), + wwo->device_name, min(strlen(wwo->device_name), + sizeof(wwo->interface_name) - strlen("winepulse: "))); + } else { + description = pa_xstrdup("PulseAudio Server Default"); + wwo->device_name = NULL; + strcpy(wwo->interface_name, "winepulse: default"); + } + + memset(wwo, 0, sizeof(WINE_WAVEDEV)); + memset(&(wwo->caps.out), 0, sizeof(wwo->caps.out)); + MultiByteToWideChar(CP_ACP, 0, description, -1, wwo->caps.out.szPname, sizeof(wwo->caps.out.szPname)/sizeof(WCHAR)); + wwo->caps.out.szPname[sizeof(wwo->caps.out.szPname)/sizeof(WCHAR) - 1] = '\0'; + wwo->caps.out.wMid = MM_CREATIVE; + wwo->caps.out.wPid = MM_CREATIVE_SBP16_WAVEOUT; + wwo->caps.out.vDriverVersion = 0x0100; + wwo->caps.out.dwSupport |= WAVECAPS_VOLUME | WAVECAPS_LRVOLUME; + wwo->caps.out.wChannels = 2; + wwo->caps.out.dwFormats |= + WAVE_FORMAT_1M08 | /* Mono 11025Hz 8-bit */ + WAVE_FORMAT_1M16 | /* Mono 11025Hz 16-bit */ + WAVE_FORMAT_1S08 | /* Stereo 11025Hz 8-bit */ + WAVE_FORMAT_1S16 | /* Stereo 11025Hz 16-bit */ + WAVE_FORMAT_2M08 | /* Mono 22050Hz 8-bit */ + WAVE_FORMAT_2M16 | /* Mono 22050Hz 16-bit */ + WAVE_FORMAT_2S08 | /* Stereo 22050Hz 8-bit */ + WAVE_FORMAT_2S16 | /* Stereo 22050Hz 16-bit */ + WAVE_FORMAT_4M08 | /* Mono 44100Hz 8-bit */ + WAVE_FORMAT_4M16 | /* Mono 44100Hz 16-bit */ + WAVE_FORMAT_4S08 | /* Stereo 44100Hz 8-bit */ + WAVE_FORMAT_4S16 | /* Stereo 44100Hz 16-bit */ + WAVE_FORMAT_48M08 | /* Mono 48000Hz 8-bit */ + WAVE_FORMAT_48S08 | /* Stereo 48000Hz 8-bit */ + WAVE_FORMAT_48M16 | /* Mono 48000Hz 16-bit */ + WAVE_FORMAT_48S16 | /* Stereo 48000Hz 16-bit */ + WAVE_FORMAT_96M08 | /* Mono 96000Hz 8-bit */ + WAVE_FORMAT_96S08 | /* Stereo 96000Hz 8-bit */ + WAVE_FORMAT_96M16 | /* Mono 96000HZ 16-bit */ + WAVE_FORMAT_96S16 ; /* Stereo 96000Hz 16-bit */ + + wwo->left_vol = PA_VOLUME_NORM; + wwo->right_vol = PA_VOLUME_NORM; + + /* NULL out the instance pointers */ + for (x = 0; x < PULSE_MAX_STREAM_INSTANCES; x++) wwo->instance[x] = NULL; +} + +/************************************************************************** + * PULSE_source_info_callback [internal] + * + * Calls PULSE_add_input_device to add the source to the list of devices + */ +static void PULSE_source_info_callback(pa_context *c, pa_source_info *i, int eol, void *userdata) { + assert(c); + if (!eol && i) + if (i->monitor_of_sink == PA_INVALID_INDEX) /* For now only add non-montior sources */ + PULSE_add_input_device(i, (DWORD*)userdata); + + pa_threaded_mainloop_signal(PULSE_ml, 0); +} + +/************************************************************************** + * PULSE_sink_info_callback [internal] + * + * Calls PULSE_add_output_device to add the sink to the list of devices + */ +static void PULSE_sink_info_callback(pa_context *c, pa_sink_info *i, int eol, void *userdata) { + assert(c); + if (!eol && i) + PULSE_add_output_device(i, (DWORD*)userdata); + + pa_threaded_mainloop_signal(PULSE_ml, 0); +} + +/************************************************************************** + * PULSE_WaveInit [internal] + * + * Connects to the pulseaudio server, tries to discover sinks and sources and + * allocates the WaveIn/WaveOut devices. + */ +LONG PULSE_WaveInit(void) { + pa_operation *o = NULL; + DWORD allocated; + + WOutDev = NULL; + WInDev = NULL; + PULSE_WodNumDevs = 0; + PULSE_WidNumDevs = 0; + PULSE_context = NULL; + PULSE_ml = NULL; + + if (!(PULSE_ml = pa_threaded_mainloop_new())) { + WARN("Failed to create mainloop object."); + return -1; + } + + pa_threaded_mainloop_start(PULSE_ml); + + /* FIXME: better name? */ + PULSE_context = pa_context_new(pa_threaded_mainloop_get_api(PULSE_ml), "Wine Application"); + assert(PULSE_context); + + pa_context_set_state_callback(PULSE_context, PULSE_context_state_callback, NULL); + + if (pa_context_get_state(PULSE_context) != PA_CONTEXT_UNCONNECTED) + return 0; + + pa_threaded_mainloop_lock(PULSE_ml); + + TRACE("Attempting to connect to pulseaudio server.\n"); + if (pa_context_connect(PULSE_context, NULL, 0, NULL) < 0) { + WARN("failed to connect context object %s\n", pa_strerror(pa_context_errno(PULSE_context))); + return -1; + } + + /* Wait for connection */ + for (;;) { + pa_context_state_t state = pa_context_get_state(PULSE_context); + + if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + ERR("Failed to connect to pulseaudio server.\n"); + pa_context_unref(PULSE_context); + pa_threaded_mainloop_unlock(PULSE_ml); + pa_threaded_mainloop_stop(PULSE_ml); + pa_threaded_mainloop_free(PULSE_ml); + return -1; + } + + if (state == PA_CONTEXT_READY) { + TRACE("Connection succeeded!\n"); + break; + } + + pa_threaded_mainloop_wait(PULSE_ml); + } + + /* Ask for all the sinks and create objects in the WOutDev array */ + allocated=0; + /* add a fake output (server default sink) */ + PULSE_add_output_device(NULL, &allocated); + o = pa_context_get_sink_info_list(PULSE_context, (pa_sink_info_cb_t)PULSE_sink_info_callback, &allocated); + assert(o); + while (pa_operation_get_state(o) != PA_OPERATION_DONE) + pa_threaded_mainloop_wait(PULSE_ml); + pa_operation_unref(o); + pa_threaded_mainloop_unlock(PULSE_ml); + TRACE("Allocated %i output device(s).\n",allocated); + PULSE_WodNumDevs=allocated; + if (PULSE_WodNumDevs == 1) /* Only the fake sink exists */ + PULSE_free_wavedevs(WOutDev, &PULSE_WodNumDevs); + + /* Repeate for all the sources */ + allocated=0; + /* add a fake input (server default source) */ + PULSE_add_input_device(NULL, &allocated); + pa_threaded_mainloop_lock(PULSE_ml); + o = pa_context_get_source_info_list(PULSE_context, (pa_source_info_cb_t)PULSE_source_info_callback, &allocated); + assert(o); + while (pa_operation_get_state(o) != PA_OPERATION_DONE) + pa_threaded_mainloop_wait(PULSE_ml); + pa_operation_unref(o); + pa_threaded_mainloop_unlock(PULSE_ml); + TRACE("Allocated %i input device(s).\n", allocated); + PULSE_WidNumDevs=allocated; + if (PULSE_WidNumDevs == 1) /* Only the fake source exists */ + PULSE_free_wavedevs(WInDev, &PULSE_WidNumDevs); + + return 1; +} + +/************************************************************************** + * PULSE_WaveClose [internal] + * + * Disconnect from the server, deallocated the WaveIn/WaveOut devices, stop and + * free the mainloop. + */ + +LONG PULSE_WaveClose(void) { + pa_threaded_mainloop_lock(PULSE_ml); + if (PULSE_context) { + pa_context_disconnect(PULSE_context); + pa_context_unref(PULSE_context); + PULSE_context = NULL; + } + + PULSE_free_wavedevs(WOutDev, &PULSE_WodNumDevs); + PULSE_free_wavedevs(WInDev, &PULSE_WidNumDevs); + + pa_threaded_mainloop_unlock(PULSE_ml); + pa_threaded_mainloop_stop(PULSE_ml); + pa_threaded_mainloop_free(PULSE_ml); + + return 1; +} + +#endif /* HAVE_PULSEAUDIO */ + +/************************************************************************** + * DriverProc (WINEPULSE.@) + */ +LRESULT CALLBACK PULSE_DriverProc(DWORD_PTR dwDevID, HDRVR hDriv, UINT wMsg, + LPARAM dwParam1, LPARAM dwParam2) { + + switch(wMsg) { +#ifdef HAVE_PULSEAUDIO + case DRV_LOAD: PULSE_WaveInit(); + return 1; + case DRV_FREE: return PULSE_WaveClose(); + case DRV_OPEN: return 1; + case DRV_CLOSE: return 1; + case DRV_ENABLE: return 1; + case DRV_DISABLE: return 1; + case DRV_QUERYCONFIGURE: return 1; + case DRV_CONFIGURE: MessageBoxA(0, "PulseAudio MultiMedia Driver !", "PulseAudio Driver", MB_OK); return 1; + case DRV_INSTALL: return DRVCNF_RESTART; + case DRV_REMOVE: return DRVCNF_RESTART; +#endif + default: + return DefDriverProc(dwDevID, hDriv, wMsg, dwParam1, dwParam2); + } +} diff --git a/dlls/winepulse.drv/waveout.c b/dlls/winepulse.drv/waveout.c new file mode 100644 index 0000000..fe7543f --- /dev/null +++ b/dlls/winepulse.drv/waveout.c @@ -0,0 +1,180 @@ +/* + * Wine Driver for PulseAudio - WaveOut Functionality + * http://pulseaudio.org/ + * + * Copyright 2002 Eric Pouech + * 2002 Marco Pietrobono + * 2003 Christian Costa + * 2006-2007 Maarten Lankhorst + * 2008 Arthur Taylor (PulseAudio version) + * + * 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 + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "windef.h" +#include "winbase.h" +#include "wingdi.h" +#include "winuser.h" +#include "winnls.h" +#include "winerror.h" +#include "mmddk.h" +#include "mmreg.h" +#include "ks.h" +#include "ksguid.h" +#include "ksmedia.h" +#include "dsound.h" +#include "dsdriver.h" + +#include + +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(wave); + +#if HAVE_PULSEAUDIO + +/* state diagram for waveOut writing: + * + * +---------+-------------+---------------+---------------------------------+ + * | state | function | event | new state | + * +---------+-------------+---------------+---------------------------------+ + * | | open() | | STOPPED | + * | PAUSED | write() | | PAUSED | + * | STOPPED | write() | | PLAYING | + * | PLAYING | write() | HEADER | PLAYING | + * | (other) | write() | | | + * | (any) | pause() | PAUSING | PAUSED | + * | PAUSED | restart() | RESTARTING | PLAYING (if no thrd => STOPPED) | + * | PAUSED | reset() | RESETTING | PAUSED | + * | (other) | reset() | RESETTING | STOPPED | + * | (any) | close() | CLOSING | CLOSED | + * +---------+-------------+---------------+---------------------------------+ + */ + +/************************************************************************** + * wodGetDevCaps [internal] + */ +static DWORD wodGetDevCaps(DWORD wDevID, LPWAVEOUTCAPSW lpCaps, DWORD dwSize) { + TRACE("(%u, %p, %u);\n", wDevID, lpCaps, dwSize); + + if (lpCaps == NULL) return MMSYSERR_NOTENABLED; + + if (wDevID >= PULSE_WodNumDevs) { + TRACE("Asked for device %d, but only %d known!\n", wDevID, PULSE_WodNumDevs); + return MMSYSERR_INVALHANDLE; + } + + memcpy(lpCaps, &(WOutDev[wDevID].caps.out), min(dwSize, sizeof(*lpCaps))); + return MMSYSERR_NOERROR; +} + +/************************************************************************** + * wodDevInterfaceSize [internal] + */ +static DWORD wodDevInterfaceSize(UINT wDevID, LPDWORD dwParam1) { + + *dwParam1 = MultiByteToWideChar(CP_ACP, 0, WOutDev[wDevID].interface_name, -1, NULL, 0) * sizeof(WCHAR); + return MMSYSERR_NOERROR; +} + +/************************************************************************** + * wodDevInterface [internal] + */ +static DWORD wodDevInterface(UINT wDevID, PWCHAR dwParam1, DWORD dwParam2) { + if (dwParam2 >= MultiByteToWideChar(CP_ACP, 0, WOutDev[wDevID].interface_name, -1, + NULL, 0 ) * sizeof(WCHAR)) + { + MultiByteToWideChar(CP_ACP, 0, WOutDev[wDevID].interface_name, -1, + dwParam1, dwParam2 / sizeof(WCHAR)); + return MMSYSERR_NOERROR; + } + return MMSYSERR_INVALPARAM; +} + +/*======================================================================* + * Low level DSOUND implementation * + *======================================================================*/ +static DWORD wodDsCreate(UINT wDevID, PIDSDRIVER* drv) { + /* Is this possible ?*/ + return MMSYSERR_NOTSUPPORTED; +} + +static DWORD wodDsDesc(UINT wDevID, PDSDRIVERDESC desc) { + memset(desc, 0, sizeof(*desc)); + strcpy(desc->szDesc, "Wine PulseAudio DirectSound Driver"); + strcpy(desc->szDrvname, "winepulse.drv"); + return MMSYSERR_NOERROR; +} +/************************************************************************** + * wodMessage (WINEPULSE.@) + */ +DWORD WINAPI PULSE_wodMessage(UINT wDevID, UINT wMsg, DWORD dwUser, + DWORD dwParam1, DWORD dwParam2) { + + switch (wMsg) { + case DRVM_INIT: + case DRVM_EXIT: + case DRVM_ENABLE: + case DRVM_DISABLE: + /* FIXME: Pretend this is supported */ + return 0; + case WODM_OPEN: return MMSYSERR_NOTSUPPORTED; + case WODM_CLOSE: return MMSYSERR_NOTSUPPORTED; + case WODM_WRITE: return MMSYSERR_NOTSUPPORTED; + case WODM_PAUSE: return MMSYSERR_NOTSUPPORTED; + case WODM_GETPOS: return MMSYSERR_NOTSUPPORTED; + case WODM_BREAKLOOP: return MMSYSERR_NOTSUPPORTED; + case WODM_PREPARE: return MMSYSERR_NOTSUPPORTED; + case WODM_UNPREPARE: return MMSYSERR_NOTSUPPORTED; + case WODM_GETDEVCAPS: return wodGetDevCaps (wDevID, (LPWAVEOUTCAPSW)dwParam1, dwParam2); + case WODM_GETNUMDEVS: return PULSE_WodNumDevs; + case WODM_GETPITCH: return MMSYSERR_NOTSUPPORTED; + case WODM_SETPITCH: return MMSYSERR_NOTSUPPORTED; + case WODM_GETPLAYBACKRATE: return MMSYSERR_NOTSUPPORTED; /* support if theoretically possible */ + case WODM_SETPLAYBACKRATE: return MMSYSERR_NOTSUPPORTED; /* since pulseaudio 0.9.8 */ + case WODM_GETVOLUME: return MMSYSERR_NOTSUPPORTED; + case WODM_SETVOLUME: return MMSYSERR_NOTSUPPORTED; + case WODM_RESTART: return MMSYSERR_NOTSUPPORTED; + case WODM_RESET: return MMSYSERR_NOTSUPPORTED; + case DRV_QUERYDEVICEINTERFACESIZE: return wodDevInterfaceSize (wDevID, (LPDWORD)dwParam1); + case DRV_QUERYDEVICEINTERFACE: return wodDevInterface (wDevID, (PWCHAR)dwParam1, dwParam2); + case DRV_QUERYDSOUNDIFACE: return wodDsCreate (wDevID, (PIDSDRIVER*)dwParam1); + case DRV_QUERYDSOUNDDESC: return wodDsDesc (wDevID, (PDSDRIVERDESC)dwParam1); + default: + FIXME("unknown message %d!\n", wMsg); + } + return MMSYSERR_NOTSUPPORTED; +} + +#else /* !HAVE_PULSEAUDIO */ + +/************************************************************************** + * wodMessage (WINEPULSE.@) + */ +DWORD WINAPI PULSE_wodMessage(WORD wDevID, WORD wMsg, DWORD dwUser, + DWORD dwParam1, DWORD dwParam2) { + FIXME("(%u, %04X, %08X, %08X, %08X):stub\n", wDevID, wMsg, dwUser, + dwParam1, dwParam2); + return MMSYSERR_NOTENABLED; +} + +#endif /* HAVE_PULSEAUDIO */ diff --git a/dlls/winepulse.drv/winepulse.drv.spec b/dlls/winepulse.drv/winepulse.drv.spec new file mode 100644 index 0000000..1707969 --- /dev/null +++ b/dlls/winepulse.drv/winepulse.drv.spec @@ -0,0 +1,2 @@ +@ stdcall -private DriverProc(long long long long long long) PULSE_DriverProc +@ stdcall -private wodMessage(long long long long long long) PULSE_wodMessage diff --git a/dlls/winepulse.drv/winepulse.h b/dlls/winepulse.drv/winepulse.h new file mode 100644 index 0000000..277221a --- /dev/null +++ b/dlls/winepulse.drv/winepulse.h @@ -0,0 +1,169 @@ +/* Definitions for PulseAudio Wine Driver + * + * Copyright 2008 Arthur Taylor + * + * 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 + */ + +#ifndef __WINE_CONFIG_H +# error You must include config.h to use this header +#endif + +#if defined(HAVE_PULSEAUDIO) && !defined(__WINEPULSE_H) +#define __WINEPULSE_H + +#include "mmreg.h" +#include "dsound.h" +#include "dsdriver.h" + +#include "ks.h" +#include "ksmedia.h" +#include "ksguid.h" + +#include + +/* state diagram for waveOut writing: + * + * +---------+-------------+---------------+---------------------------------+ + * | state | function | event | new state | + * +---------+-------------+---------------+---------------------------------+ + * | | open() | | STOPPED | + * | PAUSED | write() | | PAUSED | + * | STOPPED | write() | | PLAYING | + * | PLAYING | write() | HEADER | PLAYING | + * | (other) | write() | | | + * | (any) | pause() | PAUSING | PAUSED | + * | PAUSED | restart() | RESTARTING | PLAYING (if no thrd => STOPPED) | + * | (any) | reset() | RESETTING | STOPPED | + * | (any) | close() | CLOSING | CLOSED | + * +---------+-------------+---------------+---------------------------------+ + */ + +#undef PULSE_VERBOSE + +/* states of the playing device */ +#define WINE_WS_PLAYING 1 +#define WINE_WS_PAUSED 2 +#define WINE_WS_STOPPED 3 +#define WINE_WS_CLOSED 4 +#define WINE_WS_FAILED 5 + +#define PULSE_MAX_STREAM_INSTANCES 16 /* Sixteen streams per device should be good enough? */ + +/* events to be send to device */ +enum win_wm_message { + WINE_WM_PAUSING = WM_USER + 1, WINE_WM_RESTARTING, WINE_WM_RESETTING, WINE_WM_HEADER, + WINE_WM_BREAKLOOP, WINE_WM_CLOSING, WINE_WM_STARTING, WINE_WM_STOPPING, WINE_WM_XRUN, WINE_WM_FEED +}; + +typedef struct { + enum win_wm_message msg; /* message identifier */ + DWORD param; /* parameter for this message */ + HANDLE hEvent; /* if message is synchronous, handle of event for synchro */ +} PULSE_MSG; + +/* implement an in-process message ring for better performance + * (compared to passing thru the server) + * this ring will be used by the input (resp output) record (resp playback) routine + */ +typedef struct { + PULSE_MSG * messages; + int ring_buffer_size; + int msg_tosave; + int msg_toget; +/* Either pipe or event is used, but that is defined in pulse.c, + * since this is a global header we define both here */ + int msg_pipe[2]; + HANDLE msg_event; + CRITICAL_SECTION msg_crst; +} PULSE_MSG_RING; + +/* Per-playback/record instance */ +typedef struct { + int instance_ref; /* The array index of WINE_WAVEDEV->instance[] that this is */ + volatile int state; /* one of the WINE_WS_ manifest constants */ + WAVEOPENDESC waveDesc; + WORD wFlags; + pa_stream *stream; /* The PulseAudio stream */ + const pa_timing_info *timing_info; + pa_buffer_attr *buffer_attr; + pa_sample_spec sample_spec; /* Sample spec of this stream / device */ + pa_cvolume volume; + + /* WavaHdr information */ + LPWAVEHDR lpQueuePtr; /* start of queued WAVEHDRs (waiting to be notified) */ + LPWAVEHDR lpPlayPtr; /* start of not yet fully played buffers */ + DWORD dwPartialOffset; /* Offset of not yet written bytes in lpPlayPtr */ + LPWAVEHDR lpLoopPtr; /* pointer of first buffer in loop, if any */ + DWORD dwLoops; /* private copy of loop counter */ + + /* Virtual stream positioning information */ + DWORD last_reset; /* When the last reset occured, as pa stream time doesn't reset */ + BOOL is_buffering; /* !is_playing */ + struct timeval last_header; /* When the last wavehdr was received, only updated when is_buffering is true */ + BOOL is_releasing; /* Whether we are releasing wavehdrs, may not always be the inverse of is_buffering */ + struct timeval started_releasing; /* When wavehdr releasing started, for comparison to queued written wavehdrs */ + DWORD releasing_offset; /* How much audio has been released prior when releasing started in this instance */ + + /* Thread communication and synchronization stuff */ + HANDLE hStartUpEvent; + HANDLE hThread; + DWORD dwThreadID; + PULSE_MSG_RING msgRing; +} WINE_WAVEINST; + +/* Per-playback/record device */ +typedef struct { + char *device_name; /* Name of the device used as an identifer on the server */ + char interface_name[MAXPNAMELEN * 2]; + + pa_volume_t left_vol; /* Volume is per device and always stereo */ + pa_volume_t right_vol; + + union { + WAVEOUTCAPSW out; + WAVEINCAPSW in; + } caps; + + WINE_WAVEINST *instance[PULSE_MAX_STREAM_INSTANCES]; +} WINE_WAVEDEV; + +/* We establish one context per instance, so make it global to the lib */ +pa_context *PULSE_context; /* Connection Context */ +pa_threaded_mainloop *PULSE_ml; /* PA Runtime information */ + +/* WaveIn / WaveOut devices */ +WINE_WAVEDEV *WOutDev; +WINE_WAVEDEV *WInDev; +DWORD PULSE_WodNumDevs; +DWORD PULSE_WidNumDevs; + +/* pulse.c */ +void PULSE_set_buffer_attr_callback(pa_stream *stream, int success, void *userdata); +void PULSE_wait_for_operation(pa_operation *o, WINE_WAVEINST *ww); +void PULSE_stream_success_callback(pa_stream *s, int success, void *userdata); +void PULSE_stream_state_callback(pa_stream *s, void *userdata); +void PULSE_context_success_callback(pa_context *c, int success, void *userdata); +void PULSE_stream_request_callback(pa_stream *s, size_t n, void *userdata); +DWORD PULSE_bytes_to_mmtime(LPMMTIME lpTime, DWORD position, pa_sample_spec *ss); +BOOL PULSE_setupFormat(LPWAVEFORMATEX wf, pa_sample_spec *ss); +int PULSE_InitRingMessage(PULSE_MSG_RING* omr); +int PULSE_DestroyRingMessage(PULSE_MSG_RING* omr); +void PULSE_ResetRingMessage(PULSE_MSG_RING* omr); +void PULSE_WaitRingMessage(PULSE_MSG_RING* omr, DWORD sleep); +int PULSE_AddRingMessage(PULSE_MSG_RING* omr, enum win_wm_message msg, DWORD param, BOOL wait); +int PULSE_RetrieveRingMessage(PULSE_MSG_RING* omr, enum win_wm_message *msg, DWORD *param, HANDLE *hEvent); +const char * PULSE_getCmdString(enum win_wm_message msg); +#endif