From 484161ad7104e63d0f459c1a3db7d53e4fa48c7b Mon Sep 17 00:00:00 2001 From: Andreas Bierfert Date: Jun 11 2009 21:17:25 +0000 Subject: - version upgrade --- diff --git a/.cvsignore b/.cvsignore index 8cff2a6..80b2d9b 100644 --- a/.cvsignore +++ b/.cvsignore @@ -1 +1 @@ -wine-1.1.18-fe.tar.bz2 +wine-1.1.23-fe.tar.bz2 diff --git a/adding-pulseaudio-to-winecfg.patch b/adding-pulseaudio-to-winecfg.patch index f8770eb..eb1ffd6 100644 --- a/adding-pulseaudio-to-winecfg.patch +++ b/adding-pulseaudio-to-winecfg.patch @@ -178,18 +178,6 @@ index 830cabf..0ed1dbd 100644 IDS_DRIVER_ALSA "Controlador ALSA" IDS_DRIVER_ESOUND "Controlador EsounD" IDS_DRIVER_OSS "Controlador OSS" -diff --git a/programs/winecfg/Ro.rc b/programs/winecfg/Ro.rc -index a4b0cad..c065d36 100644 ---- a/programs/winecfg/Ro.rc -+++ b/programs/winecfg/Ro.rc -@@ -270,6 +270,7 @@ BEGIN - IDS_ACCEL_STANDARD "Standard" - IDS_ACCEL_BASIC "De basă" - IDS_ACCEL_EMULATION "Emulare" -+ IDS_DRIVER_PULSE "Driver PulseAudio" - IDS_DRIVER_ALSA "Driver ALSA" - IDS_DRIVER_ESOUND "Driver Esound" - IDS_DRIVER_OSS "Driver OSS" diff --git a/programs/winecfg/Ru.rc b/programs/winecfg/Ru.rc index 782e1fd..f516399 100644 --- a/programs/winecfg/Ru.rc @@ -287,3 +275,15 @@ index a18fe76..1c4a1e7 100644 #define IDS_DRIVER_ESOUND 8306 #define IDS_DRIVER_OSS 8307 #define IDS_DRIVER_JACK 8308 +diff --git a/programs/winecfg/Ro.rc b/programs/winecfg/Ro.rc +index a4b0cad..c065d36 100644 +--- a/programs/winecfg/Ro.rc ++++ b/programs/winecfg/Ro.rc +@@ -270,6 +270,7 @@ + IDS_ACCEL_STANDARD "Standard" + IDS_ACCEL_BASIC "De bază" + IDS_ACCEL_EMULATION "Emulare" ++ IDS_DRIVER_PULSE "Driver PulseAudio" + IDS_DRIVER_ALSA "Driver ALSA" + IDS_DRIVER_ESOUND "Driver Esound" + IDS_DRIVER_OSS "Driver OSS" diff --git a/sources b/sources index 4efaac4..db75873 100644 --- a/sources +++ b/sources @@ -1 +1 @@ -1933049a7cc725bcb488c6669fa5ca9e wine-1.1.18-fe.tar.bz2 +eb26fea9e52e1cbbe48b107b23564988 wine-1.1.23-fe.tar.bz2 diff --git a/wine.spec b/wine.spec index 41197b9..600a8c5 100644 --- a/wine.spec +++ b/wine.spec @@ -1,5 +1,5 @@ Name: wine -Version: 1.1.18 +Version: 1.1.23 Release: 1%{?dist} Summary: A Windows 16/32/64 bit emulator @@ -43,14 +43,12 @@ Source300: wine-mime-msi.desktop # see http://bugs.winehq.org/show_bug.cgi?id=10495 # and http://art.ified.ca/?page_id=40 Patch400: http://art.ified.ca/downloads/winepulse-0.17-configure.ac.patch -Patch401: http://art.ified.ca/downloads/winepulse-0.24.patch +Patch401: http://art.ified.ca/downloads/winepulse-0.25.patch Patch402: http://art.ified.ca/downloads/adding-pulseaudio-to-winecfg.patch Source402: README-FEDORA-PULSEAUDIO Patch1: wine-rpath.patch -# fix #448338 -Patch2: wine-desktop-mime.patch Buildroot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) ExclusiveArch: %{ix86} @@ -239,7 +237,6 @@ wine audio driver. Please do not report bugs regarding this driver at winehq.org %prep %setup -q -n %{name}-%{version}-fe %patch1 -%patch2 %patch400 -p1 %patch401 -p1 %patch402 -p1 @@ -421,6 +418,7 @@ update-desktop-database &>/dev/null || : %{_libdir}/wine/secedit.exe.so %{_libdir}/wine/services.exe.so %{_libdir}/wine/start.exe.so +%{_libdir}/wine/termsv.exe.so %{_libdir}/wine/wineboot.exe.so %{_libdir}/wine/winebrowser.exe.so %{_libdir}/wine/wineconsole.exe.so @@ -451,6 +449,7 @@ update-desktop-database &>/dev/null || : %{_libdir}/wine/authz.dll.so %{_libdir}/wine/avicap32.dll.so %{_libdir}/wine/avifil32.dll.so +%{_libdir}/wine/bcrypt.dll.so %{_libdir}/wine/browseui.dll.so %{_libdir}/wine/cabinet.dll.so %{_libdir}/wine/cards.dll.so @@ -479,7 +478,6 @@ update-desktop-database &>/dev/null || : %{_libdir}/wine/d3dxof.dll.so %{_libdir}/wine/dbghelp.dll.so %{_libdir}/wine/dciman32.dll.so -%{_libdir}/wine/ddeml.dll16 %{_libdir}/wine/ddraw.dll.so %{_libdir}/wine/ddrawex.dll.so %{_libdir}/wine/devenum.dll.so @@ -589,7 +587,6 @@ update-desktop-database &>/dev/null || : %{_libdir}/wine/msvcrtd.dll.so %{_libdir}/wine/msvfw32.dll.so %{_libdir}/wine/msvidc32.dll.so -%{_libdir}/wine/msvideo.dll16 %{_libdir}/wine/mswsock.dll.so %{_libdir}/wine/msxml3.dll.so %{_libdir}/wine/nddeapi.dll.so @@ -640,7 +637,6 @@ update-desktop-database &>/dev/null || : %{_libdir}/wine/sfc_os.dll.so %{_libdir}/wine/shdoclc.dll.so %{_libdir}/wine/shdocvw.dll.so -%{_libdir}/wine/shell.dll16 %{_libdir}/wine/shell32.dll.so %{_libdir}/wine/shfolder.dll.so %{_libdir}/wine/shlwapi.dll.so @@ -656,6 +652,7 @@ update-desktop-database &>/dev/null || : %{_libdir}/wine/svrapi.dll.so %{_libdir}/wine/sxs.dll.so %{_libdir}/wine/system.drv16 +%{_libdir}/wine/t2embed.dll.so %{_libdir}/wine/tapi32.dll.so %{_libdir}/wine/toolhelp.dll16 %{_libdir}/wine/traffic.dll.so @@ -679,6 +676,7 @@ update-desktop-database &>/dev/null || : %{_libdir}/wine/vtdapi.vxd.so %{_libdir}/wine/vwin32.vxd.so %{_libdir}/wine/w32skrnl.dll.so +%{_libdir}/wine/wbemprox.dll.so %{_libdir}/wine/wineaudioio.drv.so %{_libdir}/wine/winedos.dll.so %{_libdir}/wine/wineoss.drv.so @@ -723,6 +721,7 @@ update-desktop-database &>/dev/null || : %{_libdir}/wine/xinput1_2.dll.so %{_libdir}/wine/xinput1_3.dll.so %{_libdir}/wine/xinput9_1_0.dll.so +%{_libdir}/wine/xmllite.dll.so %{_sysconfdir}/ld.so.conf.d/wine-32.conf # 16bit %{_libdir}/wine/avifile.dll16.so @@ -730,6 +729,7 @@ update-desktop-database &>/dev/null || : %{_libdir}/wine/compobj.dll16.so %{_libdir}/wine/ctl3d.dll16.so %{_libdir}/wine/ctl3dv2.dll16.so +%{_libdir}/wine/ddeml.dll16.so %{_libdir}/wine/dispdib.dll16.so %{_libdir}/wine/display.drv16.so %{_libdir}/wine/imm.dll16.so @@ -738,6 +738,7 @@ update-desktop-database &>/dev/null || : %{_libdir}/wine/mciqtz32.dll.so %{_libdir}/wine/mouse.drv16.so %{_libdir}/wine/msacm.dll16.so +%{_libdir}/wine/msvideo.dll16.so %{_libdir}/wine/ole2.dll16.so %{_libdir}/wine/ole2conv.dll16.so %{_libdir}/wine/ole2disp.dll16.so @@ -747,6 +748,7 @@ update-desktop-database &>/dev/null || : %{_libdir}/wine/olecli.dll16.so %{_libdir}/wine/olesvr.dll16.so %{_libdir}/wine/rasapi16.dll16.so +%{_libdir}/wine/shell.dll16.so %{_libdir}/wine/sound.drv16.so %{_libdir}/wine/storage.dll16.so %{_libdir}/wine/stress.dll16.so @@ -864,6 +866,23 @@ update-desktop-database &>/dev/null || : %{_libdir}/wine/winepulse.drv.so %changelog +* Wed Jun 10 2009 Andreas Bierfert +- 1.1.23-1 +- version upgrade (#491321) +- rediff pulseaudio patch (Michael Cronenworth) + +* Wed May 13 2009 Andreas Bierfert +- 1.1.21-2 +- fix uninstaller (#500479) + +* Tue May 12 2009 Andreas Bierfert +- 1.1.21-1 +- version upgrade + +* Mon Apr 27 2009 Andreas Bierfert +- 1.1.20-1 +- version upgrade + * Mon Mar 30 2009 Andreas Bierfert - 1.1.18-1 - version upgrade (#490672, #491321) diff --git a/winepulse-0.24.patch b/winepulse-0.24.patch deleted file mode 100644 index 97466a4..0000000 --- a/winepulse-0.24.patch +++ /dev/null @@ -1,3390 +0,0 @@ -diff --git a/dlls/winepulse.drv/Makefile.in b/dlls/winepulse.drv/Makefile.in -new file mode 100644 -index 0000000..327f225 ---- /dev/null -+++ b/dlls/winepulse.drv/Makefile.in -@@ -0,0 +1,16 @@ -+TOPSRCDIR = @top_srcdir@ -+TOPOBJDIR = ../.. -+SRCDIR = @srcdir@ -+VPATH = @srcdir@ -+MODULE = winepulse.drv -+IMPORTS = dxguid uuid winmm user32 advapi32 kernel32 -+EXTRALIBS = @PULSELIBS@ -+ -+C_SRCS = dsoutput.c \ -+ waveout.c \ -+ wavein.c \ -+ pulse.c -+ -+@MAKE_DLL_RULES@ -+ -+@DEPENDENCIES@ # everything below this line is overwritten by make depend -diff --git a/dlls/winepulse.drv/dsoutput.c b/dlls/winepulse.drv/dsoutput.c -new file mode 100644 -index 0000000..b37313a ---- /dev/null -+++ b/dlls/winepulse.drv/dsoutput.c -@@ -0,0 +1,576 @@ -+/* -+ * Wine Driver for PulseAudio - DSound Output Functionality -+ * http://pulseaudio.org/ -+ * -+ * Copyright 2009 Arthur Talyor -+ * -+ * Conatins code from other wine multimedia 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 "windef.h" -+#include "winbase.h" -+#include "wingdi.h" -+#include "winuser.h" -+#include "winerror.h" -+#include "mmddk.h" -+#include "dsound.h" -+#include "dsdriver.h" -+#include "winreg.h" -+ -+#include -+#include "wine/debug.h" -+ -+WINE_DEFAULT_DEBUG_CHANNEL(dspulse); -+ -+#if HAVE_PULSEAUDIO -+ -+/*======================================================================* -+ * Low level DSOUND implementation * -+ *======================================================================*/ -+ -+/* A buffer is allocated with a pointer indicating the read position in the -+ * buffer. The pulse write callback reads data from the buffer, updating the -+ * read pointer to the location at the end of the read. Upon reaching the end -+ * or attempting to read past the end of buffer the read pointer wraps around -+ * to the beginning again. DirectSound applications can write anywhere in the -+ * buffer at anytime without locking and can know the location of the read -+ * pointer. The position of the read pointer cannot be changed by the -+ * application and access to it uses a locking scheme. A fake pointer -+ * indicating estimated playback position is also available to the application. -+ * Applications can potentially write to the same area of memory which is also -+ * being read by the pulse thread. However, this is uncommon as directsound -+ * applications know where pulse should be reading from via the pointer -+ * locations and MSDN says that such an operation should be avoided with the -+ * results being undefined. -+ */ -+ -+/* Fragment lengths try to be a power of two close to 10ms worth of data. See -+ * dlls/dsound/mixer.c -+ */ -+static int fragment_length(pa_sample_spec *s) { -+ if (s->rate <= 12800) -+ return 128 * pa_frame_size(s); -+ -+ if (s->rate <= 25600) -+ return 256 * pa_frame_size(s); -+ -+ if (s->rate <= 51200) -+ return 512 * pa_frame_size(s); -+ -+ return 1024 * pa_frame_size(s); -+} -+ -+/* Callback from the pulse thread for stream data */ -+static void DSPULSE_BufferReadCallback(pa_stream *s, size_t nbytes, void *userdata) { -+ IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)userdata; -+ -+ if (!This->buffer) -+ return; -+ -+ /* Fraglens are always powers of 2 */ -+ nbytes+= This->fraglen - 1; -+ nbytes&= ~(This->fraglen - 1); -+ -+ /* If we advance more than 10 fragments at a time it appears that the buffer -+ * pointer is never advancing because of wrap-around. Evil magic numbers. */ -+ if (nbytes > This->fraglen * 5) -+ nbytes = This->fraglen * 5; -+ -+ TRACE("Reading %u bytes.\n", nbytes); -+ -+ if (This->buffer_read_offset + nbytes <= This->buffer_length) { -+ pa_stream_write(s, This->buffer + This->buffer_read_offset, nbytes, NULL, 0, PA_SEEK_RELATIVE); -+ This->buffer_play_offset = This->buffer_read_offset; -+ This->buffer_read_offset += nbytes; -+ } else { -+ size_t write_length = This->buffer_length - This->buffer_read_offset; -+ nbytes -= write_length; -+ pa_stream_write(s, This->buffer + This->buffer_read_offset, write_length, NULL, 0, PA_SEEK_RELATIVE); -+ pa_stream_write(s, This->buffer, nbytes, NULL, 0, PA_SEEK_RELATIVE); -+ This->buffer_play_offset = This->buffer_read_offset; -+ This->buffer_read_offset = nbytes; -+ } -+ -+ This->buffer_read_offset %= This->buffer_length; -+} -+ -+/* Called when the stream underruns. Just for information */ -+static void DSPULSE_BufferUnderflowCallback(pa_stream *s, void *userdata) { -+ WARN("(%p) underrun.\n", userdata); -+} -+ -+/* Connects a stream to the server. Does not update -+ * IDsDriverBufferImpl->fraglen. Does not lock the pulse mainloop or free -+ * objects in case of failure. This should be handled by the calling function. -+ */ -+static HRESULT DSPULSE_ConnectStream(IDsDriverBufferImpl* This) { -+ pa_buffer_attr ba_request; -+ const pa_buffer_attr *ba_obtained; -+ char c[PA_SAMPLE_SPEC_SNPRINT_MAX]; -+ pa_stream_flags_t stream_flags = PA_STREAM_START_CORKED; -+ -+#if PA_PROTOCOL_VERSION >= 14 -+ /* We are a "fragment wait based" application, so this flag should be -+ * acceptable. */ -+ stream_flags |= PA_STREAM_EARLY_REQUESTS; -+#elif PA_PROTOCOL_VERSION >= 13 && PA_API_VERSION >= 12 -+ stream_flags |= PA_STREAM_ADJUST_LATENCY; -+#endif -+ -+ pa_sample_spec_snprint(c, PA_SAMPLE_SPEC_SNPRINT_MAX, &This->sample_spec); -+ TRACE("Sample spec %s fragment size %u.\n", c, This->fraglen); -+ -+ ba_request.tlength = This->fraglen * 4; // ~40ms -+ ba_request.minreq = This->fraglen; // ~10ms -+ ba_request.prebuf = (uint32_t)-1; // same as tlength -+ ba_request.maxlength = This->buffer_length; // 2^x = ~3s -+ -+ TRACE("Asking for buffer tlength:%u (%llums) minreq:%u (%llums)\n", -+ ba_request.tlength, pa_bytes_to_usec(ba_request.tlength, &This->sample_spec)/1000, -+ ba_request.minreq, pa_bytes_to_usec(ba_request.minreq, &This->sample_spec)/1000); -+ -+ This->stream = pa_stream_new(PULSE_context, "DirectSound Buffer", &This->sample_spec, NULL); -+ if (!This->stream) return DSERR_BADFORMAT; -+ -+ pa_stream_set_state_callback(This->stream, PULSE_StreamStateCallback, This); -+ pa_stream_set_write_callback(This->stream, DSPULSE_BufferReadCallback, This); -+ pa_stream_set_underflow_callback(This->stream, DSPULSE_BufferUnderflowCallback, This); -+ -+ TRACE("Attempting to connect (%p)->stream for playback on %s\n", This, WOutDev[This->drv->wDevID].device_name); -+ pa_stream_connect_playback(This->stream, WOutDev[This->drv->wDevID].device_name, &ba_request, stream_flags, NULL, NULL); -+ for (;;) { -+ pa_context_state_t cstate = pa_context_get_state(PULSE_context); -+ pa_stream_state_t sstate = pa_stream_get_state(This->stream); -+ -+ if (cstate == PA_CONTEXT_FAILED || cstate == PA_CONTEXT_TERMINATED || -+ sstate == PA_STREAM_FAILED || sstate == PA_STREAM_TERMINATED) { -+ ERR("Failed to connect stream context object: %s\n", pa_strerror(pa_context_errno(PULSE_context))); -+ return DSERR_BUFFERLOST; -+ } -+ -+ if (sstate == PA_STREAM_READY) -+ break; -+ -+ pa_threaded_mainloop_wait(PULSE_ml); -+ } -+ TRACE("(%p)->stream connected for playback.\n", This); -+ ba_obtained = pa_stream_get_buffer_attr(This->stream); -+ -+ TRACE("Obtained buffer tlength:%u (%llums) minreq:%u (%llums)\n", -+ ba_obtained->tlength, pa_bytes_to_usec(ba_obtained->tlength, &This->sample_spec)/1000, -+ ba_obtained->minreq, pa_bytes_to_usec(ba_obtained->minreq, &This->sample_spec)/1000); -+ -+ return DS_OK; -+} -+ -+static HRESULT WINAPI IDsDriverBufferImpl_QueryInterface(PIDSDRIVERBUFFER iface, REFIID riid, LPVOID *ppobj) { -+ /* IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; */ -+ FIXME("(): stub!\n"); -+ return DSERR_UNSUPPORTED; -+} -+ -+static ULONG WINAPI IDsDriverBufferImpl_AddRef(PIDSDRIVERBUFFER iface) { -+ IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; -+ ULONG refCount = InterlockedIncrement(&This->ref); -+ -+ TRACE("(%p)->(ref before=%u)\n",This, refCount - 1); -+ -+ return refCount; -+} -+ -+static ULONG WINAPI IDsDriverBufferImpl_Release(PIDSDRIVERBUFFER iface) { -+ IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; -+ ULONG refCount = InterlockedDecrement(&This->ref); -+ -+ TRACE("(%p)->(ref before=%u)\n",This, refCount + 1); -+ -+ if (refCount) -+ return refCount; -+ -+ TRACE("mmap buffer %p destroyed\n", This->buffer); -+ pa_threaded_mainloop_lock(PULSE_ml); -+ PULSE_WaitForOperation(pa_stream_cork(This->stream, 1, PULSE_StreamSuccessCallback, This)); -+ pa_stream_disconnect(This->stream); -+ if (This == This->drv->primary) -+ This->drv->primary = NULL; -+ HeapFree(GetProcessHeap(), 0, This->buffer); -+ This->buffer = NULL; -+ pa_stream_unref(This->stream); -+ This->stream = NULL; -+ HeapFree(GetProcessHeap(), 0, This); -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ -+ return 0; -+} -+ -+static HRESULT WINAPI IDsDriverBufferImpl_Lock(PIDSDRIVERBUFFER iface, -+ LPVOID*ppvAudio1,LPDWORD pdwLen1, -+ LPVOID*ppvAudio2,LPDWORD pdwLen2, -+ DWORD dwWritePosition,DWORD dwWriteLen, -+ DWORD dwFlags) -+{ -+ /* We set DSDDESC_DONTNEEDPRIMARYLOCK so this should never be called */ -+ TRACE("(%p): stub", iface); -+ return DSERR_UNSUPPORTED; -+} -+ -+static HRESULT WINAPI IDsDriverBufferImpl_Unlock(PIDSDRIVERBUFFER iface, -+ LPVOID pvAudio1,DWORD dwLen1, -+ LPVOID pvAudio2,DWORD dwLen2) -+{ -+ /* We set DSDDESC_DONTNEEDPRIMARYLOCK so this should never be called */ -+ TRACE("(%p): stub", iface); -+ return DSERR_UNSUPPORTED; -+} -+ -+/* You cannot change the sample format of a connected stream, so we need to -+ * destroy and re-create the stream if the sample spec is different */ -+static HRESULT WINAPI IDsDriverBufferImpl_SetFormat(PIDSDRIVERBUFFER iface, LPWAVEFORMATEX pwfx) { -+ IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; -+ pa_sample_spec old_spec; -+ -+ TRACE("(%p, %p)\n", iface, pwfx); -+ -+ old_spec.rate = This->sample_spec.rate; -+ old_spec.format = This->sample_spec.format; -+ old_spec.channels = This->sample_spec.channels; -+ -+ if (!PULSE_SetupFormat(pwfx, &This->sample_spec)) -+ return DSERR_BADFORMAT; -+ -+ if (old_spec.rate == This->sample_spec.rate && -+ old_spec.format == This->sample_spec.format && -+ old_spec.channels == This->sample_spec.channels) { -+ TRACE("same as original sample spec, exiting.\n"); -+ return DS_OK; -+ } -+ -+ /* If the format doesn't match, return an error and the buffer will be remade */ -+ TRACE("Formats don't match, failing causing re-creation.\n"); -+ return DSERR_BUFFERLOST; -+} -+ -+static HRESULT WINAPI IDsDriverBufferImpl_SetFrequency(PIDSDRIVERBUFFER iface, DWORD dwFreq) -+{ -+ /* IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; */ -+ /* You can't do for primary buffers anyways */ -+ return S_OK; -+} -+ -+static HRESULT WINAPI IDsDriverBufferImpl_SetVolumePan(PIDSDRIVERBUFFER iface, PDSVOLUMEPAN pVolPan) { -+ IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; -+ -+ if (This->sample_spec.channels == 2) { -+ This->volume.channels = 2; -+ if (pVolPan->lPan < 0) { -+ This->volume.values[0] = pa_sw_volume_from_dB(pVolPan->lVolume/-10.0 + pVolPan->lPan/10.0); -+ This->volume.values[1] = pa_sw_volume_from_dB(pVolPan->lVolume/-10.0); -+ } else { -+ This->volume.values[0] = pa_sw_volume_from_dB(pVolPan->lVolume/-10.0); -+ This->volume.values[1] = pa_sw_volume_from_dB(pVolPan->lVolume/-10.0 - pVolPan->lPan/10.0); -+ } -+ } else { -+ WARN("Panning non-stereo streams not supported yet!\n"); -+ pa_cvolume_set(&This->volume, This->sample_spec.channels, pa_sw_volume_from_dB(pVolPan->lVolume/-10.0)); -+ /* Would be nice to return DSERR_CONTROLUNAVAIL, but that isn't up to us -+ * here */ -+ } -+ -+ pa_threaded_mainloop_lock(PULSE_ml); -+ if (This->stream && PULSE_context && pa_context_get_state(PULSE_context) == PA_CONTEXT_READY && -+ pa_stream_get_state(This->stream) == PA_STREAM_READY && pa_cvolume_valid(&This->volume)) -+ PULSE_WaitForOperation( -+ pa_context_set_sink_input_volume( -+ PULSE_context, pa_stream_get_index(This->stream), -+ &This->volume, PULSE_ContextSuccessCallback, This)); -+ -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ -+ return DS_OK; -+} -+ -+static HRESULT WINAPI IDsDriverBufferImpl_SetPosition(PIDSDRIVERBUFFER iface, DWORD dwNewPos) -+{ -+ /* IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; */ -+ /* Not allowed on primary buffers */ -+ return DSERR_UNSUPPORTED; -+} -+ -+static HRESULT WINAPI IDsDriverBufferImpl_GetPosition(PIDSDRIVERBUFFER iface, -+ LPDWORD lpdwPlay, LPDWORD lpdwWrite) -+{ -+ IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; -+ -+ pa_threaded_mainloop_lock(PULSE_ml); -+ if (!This->buffer || pa_stream_get_state(This->stream) != PA_STREAM_READY) { -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ return DSERR_UNINITIALIZED; -+ } -+ -+ if (lpdwPlay) -+ *lpdwPlay = This->buffer_play_offset; -+ if (lpdwWrite) -+ *lpdwWrite = This->buffer_read_offset; -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ -+ return DS_OK; -+} -+ -+static HRESULT WINAPI IDsDriverBufferImpl_Play(PIDSDRIVERBUFFER iface, DWORD dwRes1, DWORD dwRes2, DWORD dwFlags) { -+ IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; -+ TRACE("(%p,%x,%x,%x)\n",iface,dwRes1,dwRes2,dwFlags); -+ -+ pa_threaded_mainloop_lock(PULSE_ml); -+ PULSE_WaitForOperation(pa_stream_cork(This->stream, 0, PULSE_StreamSuccessCallback, This)); -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ return DS_OK; -+} -+ -+static HRESULT WINAPI IDsDriverBufferImpl_Stop(PIDSDRIVERBUFFER iface) { -+ IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; -+ TRACE("(%p)\n",iface); -+ -+ pa_threaded_mainloop_lock(PULSE_ml); -+ PULSE_WaitForOperation(pa_stream_cork(This->stream, 1, PULSE_StreamSuccessCallback, This)); -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ return DS_OK; -+} -+ -+/*****************************************************************************/ -+ -+static const IDsDriverBufferVtbl dsdbvt = -+{ -+ IDsDriverBufferImpl_QueryInterface, -+ IDsDriverBufferImpl_AddRef, -+ IDsDriverBufferImpl_Release, -+ IDsDriverBufferImpl_Lock, -+ IDsDriverBufferImpl_Unlock, -+ IDsDriverBufferImpl_SetFormat, -+ IDsDriverBufferImpl_SetFrequency, -+ IDsDriverBufferImpl_SetVolumePan, -+ IDsDriverBufferImpl_SetPosition, -+ IDsDriverBufferImpl_GetPosition, -+ IDsDriverBufferImpl_Play, -+ IDsDriverBufferImpl_Stop -+}; -+ -+static HRESULT WINAPI IDsDriverImpl_CreateSoundBuffer(PIDSDRIVER iface, -+ LPWAVEFORMATEX pwfx, -+ DWORD dwFlags, DWORD dwCardAddress, -+ LPDWORD pdwcbBufferSize, -+ LPBYTE *ppbBuffer, -+ LPVOID *ppvObj) { -+ IDsDriverImpl *This = (IDsDriverImpl *)iface; -+ IDsDriverBufferImpl** ippdsdb = (IDsDriverBufferImpl**)ppvObj; -+ IDsDriverBufferImpl *That; -+ HRESULT ret; -+ -+ TRACE("(%p,%p,%x,%x)\n",iface,pwfx,dwFlags,dwCardAddress); -+ /* we only support primary buffers */ -+ -+ if (!(dwFlags & DSBCAPS_PRIMARYBUFFER)) -+ return DSERR_UNSUPPORTED; -+ if (This->primary) -+ return DSERR_ALLOCATED; -+ This->primary = *ippdsdb; -+ -+ *ippdsdb = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(IDsDriverBufferImpl)); -+ -+ if (!*ippdsdb) { -+ ERR("Out of memory\n"); -+ return DSERR_OUTOFMEMORY; -+ } -+ -+ That = *ippdsdb; -+ That->lpVtbl = &dsdbvt; -+ That->ref = 1; -+ -+ That->drv = This; -+ TRACE("IdsDriverBufferImpl %p created.\n", That); -+ -+ if (!PULSE_SetupFormat(pwfx, &That->sample_spec)) { -+ WARN("Bad audio format.\n"); -+ ret = DSERR_BADFORMAT; -+ goto err; -+ } -+ -+ /* The buffer length has to be greater than fraglen * 20 or else logic in -+ * dlls/dsound/mixer.c fails to correctly understand buffer wrap around. */ -+ That->fraglen = fragment_length(&That->sample_spec); -+ That->buffer_length = That->fraglen * 32; -+ That->buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, That->buffer_length); -+ if (!That->buffer) { -+ ret = DSERR_OUTOFMEMORY; -+ goto err; -+ } -+ That->buffer_read_offset = 0; -+ That->buffer_play_offset = 0; -+ -+ pa_threaded_mainloop_lock(PULSE_ml); -+ ret = DSPULSE_ConnectStream(That); -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ -+ if (ret != DS_OK) -+ goto err; -+ -+ *pdwcbBufferSize = That->buffer_length; -+ *ppbBuffer = That->buffer; -+ -+ TRACE("Exiting with success. Have buffer %p of length %u\n", That->buffer, That->buffer_length); -+ return DS_OK; -+ -+err: -+ pa_threaded_mainloop_lock(PULSE_ml); -+ if (That->stream) { -+ if (pa_stream_get_state(That->stream) == PA_STREAM_READY) -+ pa_stream_disconnect(That->stream); -+ pa_stream_unref(That->stream); -+ That->stream = NULL; -+ } -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ HeapFree(GetProcessHeap(), 0, That->buffer); -+ HeapFree(GetProcessHeap(), 0, *ippdsdb); -+ WARN("exiting with failure.\n"); -+ return ret; -+} -+ -+static HRESULT WINAPI IDsDriverImpl_QueryInterface(PIDSDRIVER iface, REFIID riid, LPVOID *ppobj) { -+ /* IDsDriverImpl *This = (IDsDriverImpl *)iface; */ -+ FIXME("(%p): stub!\n",iface); -+ return DSERR_UNSUPPORTED; -+} -+ -+static ULONG WINAPI IDsDriverImpl_AddRef(PIDSDRIVER iface) { -+ IDsDriverImpl *This = (IDsDriverImpl *)iface; -+ ULONG refCount = InterlockedIncrement(&This->ref); -+ -+ TRACE("(%p)->(ref before=%u)\n",This, refCount - 1); -+ -+ return refCount; -+} -+ -+static ULONG WINAPI IDsDriverImpl_Release(PIDSDRIVER iface) { -+ IDsDriverImpl *This = (IDsDriverImpl *)iface; -+ ULONG refCount = InterlockedDecrement(&This->ref); -+ -+ TRACE("(%p)->(ref before=%u)\n",This, refCount + 1); -+ -+ if (refCount) -+ return refCount; -+ -+ HeapFree(GetProcessHeap(), 0, This); -+ return 0; -+} -+ -+static HRESULT WINAPI IDsDriverImpl_GetDriverDesc(PIDSDRIVER iface, PDSDRIVERDESC pDesc) { -+ IDsDriverImpl *This = (IDsDriverImpl *)iface; -+ TRACE("(%p,%p)\n",iface,pDesc); -+ *pDesc = WOutDev[This->wDevID].ds_desc; -+ pDesc->dwFlags = DSDDESC_DONTNEEDSECONDARYLOCK | DSDDESC_DONTNEEDPRIMARYLOCK; -+ pDesc->dnDevNode = 0; /*TODO: Bwah? */ -+ pDesc->wVxdId = 0; -+ pDesc->wReserved = 0; -+ pDesc->ulDeviceNum = This->wDevID; -+ pDesc->dwHeapType = DSDHEAP_NOHEAP; -+ pDesc->pvDirectDrawHeap = NULL; -+ pDesc->dwMemStartAddress = 0xDEAD0000; -+ pDesc->dwMemEndAddress = 0xDEAF0000; -+ pDesc->dwMemAllocExtra = 0; -+ pDesc->pvReserved1 = NULL; -+ pDesc->pvReserved2 = NULL; -+ return DS_OK; -+} -+ -+static HRESULT WINAPI IDsDriverImpl_Close(PIDSDRIVER iface) { -+ IDsDriverImpl *This = (IDsDriverImpl *)iface; -+ TRACE("(%p) stub, harmless\n",This); -+ return DS_OK; -+} -+ -+static HRESULT WINAPI IDsDriverImpl_Open(PIDSDRIVER iface) { -+ IDsDriverImpl *This = (IDsDriverImpl *)iface; -+ TRACE("(%p) stub, harmless\n",This); -+ return S_OK; -+} -+ -+static HRESULT WINAPI IDsDriverImpl_GetCaps(PIDSDRIVER iface, PDSDRIVERCAPS pCaps) { -+ IDsDriverImpl *This = (IDsDriverImpl *)iface; -+ -+ if (pa_context_get_state(PULSE_context) != PA_CONTEXT_READY) { -+ ERR("Context failure.\n"); -+ return DSERR_GENERIC; -+ } -+ -+ TRACE("(%p,%p)\n",iface,pCaps); -+ *pCaps = WOutDev[This->wDevID].ds_caps; -+ return DS_OK; -+} -+ -+static HRESULT WINAPI IDsDriverImpl_DuplicateSoundBuffer(PIDSDRIVER iface, -+ PIDSDRIVERBUFFER pBuffer, -+ LPVOID *ppvObj) -+{ -+ IDsDriverImpl *This = (IDsDriverImpl *)iface; -+ FIXME("(%p,%p): stub\n",This,pBuffer); -+ return DSERR_INVALIDCALL; -+} -+ -+static const IDsDriverVtbl dsdvt = -+{ -+ IDsDriverImpl_QueryInterface, -+ IDsDriverImpl_AddRef, -+ IDsDriverImpl_Release, -+ IDsDriverImpl_GetDriverDesc, -+ IDsDriverImpl_Open, -+ IDsDriverImpl_Close, -+ IDsDriverImpl_GetCaps, -+ IDsDriverImpl_CreateSoundBuffer, -+ IDsDriverImpl_DuplicateSoundBuffer -+}; -+ -+DWORD wodDsCreate(UINT wDevID, PIDSDRIVER* drv) { -+ IDsDriverImpl** idrv = (IDsDriverImpl**)drv; -+ -+ if (pa_context_get_state(PULSE_context) != PA_CONTEXT_READY || pa_context_is_local(PULSE_context) != 1) { -+ WARN("Connection failure or server is not local, falling back to WaveOut HEL.\n"); -+ return MMSYSERR_NOTSUPPORTED; -+ } -+ -+ *idrv = HeapAlloc(GetProcessHeap(), 0, sizeof(IDsDriverImpl)); -+ if (!*idrv) -+ return MMSYSERR_NOMEM; -+ -+ TRACE("IDsDriverImpl %p created.\n", *idrv); -+ -+ (*idrv)->lpVtbl = &dsdvt; -+ (*idrv)->ref = 1; -+ (*idrv)->wDevID = wDevID; -+ (*idrv)->primary = NULL; -+ -+ return MMSYSERR_NOERROR; -+} -+ -+DWORD wodDsDesc(UINT wDevID, PDSDRIVERDESC desc) { -+ TRACE("(%u, %p)\n", wDevID, desc); -+ *desc = WOutDev[wDevID].ds_desc; -+ return MMSYSERR_NOERROR; -+} -+#endif /* HAVE_PULSEAUDIO */ -diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c -new file mode 100644 -index 0000000..aa84c9b ---- /dev/null -+++ b/dlls/winepulse.drv/pulse.c -@@ -0,0 +1,861 @@ -+/* -+ * Wine Driver for PulseAudio -+ * http://pulseaudio.org/ -+ * -+ * Copyright 2009 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 "winreg.h" -+#include "mmddk.h" -+#include "ks.h" -+#include "ksguid.h" -+#include "ksmedia.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); -+ -+/* 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; int foo; foo = write((omr)->msg_pipe[1], &x, sizeof(x)); } while (0) -+#define CLEAR_OMR(omr) do { int x = 0; int foo; foo = 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; -+ } -+ /* 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_SetupFormat -+ * -+ * Checks to see if the audio format in wf is supported, and if so set up the -+ * pa_sample_spec at ss to that format. -+ */ -+BOOL PULSE_SetupFormat(LPWAVEFORMATEX wf, pa_sample_spec *ss) { -+ WAVEFORMATEXTENSIBLE *wfex; -+ -+ ss->channels = wf->nChannels; -+ ss->rate = wf->nSamplesPerSec; -+ ss->format = PA_SAMPLE_INVALID; -+ -+ if (ss->rate < DSBFREQUENCY_MIN || ss->rate > DSBFREQUENCY_MAX) return FALSE; -+ -+ switch (wf->wFormatTag) { -+ case WAVE_FORMAT_PCM: -+ /* MSDN says that for WAVE_FORMAT_PCM, nChannels must be 1 or 2 and -+ * wBitsPerSample must be 8 or 16, yet other values are used by some -+ * applications in the wild for surround. */ -+ if (ss->channels > 6 || ss->channels < 1) return FALSE; -+ ss->format = wf->wBitsPerSample == 8 ? PA_SAMPLE_U8 -+ : wf->wBitsPerSample == 16 ? PA_SAMPLE_S16NE -+#if PA_PROTOCOL_VERSION >= 15 -+ : wf->wBitsPerSample == 24 ? PA_SAMPLE_S24NE -+#endif -+ : wf->wBitsPerSample == 32 ? PA_SAMPLE_S32NE -+ : PA_SAMPLE_INVALID; -+ break; -+ -+ case WAVE_FORMAT_MULAW: -+ if (wf->wBitsPerSample == 8) ss->format = PA_SAMPLE_ULAW; -+ break; -+ -+ case WAVE_FORMAT_ALAW: -+ if (wf->wBitsPerSample == 8) ss->format = PA_SAMPLE_ALAW; -+ break; -+ -+ case WAVE_FORMAT_EXTENSIBLE: -+ if (wf->cbSize > 22) return FALSE; -+ if (ss->channels < 1 || ss->channels > 6) return FALSE; -+ wfex = (WAVEFORMATEXTENSIBLE *)wf; -+ if (IsEqualGUID(&wfex->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM)) { -+ if (wf->wBitsPerSample == wfex->Samples.wValidBitsPerSample) { -+ ss->format = wf->wBitsPerSample == 8 ? PA_SAMPLE_U8 -+ : wf->wBitsPerSample == 16 ? PA_SAMPLE_S16NE -+#if PA_PROTOCOL_VERSION >= 15 -+ : wf->wBitsPerSample == 24 ? PA_SAMPLE_S24NE -+#endif -+ : wf->wBitsPerSample == 32 ? PA_SAMPLE_S32NE -+ : PA_SAMPLE_INVALID; -+ } else { -+#if PA_PROTOCOL_VERSION >= 15 -+ if (wf->wBitsPerSample == 32 && wfex->Samples.wValidBitsPerSample == 24) -+ ss->format = PA_SAMPLE_S24_32NE; -+ else -+#endif -+ return FALSE; -+ } -+ } else if (wf->wBitsPerSample != wfex->Samples.wValidBitsPerSample) { -+ return FALSE; -+ } else if ((IsEqualGUID(&wfex->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))) { -+ ss->format = PA_SAMPLE_FLOAT32NE; -+ } else { -+ WARN("only KSDATAFORMAT_SUBTYPE_PCM and KSDATAFORMAT_SUBTYPE_IEEE_FLOAT of WAVE_FORMAT_EXTENSIBLE supported\n"); -+ return FALSE; -+ } -+ break; -+ -+ default: -+ WARN("only WAVE_FORMAT_PCM, WAVE_FORMAT_MULAW, WAVE_FORMAT_ALAW and WAVE_FORMAT_EXTENSIBLE supported\n"); -+ return FALSE; -+ } -+ -+ if (!pa_sample_spec_valid(ss)) return FALSE; -+ if (wf->nBlockAlign != pa_frame_size(ss)) { -+ ERR("wf->nBlockAlign != the format frame size!\n"); -+ return FALSE; -+ } -+ -+ return TRUE; -+} -+ -+/************************************************************************** -+ * PULSE_GetMMTime [internal] -+ */ -+void PULSE_GetMMTime(const pa_timing_info *t, pa_sample_spec *s, size_t last_reset, LPMMTIME lpTime) { -+ pa_usec_t time, time_temp; -+ size_t bytes, bytes_temp; -+ -+ /* If this is a recording stream we want the write_index and not the read index */ -+ if (last_reset == (size_t) -1) { -+ bytes = t->write_index; -+ last_reset = 0; -+ } else { -+ bytes = t->read_index; -+ if (last_reset > bytes) -+ last_reset = 0; -+ } -+ time = pa_bytes_to_usec(bytes, s); -+ time += pa_timeval_age(&t->timestamp); -+ -+ if (t->playing) { -+ bytes += ((pa_timeval_age(&t->timestamp) / 1000) * pa_bytes_per_second(s)) / 1000; -+ bytes_temp = (time_temp = t->sink_usec + t->transport_usec)/1000 * pa_bytes_per_second(s)/1000; -+ } else { -+ time = 0; -+ time_temp = 0; -+ bytes_temp = 0; -+ } -+ -+ time -= pa_bytes_to_usec(last_reset, s); -+ bytes -= last_reset; -+ if (bytes > bytes_temp) -+ bytes -= bytes_temp; -+ else -+ bytes = 0; -+ -+ if (time > time_temp) -+ time -= time_temp; -+ else -+ time = 0; -+ -+ bytes -= bytes % pa_frame_size(s); -+ time /= 1000; /* In milliseconds now */ -+ -+ switch (lpTime->wType) { -+ case TIME_SAMPLES: -+ lpTime->u.sample = bytes / pa_frame_size(s); -+ TRACE("TIME_SAMPLES=%u\n", lpTime->u.sample); -+ break; -+ case TIME_MS: -+ lpTime->u.ms = time; -+ TRACE("TIME_MS=%u\n", lpTime->u.ms); -+ break; -+ case TIME_SMPTE: -+ lpTime->u.smpte.fps = 30; -+ lpTime->u.smpte.sec = time/1000; -+ lpTime->u.smpte.min = lpTime->u.smpte.sec / 60; -+ lpTime->u.smpte.sec -= 60 * lpTime->u.smpte.min; -+ lpTime->u.smpte.hour = lpTime->u.smpte.min / 60; -+ lpTime->u.smpte.min -= 60 * lpTime->u.smpte.hour; -+ lpTime->u.smpte.frame = time / lpTime->u.smpte.fps * 1000; -+ TRACE("TIME_SMPTE=%02u:%02u:%02u:%02u\n", -+ lpTime->u.smpte.hour, lpTime->u.smpte.min, -+ lpTime->u.smpte.sec, lpTime->u.smpte.frame); -+ break; -+ default: -+ WARN("Format %d not supported, using TIME_BYTES !\n", lpTime->wType); -+ lpTime->wType = TIME_BYTES; -+ /* fall through */ -+ case TIME_BYTES: -+ lpTime->u.cb = bytes; -+ TRACE("TIME_BYTES=%u\n", lpTime->u.cb); -+ break; -+ } -+} -+ -+/************************************************************************** -+ * PULSE_WaitForOperation -+ * -+ * Waits for pa operations to complete, and dereferences the operation. -+ */ -+void PULSE_WaitForOperation(pa_operation *o) { -+ if (!o) return; -+ -+ for (;;) { -+ if (pa_operation_get_state(o) != PA_OPERATION_RUNNING) -+ break; -+ pa_threaded_mainloop_wait(PULSE_ml); -+ } -+ pa_operation_unref(o); -+} -+ -+/************************************************************************** -+ * Common Callbacks -+ */ -+ -+/************************************************************************** -+ * PULSE_StreamRequestCallback -+ * -+ * Called by the pulse mainloop whenever it wants or has audio data. -+ */ -+void PULSE_StreamRequestCallback(pa_stream *s, size_t nbytes, void *userdata) { -+ WINE_WAVEINST *ww = (WINE_WAVEINST*)userdata; -+ assert(s && ww); -+ -+ TRACE("Asking to feed/be fed %u bytes\n", nbytes); -+ -+ /* Make sure that the player/recorder is running */ -+ if (ww->hThread != INVALID_HANDLE_VALUE && ww->msgRing.messages) { -+ PULSE_AddRingMessage(&ww->msgRing, WINE_WM_FEED, (DWORD)nbytes, FALSE); -+ } -+} -+/************************************************************************** -+ * PULSE_StreamSuspendedCallback [internal] -+ * -+ * Called by the pulse mainloop any time stream playback is intentionally -+ * suspended or resumed from being suspended. -+ */ -+void PULSE_StreamSuspendedCallback(pa_stream *s, void *userdata) { -+ WINE_WAVEINST *wwo = (WINE_WAVEINST*)userdata; -+ assert(s && wwo); -+ -+ /* Currently we handle this kinda like an underrun. Perhaps we should -+ * tell the client somehow so it doesn't just hang? */ -+ -+ if (!pa_stream_is_suspended(s) && wwo->hThread != INVALID_HANDLE_VALUE && wwo->msgRing.ring_buffer_size > 0) -+ PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_XRUN, 0, FALSE); -+} -+ -+/************************************************************************** -+ * PULSE_StreamUnderflowCallback [internal] -+ * -+ * Called by the pulse mainloop when the prebuf runs out of data. -+ */ -+void PULSE_StreamUnderflowCallback(pa_stream *s, void *userdata) { -+ WINE_WAVEINST *wwo = (WINE_WAVEINST*)userdata; -+ assert(s && wwo); -+ -+ /* If we aren't playing, don't care ^_^ */ -+ if (wwo->state != WINE_WS_PLAYING) return; -+ -+ TRACE("Underrun occurred.\n"); -+ -+ if (wwo->hThread != INVALID_HANDLE_VALUE && wwo->msgRing.ring_buffer_size > 0); -+ PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_XRUN, 0, FALSE); -+} -+ -+/************************************************************************** -+ * PULSE_StreamOverflowCallback [internal] -+ * -+ * Called by the pulse mainloop when the write buffer was overflowed and data -+ * was lost. -+ */ -+void PULSE_StreamOverflowCallback(pa_stream *s, void *userdata) { -+ WINE_WAVEINST *wwo = (WINE_WAVEINST*)userdata; -+ assert(s && wwo); -+ -+ ERR("Overflow occurred!?!\n"); -+ -+ if (wwo->hThread != INVALID_HANDLE_VALUE && wwo->msgRing.ring_buffer_size > 0); -+ PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_XRUN, 1, FALSE); -+} -+ -+/************************************************************************** -+ * PULSE_StreamMovedCallback [internal] -+ * -+ * Called by the pulse mainloop when the stream gets moved, resulting in -+ * possibly different metrics. -+ */ -+void PULSE_StreamMovedCallback(pa_stream *s, void *userdata) { -+ FIXME("stub"); -+} -+ -+ -+/****************************************************************** -+ * PULSE_StreamStateCallback -+ * -+ * Called by pulse whenever the state of the stream changes. -+ * -+ * void *userdata is either the dsound or wave instance -+ */ -+void PULSE_StreamStateCallback(pa_stream *s, void *userdata) { -+ assert(s); -+ -+ switch (pa_stream_get_state(s)) { -+ case PA_STREAM_READY: -+ TRACE("Stream %p ready\n", userdata); -+ break; -+ -+ case PA_STREAM_TERMINATED: -+ TRACE("Stream %p terminated\n", userdata); -+ break; -+ -+ case PA_STREAM_FAILED: -+ ERR("Stream %p failed!\n", userdata); -+ break; -+ -+ case PA_STREAM_UNCONNECTED: -+ case PA_STREAM_CREATING: -+ return; -+ } -+ pa_threaded_mainloop_signal(PULSE_ml, 0); -+} -+ -+/************************************************************************** -+ * PULSE_StreamSucessCallback -+ */ -+void PULSE_StreamSuccessCallback(pa_stream *s, int success, void *userdata) { -+ if (!success) -+ WARN("Stream %p operation failed: %s\n", userdata, pa_strerror(pa_context_errno(PULSE_context))); -+ -+ pa_threaded_mainloop_signal(PULSE_ml, 0); -+} -+ -+/************************************************************************** -+ * PULSE_ContextSuccessCallback -+ */ -+void PULSE_ContextSuccessCallback(pa_context *c, int success, void *userdata) { -+ if (!success) ERR("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_ContextStateCallback [internal] -+ */ -+static void PULSE_ContextStateCallback(pa_context *c, void *userdata) { -+ assert(c); -+ -+ switch (pa_context_get_state(c)) { -+ case PA_CONTEXT_CONNECTING: -+ case PA_CONTEXT_UNCONNECTED: -+ 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: -+ ERR("Context failure: %s\n", pa_strerror(pa_context_errno(c))); -+ pa_threaded_mainloop_signal(PULSE_ml, 0); -+ break; -+ } -+} -+ -+/************************************************************************** -+ * PULSE_AllocateWaveinDevice [internal] -+ * -+ * Creates or adds a device to WInDev based on the pa_source_info. -+ */ -+static void PULSE_AllocateWaveinDevice(const char *name, const char *device, const char *description, const pa_cvolume *v) { -+ WINE_WAVEDEV *wdi; -+ -+ if (WInDev) -+ wdi = HeapReAlloc(GetProcessHeap(), 0, WInDev, sizeof(WINE_WAVEDEV) * (PULSE_WidNumDevs + 1)); -+ else -+ wdi = HeapAlloc(GetProcessHeap(), 0, sizeof(WINE_WAVEDEV)); -+ -+ if (!wdi) return; -+ -+ WInDev = wdi; -+ wdi = &WInDev[PULSE_WidNumDevs++]; -+ memset(wdi, 0, sizeof(WINE_WAVEDEV)); -+ memset(&(wdi->caps.in), 0, sizeof(wdi->caps.in)); -+ snprintf(wdi->interface_name, MAXPNAMELEN * 2, "winepulse: %s", name); -+ wdi->device_name = pa_xstrdup(device); -+ strcpy(wdi->interface_name, "winepulse: "); -+ MultiByteToWideChar(CP_ACP, 0, description, -1, wdi->caps.in.szPname, sizeof(wdi->caps.in.szPname)/sizeof(WCHAR)); -+ wdi->caps.in.szPname[sizeof(wdi->caps.in.szPname)/sizeof(WCHAR) - 1] = '\0'; -+ wdi->caps.in.wMid = MM_CREATIVE; -+ wdi->caps.in.wPid = MM_CREATIVE_SBP16_WAVEOUT; -+ wdi->caps.in.vDriverVersion = 0x0100; -+ wdi->caps.in.wChannels = v->channels == 1 ? 1 : 2; -+ wdi->caps.in.dwFormats = PULSE_ALL_FORMATS; -+ memset(&wdi->ds_desc, 0, sizeof(DSDRIVERDESC)); -+ memcpy(wdi->ds_desc.szDesc, description, min(sizeof(wdi->ds_desc.szDesc) - 1, strlen(description))); -+ memcpy(wdi->ds_desc.szDrvname, "winepulse.drv", 14); -+ wdi->ds_caps.dwMinSecondarySampleRate = DSBFREQUENCY_MIN; -+ wdi->ds_caps.dwMaxSecondarySampleRate = DSBFREQUENCY_MAX; -+ wdi->ds_caps.dwPrimaryBuffers = 1; -+ wdi->ds_caps.dwFlags = \ -+ DSCAPS_PRIMARYMONO | -+ DSCAPS_PRIMARYSTEREO | -+ DSCAPS_PRIMARY8BIT | -+ DSCAPS_PRIMARY16BIT | -+ DSCAPS_SECONDARYMONO | -+ DSCAPS_SECONDARYSTEREO | -+ DSCAPS_SECONDARY8BIT | -+ DSCAPS_SECONDARY16BIT | -+ DSCCAPS_MULTIPLECAPTURE | -+ DSCAPS_CERTIFIED | /* Useful? */ -+ DSCAPS_EMULDRIVER; /* Useful? */ -+ -+} -+ -+/************************************************************************** -+ * PULSE_AllocateWaveoutDevice [internal] -+ * -+ * Creates or adds a sink to the WOutDev array. -+ */ -+static void PULSE_AllocateWaveoutDevice(const char *name, const char *device, const char *description, const pa_cvolume *v) { -+ WINE_WAVEDEV *wdo; -+ int x; -+ -+ if (WOutDev) -+ wdo = HeapReAlloc(GetProcessHeap(), 0, WOutDev, sizeof(WINE_WAVEDEV) * (PULSE_WodNumDevs + 1)); -+ else -+ wdo = HeapAlloc(GetProcessHeap(), 0, sizeof(WINE_WAVEDEV)); -+ -+ if (!wdo) return; -+ -+ WOutDev = wdo; -+ wdo = &WOutDev[PULSE_WodNumDevs++]; -+ memset(wdo, 0, sizeof(WINE_WAVEDEV)); -+ -+ wdo->device_name = pa_xstrdup(device); -+ wdo->volume.channels = v->channels; -+ for (x = 0; x < v->channels; x++) wdo->volume.values[x] = v->values[x]; -+ snprintf(wdo->interface_name, MAXPNAMELEN * 2, "winepulse: %s", name); -+ MultiByteToWideChar(CP_ACP, 0, description, -1, wdo->caps.out.szPname, sizeof(wdo->caps.out.szPname)/sizeof(WCHAR)); -+ wdo->caps.out.szPname[sizeof(wdo->caps.out.szPname)/sizeof(WCHAR) - 1] = '\0'; -+ wdo->caps.out.wMid = MM_CREATIVE; -+ wdo->caps.out.wPid = MM_CREATIVE_SBP16_WAVEOUT; -+ wdo->caps.out.vDriverVersion = 0x0100; -+ wdo->caps.out.dwSupport = WAVECAPS_VOLUME | WAVECAPS_SAMPLEACCURATE | WAVECAPS_DIRECTSOUND; -+ if (v->channels >= 2) { -+ wdo->caps.out.wChannels = 2; -+ wdo->caps.out.dwSupport |= WAVECAPS_LRVOLUME; -+ } else -+ wdo->caps.out.wChannels = 1; -+ wdo->caps.out.dwFormats = PULSE_ALL_FORMATS; -+ memset(&wdo->ds_desc, 0, sizeof(DSDRIVERDESC)); -+ memcpy(wdo->ds_desc.szDesc, description, min(sizeof(wdo->ds_desc.szDesc) - 1, strlen(description))); -+ memcpy(wdo->ds_desc.szDrvname, "winepulse.drv", 14); -+ wdo->ds_caps.dwMinSecondarySampleRate = DSBFREQUENCY_MIN; -+ wdo->ds_caps.dwMaxSecondarySampleRate = DSBFREQUENCY_MAX; -+ wdo->ds_caps.dwPrimaryBuffers = 1; -+ wdo->ds_caps.dwFlags = \ -+ DSCAPS_PRIMARYMONO | -+ DSCAPS_PRIMARYSTEREO | -+ DSCAPS_PRIMARY8BIT | -+ DSCAPS_PRIMARY16BIT | -+ DSCAPS_SECONDARYMONO | -+ DSCAPS_SECONDARYSTEREO | -+ DSCAPS_SECONDARY8BIT | -+ DSCAPS_SECONDARY16BIT | -+ DSCAPS_CERTIFIED; -+} -+ -+/************************************************************************** -+ * PULSE_SourceInfoCallback [internal] -+ */ -+static void PULSE_SourceInfoCallback(pa_context *c, const pa_source_info *i, int eol, void *userdata) { -+ -+ if (!eol && i) -+ PULSE_AllocateWaveinDevice(i->name, i->name, i->description, &i->volume); -+ -+ pa_threaded_mainloop_signal(PULSE_ml, 0); -+} -+ -+/************************************************************************** -+ * PULSE_SinkInfoCallback [internal] -+ */ -+static void PULSE_SinkInfoCallback(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { -+ -+ if (!eol && i) -+ PULSE_AllocateWaveoutDevice(i->name, i->name, i->description, &i->volume); -+ -+ pa_threaded_mainloop_signal(PULSE_ml, 0); -+} -+ -+/************************************************************************** -+ * PULSE_ContextNotifyCallback [internal] -+ */ -+static void PULSE_ContextNotifyCallback(pa_context *c, void *userdata) { -+ pa_threaded_mainloop_signal(PULSE_ml, 0); -+} -+ -+/************************************************************************** -+ * PULSE_WaveClose [internal] -+ * -+ * Disconnect from the server, deallocated the WaveIn/WaveOut devices, stop and -+ * free the mainloop. -+ */ -+ -+static LONG PULSE_WaveClose(void) { -+ int x; -+ TRACE("()\n"); -+ if (!PULSE_ml) return DRV_FAILURE; -+ -+ pa_threaded_mainloop_lock(PULSE_ml); -+ /* device_name is allocated with pa_xstrdup, free with pa_xfree */ -+ for (x = 0; x < PULSE_WodNumDevs; x++) pa_xfree(WOutDev[x].device_name); -+ for (x = 0; x < PULSE_WidNumDevs; x++) pa_xfree(WInDev[x].device_name); -+ HeapFree(GetProcessHeap(), 0, WOutDev); -+ HeapFree(GetProcessHeap(), 0, WInDev); -+ if (PULSE_context) { -+ PULSE_WaitForOperation(pa_context_drain(PULSE_context, PULSE_ContextNotifyCallback, NULL)); -+ pa_context_disconnect(PULSE_context); -+ pa_context_unref(PULSE_context); -+ PULSE_context = NULL; -+ } -+ -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ pa_threaded_mainloop_stop(PULSE_ml); -+ pa_threaded_mainloop_free(PULSE_ml); -+ PULSE_ml = NULL; -+ -+ return DRV_SUCCESS; -+} -+ -+/************************************************************************** -+ * PULSE_WaveInit [internal] -+ * -+ * Connects to the pulseaudio server, tries to discover sinks and sources and -+ * allocates the WaveIn/WaveOut devices. -+ */ -+static LONG PULSE_WaveInit(void) { -+ char *app_name; -+ char path[PATH_MAX]; -+ char *offset = NULL; -+ int x = 0; -+ pa_cvolume fake_cvolume; -+ -+ 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; -+ } -+ -+ /* Application name giving to pulse should be unique to the binary so that -+ * pulse-*-restore can be useful */ -+ -+ /* Get binary path, and remove path a-la strrchr */ -+ if (GetModuleFileNameA(NULL, path, PATH_MAX)) -+ offset = strrchr(path, '\\'); -+ -+ if (offset && ++offset && offset < path + PATH_MAX) { -+ app_name = pa_xmalloc(strlen(offset) + 8); -+ snprintf(app_name, strlen(offset) + 8, "WINE [%s]", offset); -+ } else -+ app_name = pa_xstrdup("WINE Application"); -+ -+ TRACE("App name is \"%s\"\n", app_name); -+ -+ pa_threaded_mainloop_start(PULSE_ml); -+ PULSE_context = pa_context_new(pa_threaded_mainloop_get_api(PULSE_ml), app_name); -+ assert(PULSE_context); -+ pa_xfree(app_name); -+ -+ pa_context_set_state_callback(PULSE_context, PULSE_ContextStateCallback, NULL); -+ -+ pa_threaded_mainloop_lock(PULSE_ml); -+ -+ TRACE("libpulse protocol version: %u. API Version %u\n", pa_context_get_protocol_version(PULSE_context), PA_API_VERSION); -+ TRACE("Attempting to connect to pulseaudio server.\n"); -+ if (pa_context_connect(PULSE_context, NULL, 0, NULL) < 0) -+ goto fail; -+ -+ /* Wait for connection */ -+ for (;;) { -+ pa_context_state_t state = pa_context_get_state(PULSE_context); -+ -+ if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) -+ goto fail; -+ -+ if (state == PA_CONTEXT_READY) -+ break; -+ -+ pa_threaded_mainloop_wait(PULSE_ml); -+ } -+ -+ x = pa_context_get_server_protocol_version(PULSE_context); -+ TRACE("Connected to server %s with protocol version: %i.\n", pa_context_get_server(PULSE_context), x); -+ if (x < 14) -+ WARN("Server is old, expect poor latency or buggy-ness!\n"); -+ -+ fake_cvolume.channels = 2; -+ pa_cvolume_reset(&fake_cvolume, 2); -+ /* FIXME Translations? */ -+ PULSE_AllocateWaveoutDevice("default", NULL, "Default", &fake_cvolume); -+ PULSE_AllocateWaveinDevice("default", NULL, "Default", &fake_cvolume); -+ PULSE_WaitForOperation(pa_context_get_sink_info_list(PULSE_context, PULSE_SinkInfoCallback, &PULSE_WodNumDevs)); -+ PULSE_WaitForOperation(pa_context_get_source_info_list(PULSE_context, PULSE_SourceInfoCallback, &PULSE_WidNumDevs)); -+ TRACE("Found %u output and %u input device(s).\n", PULSE_WodNumDevs - 1, PULSE_WidNumDevs - 1); -+ -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ -+ return DRV_SUCCESS; -+ -+fail: -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ ERR("Failed to connect to server\n"); -+ return DRV_FAILURE; -+} -+ -+#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: return PULSE_WaveInit(); -+ 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/wavein.c b/dlls/winepulse.drv/wavein.c -new file mode 100644 -index 0000000..1534d6e ---- /dev/null -+++ b/dlls/winepulse.drv/wavein.c -@@ -0,0 +1,572 @@ -+/* -+ * Wine Driver for PulseAudio - WaveIn Functionality -+ * http://pulseaudio.org/ -+ * -+ * Copyright 2009 Arthur Taylor -+ * -+ * Contains code from other wine multimedia 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 "windef.h" -+#include "winbase.h" -+#include "wingdi.h" -+#include "winuser.h" -+#include "winnls.h" -+#include "mmddk.h" -+ -+#include -+ -+#include "wine/debug.h" -+ -+WINE_DEFAULT_DEBUG_CHANNEL(wave); -+ -+#if HAVE_PULSEAUDIO -+ -+/*======================================================================* -+ * WAVE IN specific PulseAudio Callbacks * -+ *======================================================================*/ -+ -+/************************************************************************** -+* widNotifyClient [internal] -+*/ -+static DWORD widNotifyClient(WINE_WAVEINST* wwi, WORD wMsg, DWORD dwParam1, DWORD dwParam2) { -+ TRACE("wMsg = 0x%04x dwParm1 = %04X dwParam2 = %04X\n", wMsg, dwParam1, dwParam2); -+ -+ switch (wMsg) { -+ case WIM_OPEN: -+ case WIM_CLOSE: -+ case WIM_DATA: -+ if (wwi->wFlags != DCB_NULL && -+ !DriverCallback(wwi->waveDesc.dwCallback, wwi->wFlags, (HDRVR)wwi->waveDesc.hWave, -+ wMsg, wwi->waveDesc.dwInstance, dwParam1, dwParam2)) { -+ WARN("can't notify client !\n"); -+ return MMSYSERR_ERROR; -+ } -+ break; -+ default: -+ FIXME("Unknown callback message %u\n", wMsg); -+ return MMSYSERR_INVALPARAM; -+ } -+ return MMSYSERR_NOERROR; -+} -+ -+/************************************************************************** -+ * widRecorder_CopyData [internal] -+ * -+ * Copys data from the fragments pulse returns to queued buffers. -+ */ -+static void widRecorder_CopyData(WINE_WAVEINST *wwi) { -+ LPWAVEHDR lpWaveHdr = wwi->lpQueuePtr; -+ size_t size; -+ while (lpWaveHdr && wwi->state == WINE_WS_PLAYING && wwi->buffer) { -+ size = min(wwi->buffer_length - wwi->buffer_read_offset, lpWaveHdr->dwBufferLength - lpWaveHdr->dwBytesRecorded); -+ if (size == 0) ERR("Size is 0! buffer is full but not freed?\n"); -+ memcpy(lpWaveHdr->lpData + lpWaveHdr->dwBytesRecorded, (PBYTE)wwi->buffer + wwi->buffer_read_offset, size); -+ wwi->buffer_read_offset += size; -+ if (wwi->buffer_read_offset == wwi->buffer_length) { -+ pa_threaded_mainloop_lock(PULSE_ml); -+ pa_stream_drop(wwi->stream); -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ wwi->buffer = NULL; -+ wwi->buffer_length = 0; -+ wwi->buffer_read_offset = 0; -+ } -+ lpWaveHdr->dwBytesRecorded += size; -+ if (lpWaveHdr->dwBytesRecorded == lpWaveHdr->dwBufferLength) { -+ wwi->lpQueuePtr = lpWaveHdr->lpNext; -+ lpWaveHdr->dwFlags &= ~WHDR_INQUEUE; -+ lpWaveHdr->dwFlags |= WHDR_DONE; -+ widNotifyClient(wwi, WIM_DATA, (DWORD)lpWaveHdr, 0); -+ lpWaveHdr = wwi->lpQueuePtr; -+ } -+ } -+} -+ -+/************************************************************************** -+* widRecorder_NextFragment [internal] -+* -+* Switches the current fragment to the next based upon a message from the -+* server. -+*/ -+static void widRecorder_NextFragment(WINE_WAVEINST *wwi, size_t sizer) { -+ LPWAVEHDR lpWaveHdr = wwi->lpQueuePtr; -+ size_t request = 0; -+ -+ for (;lpWaveHdr; lpWaveHdr = lpWaveHdr->lpNext) -+ request += lpWaveHdr->dwBufferLength; -+ -+ lpWaveHdr = wwi->lpQueuePtr; -+ pa_threaded_mainloop_lock(PULSE_ml); -+ pa_stream_peek(wwi->stream, &wwi->buffer, &request); -+ wwi->buffer_length = request; -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ -+ widRecorder_CopyData(wwi); -+} -+ -+/************************************************************************** -+ * widRecorder [internal] -+ */ -+static DWORD CALLBACK widRecorder(LPVOID lpParam) { -+ WINE_WAVEINST *wwi = (WINE_WAVEINST*)lpParam; -+ LPWAVEHDR lpWaveHdr; -+ enum win_wm_message msg; -+ DWORD param; -+ HANDLE ev; -+ -+ wwi->state = WINE_WS_STOPPED; -+ SetEvent(wwi->hStartUpEvent); -+ -+ for (;;) { -+ PULSE_WaitRingMessage(&wwi->msgRing, INFINITE); -+ while (PULSE_RetrieveRingMessage(&wwi->msgRing, &msg, ¶m, &ev)) { -+ TRACE("Received %s %x\n", PULSE_getCmdString(msg), param); -+ -+ switch (msg) { -+ case WINE_WM_FEED: -+ SetEvent(ev); -+ if (wwi->state == WINE_WS_PLAYING) -+ widRecorder_NextFragment(wwi, param); -+ break; -+ case WINE_WM_STARTING: -+ wwi->state = WINE_WS_PLAYING; -+ pa_threaded_mainloop_lock(PULSE_ml); -+ PULSE_WaitForOperation(pa_stream_cork(wwi->stream, 0, PULSE_StreamSuccessCallback, NULL)); -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ SetEvent(ev); -+ break; -+ case WINE_WM_HEADER: -+ lpWaveHdr = (LPWAVEHDR)param; -+ lpWaveHdr->lpNext = 0; -+ -+ /* insert buffer at the end of queue */ -+ { -+ LPWAVEHDR* wh; -+ for (wh = &(wwi->lpQueuePtr); *wh; wh = &((*wh)->lpNext)); -+ *wh = lpWaveHdr; -+ } -+ break; -+ case WINE_WM_STOPPING: -+ if (wwi->state != WINE_WS_STOPPED) { -+ pa_threaded_mainloop_lock(PULSE_ml); -+ PULSE_WaitForOperation(pa_stream_cork(wwi->stream, 1, PULSE_StreamSuccessCallback, NULL)); -+ pa_stream_drop(wwi->stream); -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ -+ /* return current buffer to app */ -+ lpWaveHdr = wwi->lpQueuePtr; -+ if (lpWaveHdr) { -+ LPWAVEHDR lpNext = lpWaveHdr->lpNext; -+ TRACE("stop %p %p\n", lpWaveHdr, lpWaveHdr->lpNext); -+ lpWaveHdr->dwFlags &= ~WHDR_INQUEUE; -+ lpWaveHdr->dwFlags |= WHDR_DONE; -+ wwi->lpQueuePtr = lpNext; -+ widNotifyClient(wwi, WIM_DATA, (DWORD)lpWaveHdr, 0); -+ } -+ } -+ wwi->state = WINE_WS_STOPPED; -+ SetEvent(ev); -+ break; -+ case WINE_WM_RESETTING: -+ if (wwi->state != WINE_WS_STOPPED) { -+ pa_threaded_mainloop_lock(PULSE_ml); -+ pa_stream_drop(wwi->stream); -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ } -+ wwi->state = WINE_WS_STOPPED; -+ -+ /* return all buffers to the app */ -+ for (lpWaveHdr = wwi->lpQueuePtr; lpWaveHdr; lpWaveHdr = wwi->lpQueuePtr) { -+ TRACE("reset %p %p\n", lpWaveHdr, lpWaveHdr->lpNext); -+ lpWaveHdr->dwFlags &= ~WHDR_INQUEUE; -+ lpWaveHdr->dwFlags |= WHDR_DONE; -+ wwi->lpQueuePtr = lpWaveHdr->lpNext; -+ widNotifyClient(wwi, WIM_DATA, (DWORD)lpWaveHdr, 0); -+ } -+ wwi->lpQueuePtr = NULL; -+ SetEvent(ev); -+ break; -+ case WINE_WM_XRUN: -+ pa_threaded_mainloop_lock(PULSE_ml); -+ pa_stream_drop(wwi->stream); -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ wwi->buffer_read_offset = 0; -+ break; -+ case WINE_WM_CLOSING: -+ wwi->hThread = 0; -+ if ((DWORD)param == 1) { -+ wwi->state = WINE_WS_FAILED; -+ SetEvent(ev); -+ PULSE_DestroyRingMessage(&wwi->msgRing); -+ widNotifyClient(wwi, WIM_CLOSE, 0L, 0L); -+ wwi->lpPlayPtr = wwi->lpQueuePtr = NULL; -+ pa_threaded_mainloop_lock(PULSE_ml); -+ pa_stream_disconnect(wwi->stream); -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ TRACE("Thread exiting because of failure.\n"); -+ ExitThread(1); -+ } -+ wwi->state = WINE_WS_CLOSED; -+ SetEvent(ev); -+ ExitThread(0); -+ /* shouldn't go here */ -+ default: -+ FIXME("unknown message %d\n", msg); -+ break; -+ } -+ } -+ } /* for (;;) */ -+} -+ -+/************************************************************************** -+ * widOpen [internal] -+ */ -+static DWORD widOpen(WORD wDevID, LPDWORD lpdwUser, LPWAVEOPENDESC lpDesc, DWORD dwFlags) { -+ WINE_WAVEDEV *wdi; -+ WINE_WAVEINST *wwi = NULL; -+ DWORD ret = MMSYSERR_NOERROR; -+ -+ TRACE("(%u, %p, %08X);\n", wDevID, lpDesc, dwFlags); -+ if (lpDesc == NULL) { -+ WARN("Invalid Parameter !\n"); -+ return MMSYSERR_INVALPARAM; -+ } -+ -+ if (wDevID >= PULSE_WidNumDevs) { -+ TRACE("Asked for device %d, but only %d known!\n", wDevID, PULSE_WidNumDevs); -+ return MMSYSERR_BADDEVICEID; -+ } -+ wdi = &WInDev[wDevID]; -+ -+ wwi = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WINE_WAVEINST)); -+ if (!wwi) return MMSYSERR_NOMEM; -+ *lpdwUser = (DWORD)wwi; -+ -+ /* check to see if format is supported and make pa_sample_spec struct */ -+ if (!PULSE_SetupFormat(lpDesc->lpFormat, &wwi->sample_spec)) { -+ WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n", -+ lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels, -+ lpDesc->lpFormat->nSamplesPerSec); -+ ret = WAVERR_BADFORMAT; -+ goto exit; -+ } -+ -+ if (TRACE_ON(wave)) { -+ char t[PA_SAMPLE_SPEC_SNPRINT_MAX]; -+ pa_sample_spec_snprint(t, sizeof(t), &wwi->sample_spec); -+ TRACE("Sample spec '%s'\n", t); -+ } -+ -+ if (dwFlags & WAVE_FORMAT_QUERY) { -+ TRACE("Query format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n", -+ lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels, -+ lpDesc->lpFormat->nSamplesPerSec); -+ ret = MMSYSERR_NOERROR; -+ goto exit; -+ } -+ -+ wwi->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK); -+ wwi->waveDesc = *lpDesc; -+ PULSE_InitRingMessage(&wwi->msgRing); -+ -+ wwi->stream = pa_stream_new(PULSE_context, "WaveIn", &wwi->sample_spec, NULL); -+ if (!wwi->stream) { -+ ret = WAVERR_BADFORMAT; -+ goto exit; -+ } -+ -+ pa_stream_set_state_callback(wwi->stream, PULSE_StreamStateCallback, wwi); -+ pa_stream_set_read_callback(wwi->stream, PULSE_StreamRequestCallback, wwi); -+ -+ pa_threaded_mainloop_lock(PULSE_ml); -+ TRACE("Asking to open %s for recording.\n", wdi->device_name); -+ pa_stream_connect_record(wwi->stream, wdi->device_name, NULL, -+ PA_STREAM_START_CORKED | -+ PA_STREAM_AUTO_TIMING_UPDATE | -+ PA_STREAM_INTERPOLATE_TIMING); -+ -+ for (;;) { -+ pa_context_state_t cstate = pa_context_get_state(PULSE_context); -+ pa_stream_state_t sstate = pa_stream_get_state(wwi->stream); -+ -+ if (cstate == PA_CONTEXT_FAILED || cstate == PA_CONTEXT_TERMINATED || -+ sstate == PA_STREAM_FAILED || sstate == PA_STREAM_TERMINATED) { -+ ERR("Failed to connect context object: %s\n", pa_strerror(pa_context_errno(PULSE_context))); -+ ret = MMSYSERR_NODRIVER; -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ goto exit; -+ } -+ -+ if (sstate == PA_STREAM_READY) -+ break; -+ -+ pa_threaded_mainloop_wait(PULSE_ml); -+ } -+ TRACE("(%p)->stream connected for recording.\n", wwi); -+ -+ PULSE_WaitForOperation(pa_stream_update_timing_info(wwi->stream, PULSE_StreamSuccessCallback, wwi)); -+ -+ wwi->timing_info = pa_stream_get_timing_info(wwi->stream); -+ assert(wwi->timing_info); -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ -+ wwi->hStartUpEvent = CreateEventW(NULL, FALSE, FALSE, NULL); -+ wwi->hThread = CreateThread(NULL, 0, widRecorder, (LPVOID)wwi, 0, &(wwi->dwThreadID)); -+ if (wwi->hThread) -+ SetThreadPriority(wwi->hThread, THREAD_PRIORITY_TIME_CRITICAL); -+ else { -+ ERR("Thread creation for the widRecorder failed!\n"); -+ ret = MMSYSERR_NOMEM; -+ goto exit; -+ } -+ WaitForSingleObject(wwi->hStartUpEvent, INFINITE); -+ CloseHandle(wwi->hStartUpEvent); -+ wwi->hStartUpEvent = INVALID_HANDLE_VALUE; -+ -+ return widNotifyClient(wwi, WIM_OPEN, 0L, 0L); -+ -+exit: -+ if (!wwi) -+ return ret; -+ -+ if (wwi->hStartUpEvent != INVALID_HANDLE_VALUE) -+ CloseHandle(wwi->hStartUpEvent); -+ -+ if (wwi->msgRing.ring_buffer_size > 0) -+ PULSE_DestroyRingMessage(&wwi->msgRing); -+ -+ if (wwi->stream) { -+ if (pa_stream_get_state(wwi->stream) == PA_STREAM_READY) -+ pa_stream_disconnect(wwi->stream); -+ pa_stream_unref(wwi->stream); -+ } -+ HeapFree(GetProcessHeap(), 0, wwi); -+ -+ return ret; -+} -+/************************************************************************** -+ * widClose [internal] -+ */ -+static DWORD widClose(WORD wDevID, WINE_WAVEINST *wwi) { -+ DWORD ret; -+ -+ TRACE("(%u, %p);\n", wDevID, wwi); -+ if (wDevID >= PULSE_WidNumDevs) { -+ WARN("Asked for device %d, but only %d known!\n", wDevID, PULSE_WodNumDevs); -+ return MMSYSERR_INVALHANDLE; -+ } else if (!wwi) { -+ WARN("Stream instance invalid.\n"); -+ return MMSYSERR_INVALHANDLE; -+ } -+ -+ if (wwi->state != WINE_WS_FAILED) { -+ if (wwi->lpQueuePtr) { -+ WARN("buffers recording recording !\n"); -+ return WAVERR_STILLPLAYING; -+ } -+ -+ pa_threaded_mainloop_lock(PULSE_ml); -+ if (pa_stream_get_state(wwi->stream) == PA_STREAM_READY) -+ pa_stream_drop(wwi->stream); -+ pa_stream_disconnect(wwi->stream); -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ -+ if (wwi->hThread != INVALID_HANDLE_VALUE) -+ PULSE_AddRingMessage(&wwi->msgRing, WINE_WM_CLOSING, 0, TRUE); -+ -+ PULSE_DestroyRingMessage(&wwi->msgRing); -+ } -+ ret = widNotifyClient(wwi, WIM_CLOSE, 0L, 0L); -+ -+ pa_stream_unref(wwi->stream); -+ TRACE("Deallocating record instance.\n"); -+ HeapFree(GetProcessHeap(), 0, wwi); -+ return ret; -+} -+ -+/************************************************************************** -+ * widAddBuffer [internal] -+ * -+ */ -+static DWORD widAddBuffer(WINE_WAVEINST* wwi, LPWAVEHDR lpWaveHdr, DWORD dwSize) { -+ TRACE("(%p, %p, %08X);\n", wwi, lpWaveHdr, dwSize); -+ -+ if (!wwi || wwi->state == WINE_WS_FAILED) { -+ WARN("Stream instance invalid.\n"); -+ return MMSYSERR_INVALHANDLE; -+ } -+ -+ if (lpWaveHdr->lpData == NULL || !(lpWaveHdr->dwFlags & WHDR_PREPARED)) -+ return WAVERR_UNPREPARED; -+ -+ if (lpWaveHdr->dwFlags & WHDR_INQUEUE) -+ return WAVERR_STILLPLAYING; -+ -+ lpWaveHdr->dwFlags &= ~WHDR_DONE; -+ lpWaveHdr->dwFlags |= WHDR_INQUEUE; -+ lpWaveHdr->dwBytesRecorded = 0; -+ lpWaveHdr->lpNext = 0; -+ -+ PULSE_AddRingMessage(&wwi->msgRing, WINE_WM_HEADER, (DWORD)lpWaveHdr, FALSE); -+ -+ return MMSYSERR_NOERROR; -+} -+ -+/************************************************************************** -+ * widRecorderMessage [internal] -+ */ -+static DWORD widRecorderMessage(WINE_WAVEINST *wwi, enum win_wm_message message) { -+ if (!wwi || wwi->state == WINE_WS_FAILED) { -+ WARN("Stream instance invalid.\n"); -+ return MMSYSERR_INVALHANDLE; -+ } -+ -+ PULSE_AddRingMessage(&wwi->msgRing, message, 0, TRUE); -+ return MMSYSERR_NOERROR; -+} -+ -+/************************************************************************** -+ * widGetPosition [internal] -+ */ -+static DWORD widGetPosition(WINE_WAVEINST *wwi, LPMMTIME lpTime, DWORD uSize) { -+ if (!wwi || wwi->state == WINE_WS_FAILED) { -+ WARN("Stream instance invalid.\n"); -+ return MMSYSERR_INVALHANDLE; -+ } -+ -+ if (lpTime == NULL) return MMSYSERR_INVALPARAM; -+ -+ pa_threaded_mainloop_lock(PULSE_ml); -+ PULSE_GetMMTime(wwi->timing_info, &wwi->sample_spec, (size_t)-1, lpTime); -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ -+ return MMSYSERR_NOERROR; -+} -+ -+/************************************************************************** -+ * widGetDevCaps [internal] -+ */ -+static DWORD widGetDevCaps(DWORD wDevID, LPWAVEINCAPSW lpCaps, DWORD dwSize) { -+ TRACE("(%u, %p, %u);\n", wDevID, lpCaps, dwSize); -+ -+ if (lpCaps == NULL) return MMSYSERR_NOTENABLED; -+ -+ if (wDevID >= PULSE_WidNumDevs) { -+ TRACE("Asked for device %d, but only %d known!\n", wDevID, PULSE_WidNumDevs); -+ return MMSYSERR_INVALHANDLE; -+ } -+ -+ memcpy(lpCaps, &(WInDev[wDevID].caps.in), min(dwSize, sizeof(*lpCaps))); -+ return MMSYSERR_NOERROR; -+} -+ -+/************************************************************************** -+ * widGetNumDevs [internal] -+ * Context-sanity check here, as if we respond with 0, WINE will move on -+ * to the next wavein driver. -+ */ -+static DWORD widGetNumDevs() { -+ if (pa_context_get_state(PULSE_context) != PA_CONTEXT_READY) -+ return 0; -+ -+ return PULSE_WidNumDevs; -+} -+ -+/************************************************************************** -+ * widDevInterfaceSize [internal] -+ */ -+static DWORD widDevInterfaceSize(UINT wDevID, LPDWORD dwParam1) { -+ TRACE("(%u, %p)\n", wDevID, dwParam1); -+ -+ *dwParam1 = MultiByteToWideChar(CP_UNIXCP, 0, WInDev[wDevID].interface_name, -1, -+ NULL, 0 ) * sizeof(WCHAR); -+ return MMSYSERR_NOERROR; -+} -+ -+/************************************************************************** -+ * widDevInterface [internal] -+ */ -+static DWORD widDevInterface(UINT wDevID, PWCHAR dwParam1, DWORD dwParam2) { -+ if (dwParam2 >= MultiByteToWideChar(CP_UNIXCP, 0, WInDev[wDevID].interface_name, -1, -+ NULL, 0 ) * sizeof(WCHAR)) -+ { -+ MultiByteToWideChar(CP_UNIXCP, 0, WInDev[wDevID].interface_name, -1, -+ dwParam1, dwParam2 / sizeof(WCHAR)); -+ return MMSYSERR_NOERROR; -+ } -+ return MMSYSERR_INVALPARAM; -+} -+ -+/************************************************************************** -+ * widDsDesc [internal] -+ */ -+DWORD widDsDesc(UINT wDevID, PDSDRIVERDESC desc) -+{ -+ *desc = WInDev[wDevID].ds_desc; -+ return MMSYSERR_NOERROR; -+} -+ -+/************************************************************************** -+ * widMessage (WINEPULSE.@) -+ */ -+DWORD WINAPI PULSE_widMessage(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 WIDM_OPEN: return widOpen (wDevID, (LPDWORD)dwUser, (LPWAVEOPENDESC)dwParam1, dwParam2); -+ case WIDM_CLOSE: return widClose (wDevID, (WINE_WAVEINST*)dwUser); -+ case WIDM_ADDBUFFER: return widAddBuffer ((WINE_WAVEINST*)dwUser, (LPWAVEHDR)dwParam1, dwParam2); -+ case WIDM_PREPARE: return MMSYSERR_NOTSUPPORTED; -+ case WIDM_UNPREPARE: return MMSYSERR_NOTSUPPORTED; -+ case WIDM_GETDEVCAPS: return widGetDevCaps (wDevID, (LPWAVEINCAPSW)dwParam1, dwParam2); -+ case WIDM_GETNUMDEVS: return widGetNumDevs (); -+ case WIDM_GETPOS: return widGetPosition ((WINE_WAVEINST*)dwUser, (LPMMTIME)dwParam1, dwParam2); -+ case WIDM_RESET: return widRecorderMessage((WINE_WAVEINST*)dwUser, WINE_WM_RESETTING); -+ case WIDM_START: return widRecorderMessage((WINE_WAVEINST*)dwUser, WINE_WM_STARTING); -+ case WIDM_STOP: return widRecorderMessage((WINE_WAVEINST*)dwUser, WINE_WM_STOPPING); -+ case DRV_QUERYDEVICEINTERFACESIZE: return widDevInterfaceSize (wDevID, (LPDWORD)dwParam1); -+ case DRV_QUERYDEVICEINTERFACE: return widDevInterface (wDevID, (PWCHAR)dwParam1, dwParam2); -+ case DRV_QUERYDSOUNDIFACE: return MMSYSERR_NOTSUPPORTED; /* Use emulation, as there is no advantage */ -+ case DRV_QUERYDSOUNDDESC: return widDsDesc (wDevID, (PDSDRIVERDESC)dwParam1); -+ default: -+ FIXME("unknown message %d!\n", wMsg); -+ } -+ return MMSYSERR_NOTSUPPORTED; -+} -+ -+#else /* HAVE_PULSEAUDIO */ -+ -+/************************************************************************** -+ * widMessage (WINEPULSE.@) -+ */ -+DWORD WINAPI PULSE_widMessage(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/waveout.c b/dlls/winepulse.drv/waveout.c -new file mode 100644 -index 0000000..4539103 ---- /dev/null -+++ b/dlls/winepulse.drv/waveout.c -@@ -0,0 +1,1092 @@ -+/* -+ * Wine Driver for PulseAudio - WaveOut Functionality -+ * http://pulseaudio.org/ -+ * -+ * Copyright 2009 Arthur Taylor -+ * -+ * Contains code from other wine multimedia 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 "windef.h" -+#include "winbase.h" -+#include "wingdi.h" -+#include "winuser.h" -+#include "winnls.h" -+#include "winerror.h" -+#include "mmddk.h" -+#include "mmreg.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 | -+ * +---------+-------------+---------------+---------------------------------+ -+ */ -+ -+/* -+ * - It is currently unknown if pausing in a loop works the same as expected. -+ */ -+ -+/*======================================================================* -+ * WAVE OUT specific PulseAudio Callbacks * -+ *======================================================================*/ -+ -+/************************************************************************** -+ * WAVEOUT_StreamStartedCallback [internal] -+ * -+ * Called by the pulse mainloop whenever stream playback resumes after an -+ * underflow or an initial start. Requires libpulse >= 0.9.11 -+ */ -+#if PA_API_VERSION >= 12 -+static void WAVEOUT_StreamStartedCallback(pa_stream *s, void *userdata) { -+ WINE_WAVEINST *wwo = (WINE_WAVEINST*)userdata; -+ assert(s && wwo); -+ -+ TRACE("Audio flowing.\n"); -+ -+ if (wwo->buffer_attr.tlength == 0) -+ wwo->buffer_attr.tlength = (uint32_t) -1; -+ -+ if (wwo->hThread != INVALID_HANDLE_VALUE && wwo->msgRing.ring_buffer_size) { -+ PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_STARTING, 0, FALSE); -+ } -+} -+#endif -+/************************************************************************** -+ * WAVEOUT_StreamTimingInfoUpdateCallback [internal] -+ * -+ * Called by the pulse mainloop whenever the timing info gets updated, we -+ * use this to send the started signal */ -+static void WAVEOUT_StreamTimingInfoUpdateCallback(pa_stream *s, void *userdata) { -+ WINE_WAVEINST *wwo = (WINE_WAVEINST*)userdata; -+ assert(s && wwo); -+ -+ if (!wwo->is_releasing && wwo->timing_info && wwo->timing_info->playing) { -+ TRACE("Audio flowing.\n"); -+ -+ if (wwo->buffer_attr.tlength == 0) -+ wwo->buffer_attr.tlength = (uint32_t) -1; -+ -+ if (wwo->hThread != INVALID_HANDLE_VALUE && wwo->msgRing.ring_buffer_size) -+ PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_STARTING, 0, FALSE); -+ } -+} -+ -+ -+static void WAVEOUT_SinkInputInfoCallback(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { -+ WINE_WAVEINST* wwo = (WINE_WAVEINST*)userdata; -+ if (!eol && i) { -+ for (wwo->volume.channels = 0; wwo->volume.channels != i->volume.channels; wwo->volume.channels++) -+ wwo->volume.values[wwo->volume.channels] = i->volume.values[wwo->volume.channels]; -+ pa_threaded_mainloop_signal(PULSE_ml, 0); -+ } -+} -+ -+/*======================================================================* -+ * "Low level" WAVE OUT implementation * -+ *======================================================================*/ -+ -+/************************************************************************** -+ * wodPlayer_NotifyClient [internal] -+ */ -+static DWORD wodPlayer_NotifyClient(WINE_WAVEINST* wwi, WORD wMsg, DWORD dwParam1, DWORD dwParam2) { -+ TRACE("wMsg = 0x%04x dwParm1 = %04X dwParam2 = %04X\n", wMsg, dwParam1, dwParam2); -+ -+ switch (wMsg) { -+ case WOM_OPEN: -+ case WOM_CLOSE: -+ case WOM_DONE: -+ if (wwi->wFlags != DCB_NULL && -+ !DriverCallback(wwi->waveDesc.dwCallback, wwi->wFlags, (HDRVR)wwi->waveDesc.hWave, -+ wMsg, wwi->waveDesc.dwInstance, dwParam1, dwParam2)) { -+ WARN("can't notify client !\n"); -+ return MMSYSERR_ERROR; -+ } -+ break; -+ default: -+ FIXME("Unknown callback message %u\n", wMsg); -+ return MMSYSERR_INVALPARAM; -+ } -+ return MMSYSERR_NOERROR; -+} -+ -+/************************************************************************** -+ * wodPlayer_BeginWaveHdr [internal] -+ * -+ * Makes the specified lpWaveHdr the currently playing wave header. -+ * If the specified wave header is a begin loop and we're not already in -+ * a loop, setup the loop. -+ */ -+static void wodPlayer_BeginWaveHdr(WINE_WAVEINST* wwo, LPWAVEHDR lpWaveHdr) { -+ wwo->lpPlayPtr = lpWaveHdr; -+ -+ if (!lpWaveHdr) return; -+ -+ if (lpWaveHdr->dwFlags & WHDR_BEGINLOOP) { -+ if (wwo->lpLoopPtr) { -+ WARN("Already in a loop. Discarding loop on this header (%p)\n", lpWaveHdr); -+ } else { -+ TRACE("Starting loop (%dx) with %p\n", lpWaveHdr->dwLoops, lpWaveHdr); -+ wwo->lpLoopPtr = lpWaveHdr; -+ /* Windows does not touch WAVEHDR.dwLoops, -+ * so we need to make an internal copy */ -+ wwo->dwLoops = lpWaveHdr->dwLoops; -+ } -+ } -+ wwo->dwPartialOffset = 0; -+} -+ -+/************************************************************************** -+ * wodPlayer_PlayPtrNext [internal] -+ * -+ * Advance the play pointer to the next waveheader, looping if required. -+ */ -+static LPWAVEHDR wodPlayer_PlayPtrNext(WINE_WAVEINST* wwo) { -+ LPWAVEHDR lpWaveHdr = wwo->lpPlayPtr; -+ -+ wwo->dwPartialOffset = 0; -+ if ((lpWaveHdr->dwFlags & WHDR_ENDLOOP) && wwo->lpLoopPtr) { -+ /* We're at the end of a loop, loop if required */ -+ if (--wwo->dwLoops > 0) { -+ wwo->lpPlayPtr = wwo->lpLoopPtr; -+ } else { -+ /* Handle overlapping loops correctly */ -+ if (wwo->lpLoopPtr != lpWaveHdr && (lpWaveHdr->dwFlags & WHDR_BEGINLOOP)) { -+ FIXME("Correctly handled case ? (ending loop buffer also starts a new loop)\n"); -+ /* shall we consider the END flag for the closing loop or for -+ * the opening one or for both ??? -+ * code assumes for closing loop only -+ */ -+ } else { -+ lpWaveHdr = lpWaveHdr->lpNext; -+ } -+ wwo->lpLoopPtr = NULL; -+ wodPlayer_BeginWaveHdr(wwo, lpWaveHdr); -+ } -+ } else { -+ /* We're not in a loop. Advance to the next wave header */ -+ wodPlayer_BeginWaveHdr(wwo, lpWaveHdr = lpWaveHdr->lpNext); -+ } -+ -+ return lpWaveHdr; -+} -+ -+/************************************************************************** -+ * wodPlayer_CheckReleasing [internal] -+ * -+ * Check to make sure that playback has not stalled -+ */ -+static void wodPlayer_CheckReleasing(WINE_WAVEINST *wwo) { -+ LPWAVEHDR lpWaveHdr = wwo->lpQueuePtr; -+ -+ /* If we aren't playing, and we have queued data and we aren't relasing, -+ * start releasing if either: -+ * - We have stopped being given wavehdrs, or -+ * - We have 2s worth of audio built up.*/ -+ -+ pa_threaded_mainloop_lock(PULSE_ml); -+ if (!wwo->timing_info->playing && -+ (pa_bytes_to_usec(lpWaveHdr->dwBufferLength, &wwo->sample_spec)/2 < pa_timeval_age(&wwo->last_header)|| -+ wwo->timing_info->write_index - wwo->releasing_offset > pa_bytes_per_second(&wwo->sample_spec)*2)) { -+ -+ /* Try and adjust the buffer attributes so there is less latency. -+ * Because of bugs this call does not work on older servers. Once -+ * new version of pulseaudio become ubiquitous we will drop support for -+ * versions before 0.9.15 because they have too many bugs.*/ -+ if (wwo->buffer_attr.tlength == 0 && -+ pa_context_get_server_protocol_version(PULSE_context) >= 15) { -+ wwo->buffer_attr.tlength = wwo->timing_info->write_index; -+ wwo->buffer_attr.prebuf = (uint32_t) -1; -+ wwo->buffer_attr.minreq = (uint32_t) -1; -+ wwo->buffer_attr.maxlength = (uint32_t) -1; -+ WARN("Asking for new buffer tlength of %ums (%u bytes)\n", (unsigned int)(pa_bytes_to_usec(wwo->buffer_attr.tlength, &wwo->sample_spec)/1000), wwo->buffer_attr.tlength); -+ pa_stream_set_buffer_attr(wwo->stream, &wwo->buffer_attr, PULSE_StreamSuccessCallback, wwo); -+ } else { -+ /* Fake playback start earlier, introducing latency */ -+ pa_gettimeofday(&wwo->started_releasing); -+ wwo->is_releasing = TRUE; -+ wwo->releasing_offset = wwo->lpQueuePtr->reserved; -+ TRACE("Starting to release early: %u\n", wwo->releasing_offset); -+ } -+ } -+ pa_threaded_mainloop_unlock(PULSE_ml); -+} -+ -+/************************************************************************** -+ * wodPlayer_NotifyCompletions [internal] -+ * -+ * Notifies and remove from queue all wavehdrs which have been played to -+ * the speaker based on a reference time of (theoretical) playback start. If -+ * force is true, we notify all wavehdrs and remove them all from the queue -+ * even if they are unplayed or part of a loop. We return the time to wait -+ * until the next wavehdr needs to be freed, or INFINITE if there are no more -+ * wavehdrs. We use timevals rather than the stream position data that pulse -+ * gives us as realeasing needs to be constant and smooth for good application -+ * behaviour. -+ */ -+static DWORD wodPlayer_NotifyCompletions(WINE_WAVEINST* wwo, BOOL force) { -+ LPWAVEHDR lpWaveHdr; -+ pa_usec_t time; -+ pa_usec_t wait; -+ -+ time = pa_bytes_to_usec(wwo->releasing_offset, &wwo->sample_spec); -+ if (wwo->is_releasing) -+ time += pa_timeval_age(&wwo->started_releasing); -+ -+ for (lpWaveHdr = wwo->lpQueuePtr; lpWaveHdr;) { -+ if (!force) { -+ /* Start from lpQueuePtr and keep notifying until: -+ * - we hit an unwritten wavehdr -+ * - we hit the beginning of a running loop -+ * - we hit a wavehdr which hasn't finished playing -+ */ -+ if (lpWaveHdr == wwo->lpPlayPtr) { TRACE("play %p\n", lpWaveHdr); return INFINITE; } -+ if (lpWaveHdr == wwo->lpLoopPtr) { TRACE("loop %p\n", lpWaveHdr); return INFINITE; } -+ -+ /* See if this data has been played, and if not, return when it will have been */ -+ wait = pa_bytes_to_usec(lpWaveHdr->reserved + lpWaveHdr->dwBufferLength, &wwo->sample_spec); -+ if (wait >= time) { -+ wait = ((wait - time) + 999) / 1000; -+ return wait ?: 1; -+ } -+ } -+ -+ /* return the wavehdr */ -+ wwo->lpQueuePtr = lpWaveHdr->lpNext; -+ lpWaveHdr->dwFlags &= ~WHDR_INQUEUE; -+ lpWaveHdr->dwFlags |= WHDR_DONE; -+ -+ wodPlayer_NotifyClient(wwo, WOM_DONE, (DWORD)lpWaveHdr, 0); -+ lpWaveHdr = wwo->lpQueuePtr; -+ } -+ /* No more wavehdrs */ -+ TRACE("Empty queue\n"); -+ return INFINITE; -+} -+ -+/************************************************************************** -+ * wodPlayer_WriteMax [internal] -+ * -+ * Write either how much free space or how much data we have, depending on -+ * which is less -+ */ -+static int wodPlayer_WriteMax(WINE_WAVEINST *wwo, size_t *space) { -+ LPWAVEHDR lpWaveHdr = wwo->lpPlayPtr; -+ size_t toWrite = min(lpWaveHdr->dwBufferLength - wwo->dwPartialOffset, *space); -+ -+ if (toWrite > 0 && pa_stream_write(wwo->stream, lpWaveHdr->lpData + wwo->dwPartialOffset, toWrite, NULL, 0, PA_SEEK_RELATIVE) >= 0) -+ TRACE("Writing wavehdr %p.%u[%u]\n", lpWaveHdr, wwo->dwPartialOffset, lpWaveHdr->dwBufferLength); -+ else -+ return 0; -+ -+ /* Check to see if we wrote all of the wavehdr */ -+ if ((wwo->dwPartialOffset += toWrite) >= lpWaveHdr->dwBufferLength) -+ wodPlayer_PlayPtrNext(wwo); -+ -+ *space -= toWrite; -+ -+ return toWrite; -+} -+ -+/************************************************************************** -+ * wodPlayer_Feed [internal] -+ * -+ * Feed as much sound data as we can into pulse using wodPlayer_WriteMax. -+ * size_t space _must_ have come from either pa_stream_writable_size() or -+ * the value from a stream write callback, as if it isn't you run the risk -+ * of a buffer overflow in which audio data will be lost. -+ */ -+static void wodPlayer_Feed(WINE_WAVEINST* wwo, size_t space) { -+ -+ /* no more room... no need to try to feed */ -+ if (space == 0) return; -+ -+ if (!wwo->stream || !PULSE_context || -+ pa_context_get_state(PULSE_context) != PA_CONTEXT_READY || -+ pa_stream_get_state(wwo->stream) != PA_STREAM_READY) -+ return; -+ -+ pa_threaded_mainloop_lock(PULSE_ml); -+ /* Feed from a partial wavehdr */ -+ if (wwo->lpPlayPtr && wwo->dwPartialOffset != 0) -+ wodPlayer_WriteMax(wwo, &space); -+ -+ /* Feed wavehdrs until we run out of wavehdrs or buffer space */ -+ if (wwo->dwPartialOffset == 0 && wwo->lpPlayPtr) { -+ do { -+ wwo->lpPlayPtr->reserved = wwo->timing_info->write_index; -+ } while (wodPlayer_WriteMax(wwo, &space) > 0 && wwo->lpPlayPtr && space > 0); -+ } -+ pa_threaded_mainloop_unlock(PULSE_ml); -+} -+ -+/************************************************************************** -+ * wodPlayer_Reset [internal] -+ * -+ * wodPlayer helper. Resets current output stream. -+ */ -+static void wodPlayer_Reset(WINE_WAVEINST* wwo) { -+ enum win_wm_message msg; -+ DWORD param; -+ HANDLE ev; -+ -+ TRACE("(%p)\n", wwo); -+ -+ /* remove any buffer */ -+ wodPlayer_NotifyCompletions(wwo, TRUE); -+ -+ wwo->lpPlayPtr = wwo->lpQueuePtr = wwo->lpLoopPtr = NULL; -+ if (wwo->state != WINE_WS_PAUSED) -+ wwo->state = WINE_WS_STOPPED; -+ wwo->dwPartialOffset = 0; -+ -+ if (!wwo->stream || -+ !PULSE_context || -+ pa_context_get_state(PULSE_context) != PA_CONTEXT_READY || -+ pa_stream_get_state(wwo->stream) != PA_STREAM_READY) { -+ return; -+ } -+ -+ pa_threaded_mainloop_lock(PULSE_ml); -+ -+ /* flush the output buffer of written data*/ -+ PULSE_WaitForOperation(pa_stream_flush(wwo->stream, PULSE_StreamSuccessCallback, NULL)); -+ -+ /* Reset the written byte count as some data may have been flushed */ -+ if (wwo->timing_info->write_index_corrupt) -+ PULSE_WaitForOperation(pa_stream_update_timing_info(wwo->stream, PULSE_StreamSuccessCallback, wwo)); -+ wwo->releasing_offset = wwo->last_reset = wwo->timing_info->write_index; -+ if (wwo->is_releasing) -+ pa_gettimeofday(&wwo->started_releasing); -+ -+ /* return all pending headers in queue */ -+ EnterCriticalSection(&wwo->msgRing.msg_crst); -+ while (PULSE_RetrieveRingMessage(&wwo->msgRing, &msg, ¶m, &ev)) { -+ if (msg != WINE_WM_HEADER) { -+ SetEvent(ev); -+ continue; -+ } -+ ((LPWAVEHDR)param)->dwFlags &= ~WHDR_INQUEUE; -+ ((LPWAVEHDR)param)->dwFlags |= WHDR_DONE; -+ wodPlayer_NotifyClient(wwo, WOM_DONE, param, 0); -+ } -+ PULSE_ResetRingMessage(&wwo->msgRing); -+ LeaveCriticalSection(&wwo->msgRing.msg_crst); -+ -+ pa_threaded_mainloop_unlock(PULSE_ml); -+} -+ -+/************************************************************************** -+ * wodPlayer_Underrun [internal] -+ * -+ * wodPlayer helper. Deal with a stream underrun. -+ */ -+static void wodPlayer_Underrun(WINE_WAVEINST* wwo) { -+ -+ pa_threaded_mainloop_lock(PULSE_ml); -+ -+ /* Ask for a timing update */ -+ PULSE_WaitForOperation(pa_stream_update_timing_info(wwo->stream, PULSE_StreamSuccessCallback, wwo)); -+ -+ /* See if we recovered while the message was waiting in the queue */ -+ if (wwo->timing_info->playing) { -+ TRACE("False alarm\n"); -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ return; -+ } -+ -+ if (wwo->lpPlayPtr) { -+ size_t space; -+ -+ TRACE("There is queued data. Trying to recover.\n"); -+ -+ /* Ask for a timing update */ -+ if (wwo->timing_info->write_index_corrupt) -+ PULSE_WaitForOperation(pa_stream_update_timing_info(wwo->stream, PULSE_StreamSuccessCallback, wwo)); -+ space = pa_stream_writable_size(wwo->stream); -+ wodPlayer_Feed(wwo, space); -+ } -+ WARN("Stream underrun!\n"); -+ wwo->is_releasing = FALSE; -+ wwo->releasing_offset = wwo->timing_info->write_index; -+ pa_threaded_mainloop_unlock(PULSE_ml); -+} -+ -+ -+/************************************************************************** -+ * wodPlayer_ProcessMessages [internal] -+ */ -+static void wodPlayer_ProcessMessages(WINE_WAVEINST* wwo) { -+ LPWAVEHDR lpWaveHdr; -+ enum win_wm_message msg; -+ DWORD param; -+ HANDLE ev; -+ -+ while (PULSE_RetrieveRingMessage(&wwo->msgRing, &msg, ¶m, &ev)) { -+ TRACE("Received %s %x\n", PULSE_getCmdString(msg), param); -+ -+ switch (msg) { -+ case WINE_WM_PAUSING: -+ wwo->state = WINE_WS_PAUSED; -+ -+ pa_threaded_mainloop_lock(PULSE_ml); -+ PULSE_WaitForOperation(pa_stream_cork(wwo->stream, 1, PULSE_StreamSuccessCallback, wwo)); -+ /* save how far we are, as releasing will restart from here */ -+ if (wwo->is_releasing) -+ wwo->releasing_offset = wwo->timing_info->write_index; -+ wwo->is_releasing = FALSE; -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ SetEvent(ev); -+ break; -+ -+ case WINE_WM_RESTARTING: -+ if (wwo->state == WINE_WS_PAUSED) { -+ wwo->state = WINE_WS_PLAYING; -+ pa_threaded_mainloop_lock(PULSE_ml); -+ PULSE_WaitForOperation(pa_stream_cork(wwo->stream, 0, PULSE_StreamSuccessCallback, wwo)); -+ /* If the serverside buffer was near full before pause, we need to -+ * have space to write soon, so force playback start */ -+ PULSE_WaitForOperation(pa_stream_trigger(wwo->stream, PULSE_StreamSuccessCallback, wwo)); -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ } -+ SetEvent(ev); -+ break; -+ -+ case WINE_WM_HEADER: -+ lpWaveHdr = (LPWAVEHDR)param; -+ /* insert buffer at the end of queue */ -+ { -+ LPWAVEHDR* wh; -+ for (wh = &(wwo->lpQueuePtr); *wh; wh = &((*wh)->lpNext)); -+ *wh = lpWaveHdr; -+ } -+ -+ if (!wwo->lpPlayPtr) -+ wodPlayer_BeginWaveHdr(wwo,lpWaveHdr); -+ if (wwo->state == WINE_WS_STOPPED) -+ wwo->state = WINE_WS_PLAYING; -+ -+ wodPlayer_Feed(wwo, pa_stream_writable_size(wwo->stream)); -+ if (!wwo->timing_info->playing && !wwo->is_releasing) -+ pa_gettimeofday(&wwo->last_header); -+ SetEvent(ev); -+ break; -+ -+ case WINE_WM_RESETTING: -+ wodPlayer_Reset(wwo); -+ SetEvent(ev); -+ break; -+ -+ case WINE_WM_BREAKLOOP: -+ if (wwo->state == WINE_WS_PLAYING && wwo->lpLoopPtr != NULL) -+ /* ensure exit at end of current loop */ -+ wwo->dwLoops = 1; -+ SetEvent(ev); -+ break; -+ -+ case WINE_WM_FEED: /* Sent by the pulse thread */ -+ wodPlayer_Feed(wwo, pa_stream_writable_size(wwo->stream)); -+ SetEvent(ev); -+ break; -+ -+ case WINE_WM_XRUN: /* Sent by the pulse thread */ -+ if ((DWORD)param == 1) { -+ ERR("Buffer overflow!\n"); -+ pa_threaded_mainloop_lock(PULSE_ml); -+ PULSE_WaitForOperation(pa_stream_drain(wwo->stream, PULSE_StreamSuccessCallback, wwo)); -+ wwo->is_releasing = FALSE; -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ } else -+ wodPlayer_Underrun(wwo); -+ SetEvent(ev); -+ break; -+ -+ case WINE_WM_STARTING: /* Sent by the pulse thread */ -+ /* Start releasing wavehdrs if we haven't already */ -+ if (!wwo->is_releasing) { -+ wwo->is_releasing = TRUE; -+ pa_gettimeofday(&wwo->started_releasing); -+ } -+ SetEvent(ev); -+ break; -+ -+ case WINE_WM_CLOSING: /* If param = 1, close because of a failure */ -+ wwo->hThread = NULL; -+ if ((DWORD)param == 1) { -+ /* If we are here, the stream has failed */ -+ wwo->state = WINE_WS_FAILED; -+ SetEvent(ev); -+ PULSE_DestroyRingMessage(&wwo->msgRing); -+ wodPlayer_NotifyCompletions(wwo, TRUE); -+ wodPlayer_NotifyClient(wwo, WOM_CLOSE, 0L, 0L); -+ wwo->lpPlayPtr = wwo->lpQueuePtr = wwo->lpLoopPtr = NULL; -+ pa_threaded_mainloop_lock(PULSE_ml); -+ pa_stream_disconnect(wwo->stream); -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ TRACE("Thread exiting because of failure.\n"); -+ ExitThread(1); -+ /* Stream instance will get de-refferenced upon close */ -+ } -+ wwo->state = WINE_WS_CLOSED; -+ /* sanity check: this should not happen since the device must have been reset before */ -+ if (wwo->lpQueuePtr || wwo->lpPlayPtr) ERR("out of sync\n"); -+ SetEvent(ev); -+ TRACE("Thread exiting.\n"); -+ ExitThread(0); -+ /* shouldn't go here */ -+ -+ default: -+ FIXME("unknown message %d\n", msg); -+ break; -+ } -+ } -+} -+ -+/************************************************************************** -+ * wodPlayer [internal] -+ */ -+static DWORD CALLBACK wodPlayer(LPVOID lpParam) { -+ WINE_WAVEINST *wwo = (WINE_WAVEINST*)lpParam; -+ DWORD dwSleepTime = INFINITE; -+ -+ wwo->state = WINE_WS_STOPPED; -+ SetEvent(wwo->hStartUpEvent); -+ -+ /* Wait for the shortest time before an action is required. If there are -+ * no pending actions, wait forever for a command. */ -+ for (;;) { -+ TRACE("Waiting %u ms\n", dwSleepTime); -+ PULSE_WaitRingMessage(&wwo->msgRing, dwSleepTime); -+ wodPlayer_ProcessMessages(wwo); -+ if (wwo->state == WINE_WS_PLAYING) { -+ if (!wwo->is_releasing && wwo->lpQueuePtr) -+ wodPlayer_CheckReleasing(wwo); -+ dwSleepTime = wodPlayer_NotifyCompletions(wwo, FALSE); -+ } else -+ dwSleepTime = INFINITE; -+ } -+} -+ -+/************************************************************************** -+ * wodOpen [internal] -+ */ -+static DWORD wodOpen(WORD wDevID, LPDWORD lpdwUser, LPWAVEOPENDESC lpDesc, DWORD dwFlags) { -+ WINE_WAVEDEV *wdo; -+ WINE_WAVEINST *wwo = NULL; -+ DWORD ret = MMSYSERR_NOERROR; -+ pa_stream_flags_t stream_flags; -+ -+ TRACE("(%u, %p, %08X);\n", wDevID, lpDesc, dwFlags); -+ if (lpDesc == NULL) { -+ WARN("Invalid Parameter !\n"); -+ return MMSYSERR_INVALPARAM; -+ } -+ -+ if (wDevID >= PULSE_WodNumDevs) { -+ WARN("Asked for device %d, but only %d known!\n", wDevID, PULSE_WodNumDevs); -+ return MMSYSERR_BADDEVICEID; -+ } -+ wdo = &WOutDev[wDevID]; -+ -+ wwo = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WINE_WAVEINST)); -+ if (!wwo) return MMSYSERR_NOMEM; -+ *lpdwUser = (DWORD)wwo; -+ -+ /* check to see if format is supported and make pa_sample_spec struct */ -+ if (!PULSE_SetupFormat(lpDesc->lpFormat, &wwo->sample_spec)) { -+ WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n", -+ lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels, -+ lpDesc->lpFormat->nSamplesPerSec); -+ ret = WAVERR_BADFORMAT; -+ goto exit; -+ } -+ -+ if (TRACE_ON(wave)) { -+ char t[PA_SAMPLE_SPEC_SNPRINT_MAX]; -+ pa_sample_spec_snprint(t, sizeof(t), &wwo->sample_spec); -+ TRACE("Sample spec '%s'\n", t); -+ } -+ -+ if (dwFlags & WAVE_FORMAT_QUERY) { -+ TRACE("Query format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n", -+ lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels, -+ lpDesc->lpFormat->nSamplesPerSec); -+ ret = MMSYSERR_NOERROR; -+ goto exit; -+ } -+ -+ wwo->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK); -+ wwo->waveDesc = *lpDesc; -+ PULSE_InitRingMessage(&wwo->msgRing); -+ -+ wwo->stream = pa_stream_new(PULSE_context, "WaveOut", &wwo->sample_spec, NULL); -+ /* If server doesn't support sample_spec, we will error out here */ -+ if (!wwo->stream) { -+ ret = WAVERR_BADFORMAT; -+ goto exit; -+ } -+ -+ pa_stream_set_state_callback (wwo->stream, PULSE_StreamStateCallback, wwo); -+ pa_stream_set_write_callback (wwo->stream, PULSE_StreamRequestCallback, wwo); -+ pa_stream_set_underflow_callback (wwo->stream, PULSE_StreamUnderflowCallback, wwo); -+ pa_stream_set_overflow_callback (wwo->stream, PULSE_StreamOverflowCallback, wwo); -+ pa_stream_set_moved_callback (wwo->stream, PULSE_StreamMovedCallback, wwo); -+ pa_stream_set_suspended_callback (wwo->stream, PULSE_StreamSuspendedCallback, wwo); -+ -+ stream_flags = PA_STREAM_AUTO_TIMING_UPDATE; -+ -+#if PA_API_VERSION >= 12 -+ if (pa_context_get_server_protocol_version(PULSE_context) >= 15) { -+ pa_stream_set_started_callback(wwo->stream, WAVEOUT_StreamStartedCallback, wwo); -+ stream_flags |= PA_STREAM_ADJUST_LATENCY; -+ } else -+#endif -+ { -+ pa_stream_set_latency_update_callback(wwo->stream, WAVEOUT_StreamTimingInfoUpdateCallback, wwo); -+ } -+ -+ TRACE("Connecting stream for playback on %s.\n", wdo->device_name); -+ pa_threaded_mainloop_lock(PULSE_ml); -+ pa_stream_connect_playback(wwo->stream, wdo->device_name, NULL, stream_flags, NULL, NULL); -+ -+ for (;;) { -+ pa_context_state_t cstate = pa_context_get_state(PULSE_context); -+ pa_stream_state_t sstate = pa_stream_get_state(wwo->stream); -+ -+ if (cstate == PA_CONTEXT_FAILED || cstate == PA_CONTEXT_TERMINATED || -+ sstate == PA_STREAM_FAILED || sstate == PA_STREAM_TERMINATED) { -+ ERR("Failed to connect stream context object: %s\n", pa_strerror(pa_context_errno(PULSE_context))); -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ ret = MMSYSERR_NODRIVER; -+ goto exit; -+ } -+ -+ if (sstate == PA_STREAM_READY) -+ break; -+ -+ pa_threaded_mainloop_wait(PULSE_ml); -+ } -+ TRACE("(%p)->stream connected for playback.\n", wwo); -+ -+ PULSE_WaitForOperation(pa_stream_update_timing_info(wwo->stream, PULSE_StreamSuccessCallback, wwo)); -+ -+ wwo->timing_info = pa_stream_get_timing_info(wwo->stream); -+ assert(wwo->timing_info); -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ -+ wwo->hStartUpEvent = CreateEventW(NULL, FALSE, FALSE, NULL); -+ wwo->hThread = CreateThread(NULL, 0, wodPlayer, (LPVOID)wwo, 0, &(wwo->dwThreadID)); -+ if (wwo->hThread) -+ SetThreadPriority(wwo->hThread, THREAD_PRIORITY_TIME_CRITICAL); -+ else { -+ ERR("Thread creation for the wodPlayer failed!\n"); -+ ret = MMSYSERR_NOMEM; -+ goto exit; -+ } -+ WaitForSingleObject(wwo->hStartUpEvent, INFINITE); -+ CloseHandle(wwo->hStartUpEvent); -+ wwo->hStartUpEvent = INVALID_HANDLE_VALUE; -+ -+ -+ return wodPlayer_NotifyClient (wwo, WOM_OPEN, 0L, 0L); -+ -+exit: -+ if (!wwo) -+ return ret; -+ -+ if (wwo->hStartUpEvent != INVALID_HANDLE_VALUE) -+ CloseHandle(wwo->hStartUpEvent); -+ -+ if (wwo->msgRing.ring_buffer_size > 0) -+ PULSE_DestroyRingMessage(&wwo->msgRing); -+ -+ if (wwo->stream) { -+ if (pa_stream_get_state(wwo->stream) == PA_STREAM_READY) -+ pa_stream_disconnect(wwo->stream); -+ pa_stream_unref(wwo->stream); -+ wwo->stream = NULL; -+ } -+ HeapFree(GetProcessHeap(), 0, wwo); -+ -+ return ret; -+} -+ -+/************************************************************************** -+ * wodClose [internal] -+ */ -+static DWORD wodClose(WINE_WAVEINST *wwo) { -+ DWORD ret; -+ -+ TRACE("(%p);\n", wwo); -+ if (!wwo) { -+ WARN("Stream instance invalid.\n"); -+ return MMSYSERR_INVALHANDLE; -+ } -+ -+ if (wwo->state != WINE_WS_FAILED) { -+ if (wwo->lpQueuePtr && wwo->lpPlayPtr) { -+ WARN("buffers still playing !\n"); -+ return WAVERR_STILLPLAYING; -+ } -+ -+ pa_threaded_mainloop_lock(PULSE_ml); -+ PULSE_WaitForOperation(pa_stream_drain(wwo->stream, PULSE_StreamSuccessCallback, NULL)); -+ pa_stream_disconnect(wwo->stream); -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ -+ if (wwo->hThread != INVALID_HANDLE_VALUE) -+ PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_CLOSING, 0, TRUE); -+ -+ PULSE_DestroyRingMessage(&wwo->msgRing); -+ } -+ -+ if (wwo->stream) -+ pa_stream_unref(wwo->stream); -+ ret = wodPlayer_NotifyClient(wwo, WOM_CLOSE, 0L, 0L); -+ -+ HeapFree(GetProcessHeap(), 0, wwo); -+ -+ return ret; -+} -+ -+/************************************************************************** -+ * wodWrite [internal] -+ */ -+static DWORD wodWrite(WINE_WAVEINST *wwo, LPWAVEHDR lpWaveHdr, DWORD dwSize) { -+ if (!wwo || wwo->state == WINE_WS_FAILED) { -+ WARN("Stream instance invalid.\n"); -+ return MMSYSERR_INVALHANDLE; -+ } -+ -+ if (lpWaveHdr->lpData == NULL || !(lpWaveHdr->dwFlags & WHDR_PREPARED)) -+ return WAVERR_UNPREPARED; -+ -+ if (lpWaveHdr->dwFlags & WHDR_INQUEUE) -+ return WAVERR_STILLPLAYING; -+ -+ lpWaveHdr->dwFlags &= ~WHDR_DONE; -+ lpWaveHdr->dwFlags |= WHDR_INQUEUE; -+ lpWaveHdr->lpNext = 0; -+ lpWaveHdr->reserved = 0; -+ -+ PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_HEADER, (DWORD)lpWaveHdr, FALSE); -+ return MMSYSERR_NOERROR; -+} -+ -+/************************************************************************** -+ * wodPause [internal] -+ */ -+static DWORD wodPause(WINE_WAVEINST *wwo) { -+ if (!wwo || wwo->state == WINE_WS_FAILED) { -+ WARN("Stream instance invalid.\n"); -+ return MMSYSERR_INVALHANDLE; -+ } -+ -+ PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_PAUSING, 0, TRUE); -+ return MMSYSERR_NOERROR; -+} -+ -+/************************************************************************** -+ * wodGetPosition [internal] -+ */ -+static DWORD wodGetPosition(WINE_WAVEINST *wwo, LPMMTIME lpTime, DWORD uSize) { -+ if (!wwo || wwo->state == WINE_WS_FAILED) { -+ WARN("Stream instance invalid.\n"); -+ return MMSYSERR_INVALHANDLE; -+ } -+ -+ if (lpTime == NULL) return MMSYSERR_INVALPARAM; -+ -+ pa_threaded_mainloop_lock(PULSE_ml); -+ if (wwo->timing_info->read_index_corrupt || wwo->timing_info->write_index_corrupt) -+ PULSE_WaitForOperation(pa_stream_update_timing_info(wwo->stream, PULSE_StreamSuccessCallback, wwo)); -+ -+ PULSE_GetMMTime(wwo->timing_info, &wwo->sample_spec, wwo->last_reset, lpTime); -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ -+ return MMSYSERR_NOERROR; -+} -+/************************************************************************** -+ * wodBreakLoop [internal] -+ */ -+static DWORD wodBreakLoop(WINE_WAVEINST *wwo) { -+ if (!wwo || wwo->state == WINE_WS_FAILED) { -+ WARN("Stream instance invalid.\n"); -+ return MMSYSERR_INVALHANDLE; -+ } -+ -+ PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_BREAKLOOP, 0, TRUE); -+ return MMSYSERR_NOERROR; -+} -+ -+/************************************************************************** -+ * 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; -+} -+ -+/************************************************************************** -+ * wodGetNumDevs [internal] -+ * Context-sanity check here, as if we respond with 0, WINE will move on -+ * to the next waveout driver. -+ */ -+static DWORD wodGetNumDevs() { -+ if (!PULSE_ml || !PULSE_context || pa_context_get_state(PULSE_context) != PA_CONTEXT_READY) -+ return 0; -+ -+ return PULSE_WodNumDevs; -+} -+ -+/************************************************************************** -+ * wodGetVolume [internal] -+ */ -+static DWORD wodGetVolume(WINE_WAVEINST *wwo, LPDWORD lpdwVol) { -+ float value1, value2; -+ DWORD wleft, wright; -+ -+ if (!wwo || wwo->state == WINE_WS_FAILED) { -+ WARN("Stream instance invalid.\n"); -+ return MMSYSERR_INVALHANDLE; -+ } -+ -+ TRACE("(%p, %p);\n", wwo, lpdwVol); -+ -+ if (lpdwVol == NULL) -+ return MMSYSERR_NOTENABLED; -+ -+ pa_threaded_mainloop_lock(PULSE_ml); -+ if (wwo->stream && PULSE_context && pa_context_get_state(PULSE_context) == PA_CONTEXT_READY && -+ pa_stream_get_state(wwo->stream) == PA_STREAM_READY) { -+ PULSE_WaitForOperation(pa_context_get_sink_input_info(PULSE_context, pa_stream_get_index(wwo->stream), WAVEOUT_SinkInputInfoCallback, wwo)); -+ } -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ -+ -+ if (wwo->volume.channels == 2) { -+ value1 = pa_sw_volume_to_dB(wwo->volume.values[0]); -+ value2 = pa_sw_volume_to_dB(wwo->volume.values[1]); -+ } else { -+ value1 = pa_sw_volume_to_dB(pa_cvolume_avg(&wwo->volume)); -+ value2 = 0; -+ } -+ -+ if (value1 < -60) -+ wleft = 0; -+ else -+ -+ if (value2 < -60) -+ wright = 0; -+ else -+ wright = 0xFFFFl - ((value2 / -60)*(float)0xFFFFl); -+ -+ if (wleft > 0xFFFFl) -+ wleft = 0xFFFFl; -+ if (wright > 0xFFFFl) -+ wright = 0xFFFFl; -+ -+ *lpdwVol = (WORD)wleft + (WORD)(wright << 16); -+ -+ return MMSYSERR_NOERROR; -+} -+ -+/************************************************************************** -+ * wodSetVolume [internal] -+ */ -+static DWORD wodSetVolume(WINE_WAVEINST *wwo, DWORD dwParam1) { -+ double value1, value2; -+ -+ TRACE("(%p, %08X);\n", wwo, dwParam1); -+ if (!wwo || wwo->state == WINE_WS_FAILED) { -+ WARN("Stream instance invalid.\n"); -+ return MMSYSERR_INVALHANDLE; -+ } -+ -+ /* waveOut volumes are /supposed/ to be logarithmic */ -+ value1 = LOWORD(dwParam1) == 0 ? PA_DECIBEL_MININFTY : ((float)(0xFFFFl - LOWORD(dwParam1))/0xFFFFl) * -60.0; -+ value2 = HIWORD(dwParam1) == 0 ? PA_DECIBEL_MININFTY : ((float)(0xFFFFl - HIWORD(dwParam1))/0xFFFFl) * -60.0; -+ -+ if (wwo->sample_spec.channels == 2) { -+ wwo->volume.channels = 2; -+ wwo->volume.values[0] = pa_sw_volume_from_dB(value1); -+ wwo->volume.values[1] = pa_sw_volume_from_dB(value2); -+ } else { -+ if (value1 != value2) FIXME("Non-stereo streams can't pan!\n"); -+ wwo->volume.channels = wwo->sample_spec.channels; -+ pa_cvolume_set(&wwo->volume, wwo->volume.channels, pa_sw_volume_from_dB(max(value1, value2))); -+ } -+ -+ if (TRACE_ON(wave)) { -+ char s[PA_CVOLUME_SNPRINT_MAX]; -+ pa_cvolume_snprint(s, PA_CVOLUME_SNPRINT_MAX, &wwo->volume); -+ TRACE("%s\n", s); -+ } -+ -+ pa_threaded_mainloop_lock(PULSE_ml); -+ if (!wwo->stream || !PULSE_context || pa_context_get_state(PULSE_context) != PA_CONTEXT_READY || -+ pa_stream_get_state(wwo->stream) != PA_STREAM_READY || !pa_cvolume_valid(&wwo->volume)) { -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ return MMSYSERR_NOERROR; -+ } -+ -+ PULSE_WaitForOperation(pa_context_set_sink_input_volume(PULSE_context, -+ pa_stream_get_index(wwo->stream), &wwo->volume, -+ PULSE_ContextSuccessCallback, wwo)); -+ pa_threaded_mainloop_unlock(PULSE_ml); -+ return MMSYSERR_NOERROR; -+} -+ -+/************************************************************************** -+ * wodRestart [internal] -+ */ -+static DWORD wodRestart(WINE_WAVEINST *wwo) { -+ if (!wwo || wwo->state == WINE_WS_FAILED) { -+ WARN("Stream instance invalid.\n"); -+ return MMSYSERR_INVALHANDLE; -+ } -+ -+ if (wwo->state == WINE_WS_PAUSED) -+ PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_RESTARTING, 0, TRUE); -+ return MMSYSERR_NOERROR; -+} -+ -+/************************************************************************** -+ * wodReset [internal] -+ */ -+static DWORD wodReset(WINE_WAVEINST *wwo) { -+ if (!wwo || wwo->state == WINE_WS_FAILED) { -+ WARN("Stream instance invalid.\n"); -+ return MMSYSERR_INVALHANDLE; -+ } -+ -+ PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_RESETTING, 0, TRUE); -+ 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; -+} -+ -+/************************************************************************** -+ * wodMessage (WINEPULSE.@) -+ */ -+DWORD WINAPI PULSE_wodMessage(UINT wDevID, UINT wMsg, DWORD dwUser, -+ DWORD dwParam1, DWORD dwParam2) { -+/* -+ TRACE("(%u, %s, %08X, %08X, %08X);\n", -+ wDevID, PULSE_getMessage(wMsg), dwUser, dwParam1, 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 wodOpen (wDevID, (LPDWORD)dwUser, (LPWAVEOPENDESC)dwParam1, dwParam2); -+ case WODM_CLOSE: return wodClose ((WINE_WAVEINST*)dwUser); -+ case WODM_WRITE: return wodWrite ((WINE_WAVEINST*)dwUser, (LPWAVEHDR)dwParam1, dwParam2); -+ case WODM_PAUSE: return wodPause ((WINE_WAVEINST*)dwUser); -+ case WODM_GETPOS: return wodGetPosition ((WINE_WAVEINST*)dwUser, (LPMMTIME)dwParam1, dwParam2); -+ case WODM_BREAKLOOP: return wodBreakLoop ((WINE_WAVEINST*)dwUser); -+ 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 wodGetNumDevs (); -+ 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 wodGetVolume ((WINE_WAVEINST*)dwUser, (LPDWORD)dwParam1); -+ case WODM_SETVOLUME: return wodSetVolume ((WINE_WAVEINST*)dwUser, dwParam1); -+ case WODM_RESTART: return wodRestart ((WINE_WAVEINST*)dwUser); -+ case WODM_RESET: return wodReset ((WINE_WAVEINST*)dwUser); -+ 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..1b49460 ---- /dev/null -+++ b/dlls/winepulse.drv/winepulse.drv.spec -@@ -0,0 +1,3 @@ -+@ stdcall -private DriverProc(long long long long long long) PULSE_DriverProc -+@ stdcall -private wodMessage(long long long long long long) PULSE_wodMessage -+@ stdcall -private widMessage(long long long long long long) PULSE_widMessage -diff --git a/dlls/winepulse.drv/winepulse.h b/dlls/winepulse.drv/winepulse.h -new file mode 100644 -index 0000000..59fbb38 ---- /dev/null -+++ b/dlls/winepulse.drv/winepulse.h -@@ -0,0 +1,228 @@ -+/* Definitions for PulseAudio Wine Driver -+ * -+ * Copyright 2009 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 | -+ * +---------+-------------+---------------+---------------------------------+ -+ */ -+ -+/* 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_ALL_FORMATS \ -+ 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 */ -+ -+/* events to be sent 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; -+ -+typedef struct WINE_WAVEDEV WINE_WAVEDEV; -+typedef struct WINE_WAVEINST WINE_WAVEINST; -+typedef struct IDsDriverImpl IDsDriverImpl; -+typedef struct IDsDriverBufferImpl IDsDriverBufferImpl; -+ -+struct IDsDriverImpl { -+ /* IUnknown fields */ -+ const IDsDriverVtbl *lpVtbl; -+ LONG ref; -+ -+ IDsDriverBufferImpl *primary; -+ UINT wDevID; -+}; -+ -+struct IDsDriverBufferImpl { -+ const IDsDriverBufferVtbl *lpVtbl; -+ IDsDriverImpl* drv; -+ LONG ref; -+ pa_stream *stream; -+ pa_sample_spec sample_spec; -+ pa_cvolume volume; -+ -+ PBYTE buffer; -+ DWORD buffer_length; -+ DWORD buffer_read_offset; -+ DWORD buffer_play_offset; -+ DWORD fraglen; -+}; -+ -+/* Per-playback/record device */ -+struct WINE_WAVEDEV { -+ char interface_name[MAXPNAMELEN * 2]; -+ char *device_name; -+ pa_cvolume volume; -+ -+ union { -+ WAVEOUTCAPSW out; -+ WAVEINCAPSW in; -+ } caps; -+ -+ /* DirectSound stuff */ -+ DSDRIVERDESC ds_desc; -+ DSDRIVERCAPS ds_caps; -+}; -+ -+/* Per-playback/record instance */ -+struct WINE_WAVEINST { -+ 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_sample_spec sample_spec; /* Sample spec of this stream / device */ -+ pa_cvolume volume; -+ pa_buffer_attr buffer_attr; -+ -+ /* waveIn / waveOut wavaHdr information */ -+ LPWAVEHDR lpQueuePtr; /* start of queued WAVEHDRs (waiting to be notified) */ -+ LPWAVEHDR lpPlayPtr; /* start of not yet fully written 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 */ -+ struct timeval last_header; /* When the last wavehdr was received, only updated when audio is not playing yet */ -+ BOOL is_releasing; /* Whether we are releasing wavehdrs */ -+ 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 */ -+ -+ /* waveIn */ -+ const void *buffer; /* Pointer to the latest data fragment for recording streams */ -+ DWORD buffer_length; /* How large the latest data fragment is */ -+ DWORD buffer_read_offset; /* How far into latest data fragment we last read */ -+ DWORD fraglen; -+ -+ /* Thread communication and synchronization stuff */ -+ HANDLE hStartUpEvent; -+ HANDLE hThread; -+ DWORD dwThreadID; -+ PULSE_MSG_RING msgRing; -+}; -+ -+/* 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_WaitForOperation(pa_operation *o); -+void PULSE_StreamSuccessCallback(pa_stream *s, int success, void *userdata); -+void PULSE_StreamStateCallback(pa_stream *s, void *userdata); -+void PULSE_StreamRequestCallback(pa_stream *s, size_t n, void *userdata); -+void PULSE_StreamUnderflowCallback(pa_stream *s, void *userdata); -+void PULSE_StreamOverflowCallback(pa_stream *s, void *userdata); -+void PULSE_StreamSuspendedCallback(pa_stream *s, void *userdata); -+void PULSE_StreamMovedCallback(pa_stream *s, void *userdata); -+void PULSE_ContextSuccessCallback(pa_context *c, int success, void *userdata); -+BOOL PULSE_SetupFormat(LPWAVEFORMATEX wf, pa_sample_spec *ss); -+void PULSE_GetMMTime(const pa_timing_info *t, pa_sample_spec *s, size_t last_reset, LPMMTIME lpTime); -+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); -+ -+/* dsoutput.c */ -+DWORD wodDsCreate(UINT wDevID, PIDSDRIVER* drv); -+DWORD wodDsDesc(UINT wDevID, PDSDRIVERDESC desc); -+#endif diff --git a/winepulse-0.25.patch b/winepulse-0.25.patch new file mode 100644 index 0000000..3c73118 --- /dev/null +++ b/winepulse-0.25.patch @@ -0,0 +1,3424 @@ +diff --git a/dlls/winepulse.drv/Makefile.in b/dlls/winepulse.drv/Makefile.in +new file mode 100644 +index 0000000..327f225 +--- /dev/null ++++ b/dlls/winepulse.drv/Makefile.in +@@ -0,0 +1,16 @@ ++TOPSRCDIR = @top_srcdir@ ++TOPOBJDIR = ../.. ++SRCDIR = @srcdir@ ++VPATH = @srcdir@ ++MODULE = winepulse.drv ++IMPORTS = dxguid uuid winmm user32 advapi32 kernel32 ++EXTRALIBS = @PULSELIBS@ ++ ++C_SRCS = dsoutput.c \ ++ waveout.c \ ++ wavein.c \ ++ pulse.c ++ ++@MAKE_DLL_RULES@ ++ ++@DEPENDENCIES@ # everything below this line is overwritten by make depend +diff --git a/dlls/winepulse.drv/dsoutput.c b/dlls/winepulse.drv/dsoutput.c +new file mode 100644 +index 0000000..203fac0 +--- /dev/null ++++ b/dlls/winepulse.drv/dsoutput.c +@@ -0,0 +1,578 @@ ++/* ++ * Wine Driver for PulseAudio - DSound Output Functionality ++ * http://pulseaudio.org/ ++ * ++ * Copyright 2009 Arthur Talyor ++ * ++ * Conatins code from other wine multimedia 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 "windef.h" ++#include "winbase.h" ++#include "wingdi.h" ++#include "winuser.h" ++#include "winerror.h" ++#include "mmddk.h" ++#include "dsound.h" ++#include "dsdriver.h" ++#include "winreg.h" ++ ++#include ++#include "wine/debug.h" ++ ++WINE_DEFAULT_DEBUG_CHANNEL(dspulse); ++ ++#if HAVE_PULSEAUDIO ++ ++/*======================================================================* ++ * Low level DSOUND implementation * ++ *======================================================================*/ ++ ++/* A buffer is allocated with a pointer indicating the read position in the ++ * buffer. The pulse write callback reads data from the buffer, updating the ++ * read pointer to the location at the end of the read. Upon reaching the end ++ * or attempting to read past the end of buffer the read pointer wraps around ++ * to the beginning again. DirectSound applications can write anywhere in the ++ * buffer at anytime without locking and can know the location of the read ++ * pointer. The position of the read pointer cannot be changed by the ++ * application and access to it uses a locking scheme. A fake pointer ++ * indicating estimated playback position is also available to the application. ++ * Applications can potentially write to the same area of memory which is also ++ * being read by the pulse thread. However, this is uncommon as directsound ++ * applications know where pulse should be reading from via the pointer ++ * locations and MSDN says that such an operation should be avoided with the ++ * results being undefined. ++ */ ++ ++/* Fragment lengths try to be a power of two close to 10ms worth of data. See ++ * dlls/dsound/mixer.c ++ */ ++static int fragment_length(pa_sample_spec *s) { ++ if (s->rate <= 12800) ++ return 128 * pa_frame_size(s); ++ ++ if (s->rate <= 25600) ++ return 256 * pa_frame_size(s); ++ ++ if (s->rate <= 51200) ++ return 512 * pa_frame_size(s); ++ ++ return 1024 * pa_frame_size(s); ++} ++ ++/* Callback from the pulse thread for stream data */ ++static void DSPULSE_BufferReadCallback(pa_stream *s, size_t nbytes, void *userdata) { ++ IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)userdata; ++ ++ if (!This->buffer) ++ return; ++ ++ /* Fraglens are always powers of 2 */ ++ nbytes+= This->fraglen - 1; ++ nbytes&= ~(This->fraglen - 1); ++ ++ /* If we advance more than 10 fragments at a time it appears that the buffer ++ * pointer is never advancing because of wrap-around. Evil magic numbers. */ ++ if (nbytes > This->fraglen * 5) ++ nbytes = This->fraglen * 5; ++ ++ TRACE("Reading %u bytes.\n", nbytes); ++ ++ if (This->buffer_read_offset + nbytes <= This->buffer_length) { ++ pa_stream_write(s, This->buffer + This->buffer_read_offset, nbytes, NULL, 0, PA_SEEK_RELATIVE); ++ This->buffer_play_offset = This->buffer_read_offset; ++ This->buffer_read_offset += nbytes; ++ } else { ++ size_t write_length = This->buffer_length - This->buffer_read_offset; ++ nbytes -= write_length; ++ pa_stream_write(s, This->buffer + This->buffer_read_offset, write_length, NULL, 0, PA_SEEK_RELATIVE); ++ pa_stream_write(s, This->buffer, nbytes, NULL, 0, PA_SEEK_RELATIVE); ++ This->buffer_play_offset = This->buffer_read_offset; ++ This->buffer_read_offset = nbytes; ++ } ++ ++ This->buffer_read_offset %= This->buffer_length; ++} ++ ++/* Called when the stream underruns. Just for information */ ++static void DSPULSE_BufferUnderflowCallback(pa_stream *s, void *userdata) { ++ WARN("(%p) underrun.\n", userdata); ++} ++ ++/* Connects a stream to the server. Does not update ++ * IDsDriverBufferImpl->fraglen. Does not lock the pulse mainloop or free ++ * objects in case of failure. This should be handled by the calling function. ++ */ ++static HRESULT DSPULSE_ConnectStream(IDsDriverBufferImpl* This) { ++ pa_buffer_attr ba_request; ++ const pa_buffer_attr *ba_obtained; ++ char c[PA_SAMPLE_SPEC_SNPRINT_MAX]; ++ pa_stream_flags_t stream_flags = PA_STREAM_START_CORKED; ++ ++#if PA_PROTOCOL_VERSION >= 14 ++ /* We are a "fragment wait based" application, so this flag should be ++ * acceptable. */ ++ stream_flags |= PA_STREAM_EARLY_REQUESTS; ++#elif PA_PROTOCOL_VERSION >= 13 && PA_API_VERSION >= 12 ++ stream_flags |= PA_STREAM_ADJUST_LATENCY; ++#endif ++ ++ pa_sample_spec_snprint(c, PA_SAMPLE_SPEC_SNPRINT_MAX, &This->sample_spec); ++ TRACE("Sample spec %s fragment size %u.\n", c, This->fraglen); ++ ++ ba_request.tlength = This->fraglen * 4; // ~40ms ++ ba_request.minreq = This->fraglen; // ~10ms ++ ba_request.prebuf = (uint32_t)-1; // same as tlength ++ ba_request.maxlength = This->buffer_length; // 2^x = ~3s ++ ++ TRACE("Asking for buffer tlength:%u (%llums) minreq:%u (%llums)\n", ++ ba_request.tlength, pa_bytes_to_usec(ba_request.tlength, &This->sample_spec)/1000, ++ ba_request.minreq, pa_bytes_to_usec(ba_request.minreq, &This->sample_spec)/1000); ++ ++ This->stream = pa_stream_new(PULSE_context, "DirectSound Buffer", &This->sample_spec, NULL); ++ if (!This->stream) return DSERR_BADFORMAT; ++ ++ pa_stream_set_state_callback(This->stream, PULSE_StreamStateCallback, This); ++ pa_stream_set_write_callback(This->stream, DSPULSE_BufferReadCallback, This); ++ pa_stream_set_underflow_callback(This->stream, DSPULSE_BufferUnderflowCallback, This); ++ ++ TRACE("Attempting to connect (%p)->stream for playback on %s\n", This, WOutDev[This->drv->wDevID].device_name); ++ pa_stream_connect_playback(This->stream, WOutDev[This->drv->wDevID].device_name, &ba_request, stream_flags, NULL, NULL); ++ for (;;) { ++ pa_context_state_t cstate = pa_context_get_state(PULSE_context); ++ pa_stream_state_t sstate = pa_stream_get_state(This->stream); ++ ++ if (cstate == PA_CONTEXT_FAILED || cstate == PA_CONTEXT_TERMINATED || ++ sstate == PA_STREAM_FAILED || sstate == PA_STREAM_TERMINATED) { ++ ERR("Failed to connect stream context object: %s\n", pa_strerror(pa_context_errno(PULSE_context))); ++ return DSERR_BUFFERLOST; ++ } ++ ++ if (sstate == PA_STREAM_READY) ++ break; ++ ++ pa_threaded_mainloop_wait(PULSE_ml); ++ } ++ TRACE("(%p)->stream connected for playback.\n", This); ++ ba_obtained = pa_stream_get_buffer_attr(This->stream); ++ ++ TRACE("Obtained buffer tlength:%u (%llums) minreq:%u (%llums)\n", ++ ba_obtained->tlength, pa_bytes_to_usec(ba_obtained->tlength, &This->sample_spec)/1000, ++ ba_obtained->minreq, pa_bytes_to_usec(ba_obtained->minreq, &This->sample_spec)/1000); ++ ++ return DS_OK; ++} ++ ++static HRESULT WINAPI IDsDriverBufferImpl_QueryInterface(PIDSDRIVERBUFFER iface, REFIID riid, LPVOID *ppobj) { ++ /* IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; */ ++ FIXME("(): stub!\n"); ++ return DSERR_UNSUPPORTED; ++} ++ ++static ULONG WINAPI IDsDriverBufferImpl_AddRef(PIDSDRIVERBUFFER iface) { ++ IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; ++ ULONG refCount = InterlockedIncrement(&This->ref); ++ ++ TRACE("(%p)->(ref before=%u)\n",This, refCount - 1); ++ ++ return refCount; ++} ++ ++static ULONG WINAPI IDsDriverBufferImpl_Release(PIDSDRIVERBUFFER iface) { ++ IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; ++ ULONG refCount = InterlockedDecrement(&This->ref); ++ ++ TRACE("(%p)->(ref before=%u)\n",This, refCount + 1); ++ ++ if (refCount) ++ return refCount; ++ ++ TRACE("mmap buffer %p destroyed\n", This->buffer); ++ pa_threaded_mainloop_lock(PULSE_ml); ++ PULSE_WaitForOperation(pa_stream_cork(This->stream, 1, PULSE_StreamSuccessCallback, This)); ++ pa_stream_disconnect(This->stream); ++ if (This == This->drv->primary) ++ This->drv->primary = NULL; ++ HeapFree(GetProcessHeap(), 0, This->buffer); ++ This->buffer = NULL; ++ pa_stream_unref(This->stream); ++ This->stream = NULL; ++ HeapFree(GetProcessHeap(), 0, This); ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ ++ return 0; ++} ++ ++static HRESULT WINAPI IDsDriverBufferImpl_Lock(PIDSDRIVERBUFFER iface, ++ LPVOID*ppvAudio1,LPDWORD pdwLen1, ++ LPVOID*ppvAudio2,LPDWORD pdwLen2, ++ DWORD dwWritePosition,DWORD dwWriteLen, ++ DWORD dwFlags) ++{ ++ /* We set DSDDESC_DONTNEEDPRIMARYLOCK so this should never be called */ ++ TRACE("(%p): stub", iface); ++ return DSERR_UNSUPPORTED; ++} ++ ++static HRESULT WINAPI IDsDriverBufferImpl_Unlock(PIDSDRIVERBUFFER iface, ++ LPVOID pvAudio1,DWORD dwLen1, ++ LPVOID pvAudio2,DWORD dwLen2) ++{ ++ /* We set DSDDESC_DONTNEEDPRIMARYLOCK so this should never be called */ ++ TRACE("(%p): stub", iface); ++ return DSERR_UNSUPPORTED; ++} ++ ++/* You cannot change the sample format of a connected stream, so we need to ++ * destroy and re-create the stream if the sample spec is different */ ++static HRESULT WINAPI IDsDriverBufferImpl_SetFormat(PIDSDRIVERBUFFER iface, LPWAVEFORMATEX pwfx) { ++ IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; ++ pa_sample_spec old_spec; ++ ++ TRACE("(%p, %p)\n", iface, pwfx); ++ ++ old_spec.rate = This->sample_spec.rate; ++ old_spec.format = This->sample_spec.format; ++ old_spec.channels = This->sample_spec.channels; ++ ++ if (!PULSE_SetupFormat(pwfx, &This->sample_spec)) ++ return DSERR_BADFORMAT; ++ ++ if (old_spec.rate == This->sample_spec.rate && ++ old_spec.format == This->sample_spec.format && ++ old_spec.channels == This->sample_spec.channels) { ++ TRACE("same as original sample spec, exiting.\n"); ++ PULSE_WaitForOperation(pa_stream_flush(This->stream, PULSE_StreamSuccessCallback, This)); ++ return DS_OK; ++ } ++ ++ /* If the format doesn't match, return an error and the buffer will be remade */ ++ TRACE("Formats don't match, failing causing re-creation.\n"); ++ return DSERR_BUFFERLOST; ++} ++ ++static HRESULT WINAPI IDsDriverBufferImpl_SetFrequency(PIDSDRIVERBUFFER iface, DWORD dwFreq) ++{ ++ /* IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; */ ++ /* You can't do for primary buffers anyways */ ++ return S_OK; ++} ++ ++static HRESULT WINAPI IDsDriverBufferImpl_SetVolumePan(PIDSDRIVERBUFFER iface, PDSVOLUMEPAN pVolPan) { ++ IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; ++ ++ if (This->sample_spec.channels == 2) { ++ This->volume.channels = 2; ++ if (pVolPan->lPan < 0) { ++ This->volume.values[0] = pa_sw_volume_from_dB(pVolPan->lVolume/-10.0 + pVolPan->lPan/10.0); ++ This->volume.values[1] = pa_sw_volume_from_dB(pVolPan->lVolume/-10.0); ++ } else { ++ This->volume.values[0] = pa_sw_volume_from_dB(pVolPan->lVolume/-10.0); ++ This->volume.values[1] = pa_sw_volume_from_dB(pVolPan->lVolume/-10.0 - pVolPan->lPan/10.0); ++ } ++ } else { ++ WARN("Panning non-stereo streams not supported yet!\n"); ++ pa_cvolume_set(&This->volume, This->sample_spec.channels, pa_sw_volume_from_dB(pVolPan->lVolume/-10.0)); ++ /* Would be nice to return DSERR_CONTROLUNAVAIL, but that isn't up to us ++ * here */ ++ } ++ ++ pa_threaded_mainloop_lock(PULSE_ml); ++ if (This->stream && PULSE_context && pa_context_get_state(PULSE_context) == PA_CONTEXT_READY && ++ pa_stream_get_state(This->stream) == PA_STREAM_READY && pa_cvolume_valid(&This->volume)) ++ PULSE_WaitForOperation( ++ pa_context_set_sink_input_volume( ++ PULSE_context, pa_stream_get_index(This->stream), ++ &This->volume, PULSE_ContextSuccessCallback, This)); ++ ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ ++ return DS_OK; ++} ++ ++static HRESULT WINAPI IDsDriverBufferImpl_SetPosition(PIDSDRIVERBUFFER iface, DWORD dwNewPos) ++{ ++ /* IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; */ ++ /* Not allowed on primary buffers */ ++ return DSERR_UNSUPPORTED; ++} ++ ++static HRESULT WINAPI IDsDriverBufferImpl_GetPosition(PIDSDRIVERBUFFER iface, ++ LPDWORD lpdwPlay, LPDWORD lpdwWrite) ++{ ++ IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; ++ ++ pa_threaded_mainloop_lock(PULSE_ml); ++ if (!This->buffer || pa_stream_get_state(This->stream) != PA_STREAM_READY) { ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ return DSERR_UNINITIALIZED; ++ } ++ ++ if (lpdwPlay) ++ *lpdwPlay = This->buffer_play_offset; ++ if (lpdwWrite) ++ *lpdwWrite = This->buffer_read_offset; ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ ++ return DS_OK; ++} ++ ++static HRESULT WINAPI IDsDriverBufferImpl_Play(PIDSDRIVERBUFFER iface, DWORD dwRes1, DWORD dwRes2, DWORD dwFlags) { ++ IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; ++ TRACE("(%p,%x,%x,%x)\n",iface,dwRes1,dwRes2,dwFlags); ++ ++ pa_threaded_mainloop_lock(PULSE_ml); ++ PULSE_WaitForOperation(pa_stream_cork(This->stream, 0, PULSE_StreamSuccessCallback, This)); ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ ++ return DS_OK; ++} ++ ++static HRESULT WINAPI IDsDriverBufferImpl_Stop(PIDSDRIVERBUFFER iface) { ++ IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; ++ TRACE("(%p)\n",iface); ++ ++ pa_threaded_mainloop_lock(PULSE_ml); ++ PULSE_WaitForOperation(pa_stream_cork(This->stream, 1, PULSE_StreamSuccessCallback, This)); ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ return DS_OK; ++} ++ ++/*****************************************************************************/ ++ ++static const IDsDriverBufferVtbl dsdbvt = ++{ ++ IDsDriverBufferImpl_QueryInterface, ++ IDsDriverBufferImpl_AddRef, ++ IDsDriverBufferImpl_Release, ++ IDsDriverBufferImpl_Lock, ++ IDsDriverBufferImpl_Unlock, ++ IDsDriverBufferImpl_SetFormat, ++ IDsDriverBufferImpl_SetFrequency, ++ IDsDriverBufferImpl_SetVolumePan, ++ IDsDriverBufferImpl_SetPosition, ++ IDsDriverBufferImpl_GetPosition, ++ IDsDriverBufferImpl_Play, ++ IDsDriverBufferImpl_Stop ++}; ++ ++static HRESULT WINAPI IDsDriverImpl_CreateSoundBuffer(PIDSDRIVER iface, ++ LPWAVEFORMATEX pwfx, ++ DWORD dwFlags, DWORD dwCardAddress, ++ LPDWORD pdwcbBufferSize, ++ LPBYTE *ppbBuffer, ++ LPVOID *ppvObj) { ++ IDsDriverImpl *This = (IDsDriverImpl *)iface; ++ IDsDriverBufferImpl** ippdsdb = (IDsDriverBufferImpl**)ppvObj; ++ IDsDriverBufferImpl *That; ++ HRESULT ret; ++ ++ TRACE("(%p,%p,%x,%x)\n",iface,pwfx,dwFlags,dwCardAddress); ++ /* we only support primary buffers */ ++ ++ if (!(dwFlags & DSBCAPS_PRIMARYBUFFER)) ++ return DSERR_UNSUPPORTED; ++ if (This->primary) ++ return DSERR_ALLOCATED; ++ This->primary = *ippdsdb; ++ ++ *ippdsdb = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(IDsDriverBufferImpl)); ++ ++ if (!*ippdsdb) { ++ ERR("Out of memory\n"); ++ return DSERR_OUTOFMEMORY; ++ } ++ ++ That = *ippdsdb; ++ That->lpVtbl = &dsdbvt; ++ That->ref = 1; ++ ++ That->drv = This; ++ TRACE("IdsDriverBufferImpl %p created.\n", That); ++ ++ if (!PULSE_SetupFormat(pwfx, &That->sample_spec)) { ++ WARN("Bad audio format.\n"); ++ ret = DSERR_BADFORMAT; ++ goto err; ++ } ++ ++ /* The buffer length has to be greater than fraglen * 20 or else logic in ++ * dlls/dsound/mixer.c fails to correctly understand buffer wrap around. */ ++ That->fraglen = fragment_length(&That->sample_spec); ++ That->buffer_length = That->fraglen * 32; ++ That->buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, That->buffer_length); ++ if (!That->buffer) { ++ ret = DSERR_OUTOFMEMORY; ++ goto err; ++ } ++ That->buffer_read_offset = 0; ++ That->buffer_play_offset = 0; ++ ++ pa_threaded_mainloop_lock(PULSE_ml); ++ ret = DSPULSE_ConnectStream(That); ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ ++ if (ret != DS_OK) ++ goto err; ++ ++ *pdwcbBufferSize = That->buffer_length; ++ *ppbBuffer = That->buffer; ++ ++ TRACE("Exiting with success. Have buffer %p of length %u\n", That->buffer, That->buffer_length); ++ return DS_OK; ++ ++err: ++ pa_threaded_mainloop_lock(PULSE_ml); ++ if (That->stream) { ++ if (pa_stream_get_state(That->stream) == PA_STREAM_READY) ++ pa_stream_disconnect(That->stream); ++ pa_stream_unref(That->stream); ++ That->stream = NULL; ++ } ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ HeapFree(GetProcessHeap(), 0, That->buffer); ++ HeapFree(GetProcessHeap(), 0, *ippdsdb); ++ WARN("exiting with failure.\n"); ++ return ret; ++} ++ ++static HRESULT WINAPI IDsDriverImpl_QueryInterface(PIDSDRIVER iface, REFIID riid, LPVOID *ppobj) { ++ /* IDsDriverImpl *This = (IDsDriverImpl *)iface; */ ++ FIXME("(%p): stub!\n",iface); ++ return DSERR_UNSUPPORTED; ++} ++ ++static ULONG WINAPI IDsDriverImpl_AddRef(PIDSDRIVER iface) { ++ IDsDriverImpl *This = (IDsDriverImpl *)iface; ++ ULONG refCount = InterlockedIncrement(&This->ref); ++ ++ TRACE("(%p)->(ref before=%u)\n",This, refCount - 1); ++ ++ return refCount; ++} ++ ++static ULONG WINAPI IDsDriverImpl_Release(PIDSDRIVER iface) { ++ IDsDriverImpl *This = (IDsDriverImpl *)iface; ++ ULONG refCount = InterlockedDecrement(&This->ref); ++ ++ TRACE("(%p)->(ref before=%u)\n",This, refCount + 1); ++ ++ if (refCount) ++ return refCount; ++ ++ HeapFree(GetProcessHeap(), 0, This); ++ return 0; ++} ++ ++static HRESULT WINAPI IDsDriverImpl_GetDriverDesc(PIDSDRIVER iface, PDSDRIVERDESC pDesc) { ++ IDsDriverImpl *This = (IDsDriverImpl *)iface; ++ TRACE("(%p,%p)\n",iface,pDesc); ++ *pDesc = WOutDev[This->wDevID].ds_desc; ++ pDesc->dwFlags = DSDDESC_DONTNEEDSECONDARYLOCK | DSDDESC_DONTNEEDPRIMARYLOCK; ++ pDesc->dnDevNode = 0; /*TODO: Bwah? */ ++ pDesc->wVxdId = 0; ++ pDesc->wReserved = 0; ++ pDesc->ulDeviceNum = This->wDevID; ++ pDesc->dwHeapType = DSDHEAP_NOHEAP; ++ pDesc->pvDirectDrawHeap = NULL; ++ pDesc->dwMemStartAddress = 0xDEAD0000; ++ pDesc->dwMemEndAddress = 0xDEAF0000; ++ pDesc->dwMemAllocExtra = 0; ++ pDesc->pvReserved1 = NULL; ++ pDesc->pvReserved2 = NULL; ++ return DS_OK; ++} ++ ++static HRESULT WINAPI IDsDriverImpl_Close(PIDSDRIVER iface) { ++ IDsDriverImpl *This = (IDsDriverImpl *)iface; ++ TRACE("(%p) stub, harmless\n",This); ++ return DS_OK; ++} ++ ++static HRESULT WINAPI IDsDriverImpl_Open(PIDSDRIVER iface) { ++ IDsDriverImpl *This = (IDsDriverImpl *)iface; ++ TRACE("(%p) stub, harmless\n",This); ++ return S_OK; ++} ++ ++static HRESULT WINAPI IDsDriverImpl_GetCaps(PIDSDRIVER iface, PDSDRIVERCAPS pCaps) { ++ IDsDriverImpl *This = (IDsDriverImpl *)iface; ++ ++ if (pa_context_get_state(PULSE_context) != PA_CONTEXT_READY) { ++ ERR("Context failure.\n"); ++ return DSERR_GENERIC; ++ } ++ ++ TRACE("(%p,%p)\n",iface,pCaps); ++ *pCaps = WOutDev[This->wDevID].ds_caps; ++ return DS_OK; ++} ++ ++static HRESULT WINAPI IDsDriverImpl_DuplicateSoundBuffer(PIDSDRIVER iface, ++ PIDSDRIVERBUFFER pBuffer, ++ LPVOID *ppvObj) ++{ ++ IDsDriverImpl *This = (IDsDriverImpl *)iface; ++ FIXME("(%p,%p): stub\n",This,pBuffer); ++ return DSERR_INVALIDCALL; ++} ++ ++static const IDsDriverVtbl dsdvt = ++{ ++ IDsDriverImpl_QueryInterface, ++ IDsDriverImpl_AddRef, ++ IDsDriverImpl_Release, ++ IDsDriverImpl_GetDriverDesc, ++ IDsDriverImpl_Open, ++ IDsDriverImpl_Close, ++ IDsDriverImpl_GetCaps, ++ IDsDriverImpl_CreateSoundBuffer, ++ IDsDriverImpl_DuplicateSoundBuffer ++}; ++ ++DWORD wodDsCreate(UINT wDevID, PIDSDRIVER* drv) { ++ IDsDriverImpl** idrv = (IDsDriverImpl**)drv; ++ ++ if (pa_context_get_state(PULSE_context) != PA_CONTEXT_READY || pa_context_is_local(PULSE_context) != 1) { ++ WARN("Connection failure or server is not local, falling back to WaveOut HEL.\n"); ++ return MMSYSERR_NOTSUPPORTED; ++ } ++ ++ *idrv = HeapAlloc(GetProcessHeap(), 0, sizeof(IDsDriverImpl)); ++ if (!*idrv) ++ return MMSYSERR_NOMEM; ++ ++ TRACE("IDsDriverImpl %p created.\n", *idrv); ++ ++ (*idrv)->lpVtbl = &dsdvt; ++ (*idrv)->ref = 1; ++ (*idrv)->wDevID = wDevID; ++ (*idrv)->primary = NULL; ++ ++ return MMSYSERR_NOERROR; ++} ++ ++DWORD wodDsDesc(UINT wDevID, PDSDRIVERDESC desc) { ++ TRACE("(%u, %p)\n", wDevID, desc); ++ *desc = WOutDev[wDevID].ds_desc; ++ return MMSYSERR_NOERROR; ++} ++#endif /* HAVE_PULSEAUDIO */ +diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c +new file mode 100644 +index 0000000..b68fb05 +--- /dev/null ++++ b/dlls/winepulse.drv/pulse.c +@@ -0,0 +1,770 @@ ++/* ++ * Wine Driver for PulseAudio ++ * http://pulseaudio.org/ ++ * ++ * Copyright 2009 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 "winreg.h" ++#include "mmddk.h" ++#include "ks.h" ++#include "ksguid.h" ++#include "ksmedia.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); ++ ++/* 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; ++ } ++ /* 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_SetupFormat ++ * ++ * Checks to see if the audio format in wf is supported, and if so set up the ++ * pa_sample_spec at ss to that format. ++ */ ++BOOL PULSE_SetupFormat(LPWAVEFORMATEX wf, pa_sample_spec *ss) { ++ WAVEFORMATEXTENSIBLE *wfex; ++ ++ ss->channels = wf->nChannels; ++ ss->rate = wf->nSamplesPerSec; ++ ss->format = PA_SAMPLE_INVALID; ++ ++ if (ss->rate < DSBFREQUENCY_MIN || ss->rate > DSBFREQUENCY_MAX) return FALSE; ++ ++ switch (wf->wFormatTag) { ++ case WAVE_FORMAT_PCM: ++ /* MSDN says that for WAVE_FORMAT_PCM, nChannels must be 1 or 2 and ++ * wBitsPerSample must be 8 or 16, yet other values are used by some ++ * applications in the wild for surround. */ ++ if (ss->channels > 6 || ss->channels < 1) return FALSE; ++ ss->format = wf->wBitsPerSample == 8 ? PA_SAMPLE_U8 ++ : wf->wBitsPerSample == 16 ? PA_SAMPLE_S16NE ++#if PA_PROTOCOL_VERSION >= 15 ++ : wf->wBitsPerSample == 24 ? PA_SAMPLE_S24NE ++#endif ++ : wf->wBitsPerSample == 32 ? PA_SAMPLE_S32NE ++ : PA_SAMPLE_INVALID; ++ break; ++ ++ case WAVE_FORMAT_MULAW: ++ if (wf->wBitsPerSample == 8) ss->format = PA_SAMPLE_ULAW; ++ break; ++ ++ case WAVE_FORMAT_ALAW: ++ if (wf->wBitsPerSample == 8) ss->format = PA_SAMPLE_ALAW; ++ break; ++ ++ case WAVE_FORMAT_EXTENSIBLE: ++ if (wf->cbSize > 22) return FALSE; ++ if (ss->channels < 1 || ss->channels > 6) return FALSE; ++ wfex = (WAVEFORMATEXTENSIBLE *)wf; ++ if (IsEqualGUID(&wfex->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM)) { ++ if (wf->wBitsPerSample == wfex->Samples.wValidBitsPerSample) { ++ ss->format = wf->wBitsPerSample == 8 ? PA_SAMPLE_U8 ++ : wf->wBitsPerSample == 16 ? PA_SAMPLE_S16NE ++#if PA_PROTOCOL_VERSION >= 15 ++ : wf->wBitsPerSample == 24 ? PA_SAMPLE_S24NE ++#endif ++ : wf->wBitsPerSample == 32 ? PA_SAMPLE_S32NE ++ : PA_SAMPLE_INVALID; ++ } else { ++#if PA_PROTOCOL_VERSION >= 15 ++ if (wf->wBitsPerSample == 32 && wfex->Samples.wValidBitsPerSample == 24) ++ ss->format = PA_SAMPLE_S24_32NE; ++ else ++#endif ++ return FALSE; ++ } ++ } else if (wf->wBitsPerSample != wfex->Samples.wValidBitsPerSample) { ++ return FALSE; ++ } else if ((IsEqualGUID(&wfex->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))) { ++ ss->format = PA_SAMPLE_FLOAT32NE; ++ } else { ++ WARN("only KSDATAFORMAT_SUBTYPE_PCM and KSDATAFORMAT_SUBTYPE_IEEE_FLOAT of WAVE_FORMAT_EXTENSIBLE supported\n"); ++ return FALSE; ++ } ++ break; ++ ++ default: ++ WARN("only WAVE_FORMAT_PCM, WAVE_FORMAT_MULAW, WAVE_FORMAT_ALAW and WAVE_FORMAT_EXTENSIBLE supported\n"); ++ return FALSE; ++ } ++ ++ if (!pa_sample_spec_valid(ss)) return FALSE; ++ if (wf->nBlockAlign != pa_frame_size(ss)) { ++ ERR("wf->nBlockAlign != the format frame size!\n"); ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ ++/************************************************************************** ++ * PULSE_WaitForOperation ++ * ++ * Waits for pa operations to complete, and dereferences the operation. ++ */ ++void PULSE_WaitForOperation(pa_operation *o) { ++ if (!o) return; ++ ++ for (;;) { ++ if (pa_operation_get_state(o) != PA_OPERATION_RUNNING) ++ break; ++ pa_threaded_mainloop_wait(PULSE_ml); ++ } ++ pa_operation_unref(o); ++} ++ ++/************************************************************************** ++ * Common Callbacks ++ */ ++ ++/************************************************************************** ++ * PULSE_StreamSuspendedCallback [internal] ++ * ++ * Called by the pulse mainloop any time stream playback is intentionally ++ * suspended or resumed from being suspended. ++ */ ++void PULSE_StreamSuspendedCallback(pa_stream *s, void *userdata) { ++ WINE_WAVEINST *wwo = (WINE_WAVEINST*)userdata; ++ assert(s && wwo); ++ ++ /* Currently we handle this kinda like an underrun. Perhaps we should ++ * tell the client somehow so it doesn't just hang? */ ++ ++ if (!pa_stream_is_suspended(s) && wwo->hThread != INVALID_HANDLE_VALUE && wwo->msgRing.ring_buffer_size > 0) ++ PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_XRUN, 0, FALSE); ++} ++ ++/************************************************************************** ++ * PULSE_StreamUnderflowCallback [internal] ++ * ++ * Called by the pulse mainloop when the prebuf runs out of data. ++ */ ++void PULSE_StreamUnderflowCallback(pa_stream *s, void *userdata) { ++ WINE_WAVEINST *wwo = (WINE_WAVEINST*)userdata; ++ assert(s && wwo); ++ ++ /* If we aren't playing, don't care ^_^ */ ++ if (wwo->state != WINE_WS_PLAYING) return; ++ ++ TRACE("Underrun occurred.\n"); ++ ++ if (wwo->hThread != INVALID_HANDLE_VALUE && wwo->msgRing.ring_buffer_size > 0); ++ PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_XRUN, 0, FALSE); ++} ++ ++/************************************************************************** ++ * PULSE_StreamOverflowCallback [internal] ++ * ++ * Called by the pulse mainloop when the write buffer was overflowed and data ++ * was lost. ++ */ ++void PULSE_StreamOverflowCallback(pa_stream *s, void *userdata) { ++ WINE_WAVEINST *wwo = (WINE_WAVEINST*)userdata; ++ assert(s && wwo); ++ ++ ERR("Overflow occurred!?!\n"); ++ ++ if (wwo->hThread != INVALID_HANDLE_VALUE && wwo->msgRing.ring_buffer_size > 0); ++ PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_XRUN, 1, FALSE); ++} ++ ++/************************************************************************** ++ * PULSE_StreamMovedCallback [internal] ++ * ++ * Called by the pulse mainloop when the stream gets moved, resulting in ++ * possibly different metrics. ++ */ ++void PULSE_StreamMovedCallback(pa_stream *s, void *userdata) { ++ FIXME("stub"); ++} ++ ++ ++/****************************************************************** ++ * PULSE_StreamStateCallback ++ * ++ * Called by pulse whenever the state of the stream changes. ++ * ++ * void *userdata is either the dsound or wave instance ++ */ ++void PULSE_StreamStateCallback(pa_stream *s, void *userdata) { ++ assert(s); ++ ++ switch (pa_stream_get_state(s)) { ++ case PA_STREAM_READY: ++ TRACE("Stream %p ready\n", userdata); ++ break; ++ ++ case PA_STREAM_TERMINATED: ++ TRACE("Stream %p terminated\n", userdata); ++ break; ++ ++ case PA_STREAM_FAILED: ++ ERR("Stream %p failed!\n", userdata); ++ break; ++ ++ case PA_STREAM_UNCONNECTED: ++ case PA_STREAM_CREATING: ++ return; ++ } ++ pa_threaded_mainloop_signal(PULSE_ml, 0); ++} ++ ++/************************************************************************** ++ * PULSE_StreamSucessCallback ++ */ ++void PULSE_StreamSuccessCallback(pa_stream *s, int success, void *userdata) { ++ if (!success) ++ WARN("Stream %p operation failed: %s\n", userdata, pa_strerror(pa_context_errno(PULSE_context))); ++ ++ pa_threaded_mainloop_signal(PULSE_ml, 0); ++} ++ ++/************************************************************************** ++ * PULSE_ContextSuccessCallback ++ */ ++void PULSE_ContextSuccessCallback(pa_context *c, int success, void *userdata) { ++ if (!success) ERR("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_ContextStateCallback [internal] ++ */ ++static void PULSE_ContextStateCallback(pa_context *c, void *userdata) { ++ assert(c); ++ ++ switch (pa_context_get_state(c)) { ++ case PA_CONTEXT_CONNECTING: ++ case PA_CONTEXT_UNCONNECTED: ++ 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: ++ ERR("Context failure: %s\n", pa_strerror(pa_context_errno(c))); ++ pa_threaded_mainloop_signal(PULSE_ml, 0); ++ break; ++ } ++} ++ ++/************************************************************************** ++ * PULSE_AllocateWaveinDevice [internal] ++ * ++ * Creates or adds a device to WInDev based on the pa_source_info. ++ */ ++static void PULSE_AllocateWaveinDevice(const char *name, const char *device, const char *description, const pa_cvolume *v) { ++ WINE_WAVEDEV *wdi; ++ ++ if (WInDev) ++ wdi = HeapReAlloc(GetProcessHeap(), 0, WInDev, sizeof(WINE_WAVEDEV) * (PULSE_WidNumDevs + 1)); ++ else ++ wdi = HeapAlloc(GetProcessHeap(), 0, sizeof(WINE_WAVEDEV)); ++ ++ if (!wdi) return; ++ ++ WInDev = wdi; ++ wdi = &WInDev[PULSE_WidNumDevs++]; ++ memset(wdi, 0, sizeof(WINE_WAVEDEV)); ++ memset(&(wdi->caps.in), 0, sizeof(wdi->caps.in)); ++ snprintf(wdi->interface_name, MAXPNAMELEN * 2, "winepulse: %s", name); ++ wdi->device_name = pa_xstrdup(device); ++ strcpy(wdi->interface_name, "winepulse: "); ++ MultiByteToWideChar(CP_ACP, 0, description, -1, wdi->caps.in.szPname, sizeof(wdi->caps.in.szPname)/sizeof(WCHAR)); ++ wdi->caps.in.szPname[sizeof(wdi->caps.in.szPname)/sizeof(WCHAR) - 1] = '\0'; ++ wdi->caps.in.wMid = MM_CREATIVE; ++ wdi->caps.in.wPid = MM_CREATIVE_SBP16_WAVEOUT; ++ wdi->caps.in.vDriverVersion = 0x0100; ++ wdi->caps.in.wChannels = v->channels == 1 ? 1 : 2; ++ wdi->caps.in.dwFormats = PULSE_ALL_FORMATS; ++ memset(&wdi->ds_desc, 0, sizeof(DSDRIVERDESC)); ++ memcpy(wdi->ds_desc.szDesc, description, min(sizeof(wdi->ds_desc.szDesc) - 1, strlen(description))); ++ memcpy(wdi->ds_desc.szDrvname, "winepulse.drv", 14); ++ wdi->ds_caps.dwMinSecondarySampleRate = DSBFREQUENCY_MIN; ++ wdi->ds_caps.dwMaxSecondarySampleRate = DSBFREQUENCY_MAX; ++ wdi->ds_caps.dwPrimaryBuffers = 1; ++ wdi->ds_caps.dwFlags = \ ++ DSCAPS_PRIMARYMONO | ++ DSCAPS_PRIMARYSTEREO | ++ DSCAPS_PRIMARY8BIT | ++ DSCAPS_PRIMARY16BIT | ++ DSCAPS_SECONDARYMONO | ++ DSCAPS_SECONDARYSTEREO | ++ DSCAPS_SECONDARY8BIT | ++ DSCAPS_SECONDARY16BIT | ++ DSCCAPS_MULTIPLECAPTURE | ++ DSCAPS_CERTIFIED | /* Useful? */ ++ DSCAPS_EMULDRIVER; /* Useful? */ ++ ++} ++ ++/************************************************************************** ++ * PULSE_AllocateWaveoutDevice [internal] ++ * ++ * Creates or adds a sink to the WOutDev array. ++ */ ++static void PULSE_AllocateWaveoutDevice(const char *name, const char *device, const char *description, const pa_cvolume *v) { ++ WINE_WAVEDEV *wdo; ++ int x; ++ ++ if (WOutDev) ++ wdo = HeapReAlloc(GetProcessHeap(), 0, WOutDev, sizeof(WINE_WAVEDEV) * (PULSE_WodNumDevs + 1)); ++ else ++ wdo = HeapAlloc(GetProcessHeap(), 0, sizeof(WINE_WAVEDEV)); ++ ++ if (!wdo) return; ++ ++ WOutDev = wdo; ++ wdo = &WOutDev[PULSE_WodNumDevs++]; ++ memset(wdo, 0, sizeof(WINE_WAVEDEV)); ++ ++ wdo->device_name = pa_xstrdup(device); ++ wdo->volume.channels = v->channels; ++ for (x = 0; x < v->channels; x++) wdo->volume.values[x] = v->values[x]; ++ snprintf(wdo->interface_name, MAXPNAMELEN * 2, "winepulse: %s", name); ++ MultiByteToWideChar(CP_ACP, 0, description, -1, wdo->caps.out.szPname, sizeof(wdo->caps.out.szPname)/sizeof(WCHAR)); ++ wdo->caps.out.szPname[sizeof(wdo->caps.out.szPname)/sizeof(WCHAR) - 1] = '\0'; ++ wdo->caps.out.wMid = MM_CREATIVE; ++ wdo->caps.out.wPid = MM_CREATIVE_SBP16_WAVEOUT; ++ wdo->caps.out.vDriverVersion = 0x0100; ++ wdo->caps.out.dwSupport = WAVECAPS_VOLUME | WAVECAPS_SAMPLEACCURATE | WAVECAPS_DIRECTSOUND; ++ if (v->channels >= 2) { ++ wdo->caps.out.wChannels = 2; ++ wdo->caps.out.dwSupport |= WAVECAPS_LRVOLUME; ++ } else ++ wdo->caps.out.wChannels = 1; ++ wdo->caps.out.dwFormats = PULSE_ALL_FORMATS; ++ memset(&wdo->ds_desc, 0, sizeof(DSDRIVERDESC)); ++ memcpy(wdo->ds_desc.szDesc, description, min(sizeof(wdo->ds_desc.szDesc) - 1, strlen(description))); ++ memcpy(wdo->ds_desc.szDrvname, "winepulse.drv", 14); ++ wdo->ds_caps.dwMinSecondarySampleRate = DSBFREQUENCY_MIN; ++ wdo->ds_caps.dwMaxSecondarySampleRate = DSBFREQUENCY_MAX; ++ wdo->ds_caps.dwPrimaryBuffers = 1; ++ wdo->ds_caps.dwFlags = \ ++ DSCAPS_PRIMARYMONO | ++ DSCAPS_PRIMARYSTEREO | ++ DSCAPS_PRIMARY8BIT | ++ DSCAPS_PRIMARY16BIT | ++ DSCAPS_SECONDARYMONO | ++ DSCAPS_SECONDARYSTEREO | ++ DSCAPS_SECONDARY8BIT | ++ DSCAPS_SECONDARY16BIT | ++ DSCAPS_CERTIFIED; ++} ++ ++/************************************************************************** ++ * PULSE_SourceInfoCallback [internal] ++ */ ++static void PULSE_SourceInfoCallback(pa_context *c, const pa_source_info *i, int eol, void *userdata) { ++ ++ if (!eol && i) ++ PULSE_AllocateWaveinDevice(i->name, i->name, i->description, &i->volume); ++ ++ pa_threaded_mainloop_signal(PULSE_ml, 0); ++} ++ ++/************************************************************************** ++ * PULSE_SinkInfoCallback [internal] ++ */ ++static void PULSE_SinkInfoCallback(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { ++ ++ if (!eol && i) ++ PULSE_AllocateWaveoutDevice(i->name, i->name, i->description, &i->volume); ++ ++ pa_threaded_mainloop_signal(PULSE_ml, 0); ++} ++ ++/************************************************************************** ++ * PULSE_ContextNotifyCallback [internal] ++ */ ++static void PULSE_ContextNotifyCallback(pa_context *c, void *userdata) { ++ pa_threaded_mainloop_signal(PULSE_ml, 0); ++} ++ ++/************************************************************************** ++ * PULSE_WaveClose [internal] ++ * ++ * Disconnect from the server, deallocated the WaveIn/WaveOut devices, stop and ++ * free the mainloop. ++ */ ++ ++static LONG PULSE_WaveClose(void) { ++ int x; ++ TRACE("()\n"); ++ if (!PULSE_ml) return DRV_FAILURE; ++ ++ pa_threaded_mainloop_lock(PULSE_ml); ++ /* device_name is allocated with pa_xstrdup, free with pa_xfree */ ++ for (x = 0; x < PULSE_WodNumDevs; x++) pa_xfree(WOutDev[x].device_name); ++ for (x = 0; x < PULSE_WidNumDevs; x++) pa_xfree(WInDev[x].device_name); ++ HeapFree(GetProcessHeap(), 0, WOutDev); ++ HeapFree(GetProcessHeap(), 0, WInDev); ++ if (PULSE_context) { ++ PULSE_WaitForOperation(pa_context_drain(PULSE_context, PULSE_ContextNotifyCallback, NULL)); ++ pa_context_disconnect(PULSE_context); ++ pa_context_unref(PULSE_context); ++ PULSE_context = NULL; ++ } ++ ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ pa_threaded_mainloop_stop(PULSE_ml); ++ pa_threaded_mainloop_free(PULSE_ml); ++ PULSE_ml = NULL; ++ ++ return DRV_SUCCESS; ++} ++ ++/************************************************************************** ++ * PULSE_WaveInit [internal] ++ * ++ * Connects to the pulseaudio server, tries to discover sinks and sources and ++ * allocates the WaveIn/WaveOut devices. ++ */ ++static LONG PULSE_WaveInit(void) { ++ char *app_name; ++ char path[PATH_MAX]; ++ char *offset = NULL; ++ int x = 0; ++ pa_cvolume fake_cvolume; ++ ++ 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; ++ } ++ ++ /* Application name giving to pulse should be unique to the binary so that ++ * pulse-*-restore can be useful */ ++ ++ /* Get binary path, and remove path a-la strrchr */ ++ if (GetModuleFileNameA(NULL, path, PATH_MAX)) ++ offset = strrchr(path, '\\'); ++ ++ if (offset && ++offset && offset < path + PATH_MAX) { ++ app_name = pa_xmalloc(strlen(offset) + 8); ++ snprintf(app_name, strlen(offset) + 8, "WINE [%s]", offset); ++ } else ++ app_name = pa_xstrdup("WINE Application"); ++ ++ TRACE("App name is \"%s\"\n", app_name); ++ ++ pa_threaded_mainloop_start(PULSE_ml); ++ PULSE_context = pa_context_new(pa_threaded_mainloop_get_api(PULSE_ml), app_name); ++ assert(PULSE_context); ++ pa_xfree(app_name); ++ ++ pa_context_set_state_callback(PULSE_context, PULSE_ContextStateCallback, NULL); ++ ++ pa_threaded_mainloop_lock(PULSE_ml); ++ ++ TRACE("libpulse protocol version: %u. API Version %u\n", pa_context_get_protocol_version(PULSE_context), PA_API_VERSION); ++ TRACE("Attempting to connect to pulseaudio server.\n"); ++ if (pa_context_connect(PULSE_context, NULL, 0, NULL) < 0) ++ goto fail; ++ ++ /* Wait for connection */ ++ for (;;) { ++ pa_context_state_t state = pa_context_get_state(PULSE_context); ++ ++ if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) ++ goto fail; ++ ++ if (state == PA_CONTEXT_READY) ++ break; ++ ++ pa_threaded_mainloop_wait(PULSE_ml); ++ } ++ ++ x = pa_context_get_server_protocol_version(PULSE_context); ++ TRACE("Connected to server %s with protocol version: %i.\n", pa_context_get_server(PULSE_context), x); ++ if (x < 14) ++ WARN("Server is old, expect poor latency or buggy-ness!\n"); ++ ++ fake_cvolume.channels = 2; ++ pa_cvolume_reset(&fake_cvolume, 2); ++ /* FIXME Translations? */ ++ PULSE_AllocateWaveoutDevice("default", NULL, "Default", &fake_cvolume); ++ PULSE_AllocateWaveinDevice("default", NULL, "Default", &fake_cvolume); ++ PULSE_WaitForOperation(pa_context_get_sink_info_list(PULSE_context, PULSE_SinkInfoCallback, &PULSE_WodNumDevs)); ++ PULSE_WaitForOperation(pa_context_get_source_info_list(PULSE_context, PULSE_SourceInfoCallback, &PULSE_WidNumDevs)); ++ TRACE("Found %u output and %u input device(s).\n", PULSE_WodNumDevs - 1, PULSE_WidNumDevs - 1); ++ ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ ++ return DRV_SUCCESS; ++ ++fail: ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ ERR("Failed to connect to server\n"); ++ return DRV_FAILURE; ++} ++ ++#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: return PULSE_WaveInit(); ++ 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/wavein.c b/dlls/winepulse.drv/wavein.c +new file mode 100644 +index 0000000..5495b2a +--- /dev/null ++++ b/dlls/winepulse.drv/wavein.c +@@ -0,0 +1,614 @@ ++/* ++ * Wine Driver for PulseAudio - WaveIn Functionality ++ * http://pulseaudio.org/ ++ * ++ * Copyright 2009 Arthur Taylor ++ * ++ * Contains code from other wine multimedia 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 "windef.h" ++#include "winbase.h" ++#include "wingdi.h" ++#include "winuser.h" ++#include "winnls.h" ++#include "mmddk.h" ++ ++#include ++ ++#include "wine/debug.h" ++ ++WINE_DEFAULT_DEBUG_CHANNEL(wave); ++ ++#if HAVE_PULSEAUDIO ++ ++/*======================================================================* ++ * WAVE IN specific PulseAudio Callbacks * ++ *======================================================================*/ ++ ++/************************************************************************** ++* widNotifyClient [internal] ++*/ ++static DWORD widNotifyClient(WINE_WAVEINST* wwi, WORD wMsg, DWORD dwParam1, DWORD dwParam2) { ++ TRACE("wMsg = 0x%04x dwParm1 = %04X dwParam2 = %04X\n", wMsg, dwParam1, dwParam2); ++ ++ switch (wMsg) { ++ case WIM_OPEN: ++ case WIM_CLOSE: ++ case WIM_DATA: ++ if (wwi->wFlags != DCB_NULL && ++ !DriverCallback(wwi->waveDesc.dwCallback, wwi->wFlags, (HDRVR)wwi->waveDesc.hWave, ++ wMsg, wwi->waveDesc.dwInstance, dwParam1, dwParam2)) { ++ WARN("can't notify client !\n"); ++ return MMSYSERR_ERROR; ++ } ++ break; ++ default: ++ FIXME("Unknown callback message %u\n", wMsg); ++ return MMSYSERR_INVALPARAM; ++ } ++ return MMSYSERR_NOERROR; ++} ++ ++/************************************************************************** ++ * widRecorder_NextFragment [internal] ++ * ++ * Gets the next fragment of data from the server. ++ */ ++static size_t widRecorder_NextFragment(WINE_WAVEINST *wwi) { ++ size_t nbytes; ++ ++ pa_stream_peek(wwi->stream, &wwi->buffer, &nbytes); ++ wwi->buffer_length = nbytes; ++ wwi->buffer_read_offset = 0; ++ ++ return nbytes; ++} ++ ++ ++/************************************************************************** ++ * widRecorder_CopyData [internal] ++ * ++ * Copys data from the fragments pulse returns to queued buffers. ++ */ ++static void widRecorder_CopyData(WINE_WAVEINST *wwi) { ++ LPWAVEHDR lpWaveHdr = wwi->lpQueuePtr; ++ size_t nbytes; ++ ++ while (lpWaveHdr && wwi->state == WINE_WS_PLAYING) { ++ ++ nbytes = min(wwi->buffer_length - wwi->buffer_read_offset, lpWaveHdr->dwBufferLength - lpWaveHdr->dwBytesRecorded); ++ if (nbytes == 0) break; ++ ++ TRACE("%u bytes from %p to %p\n", nbytes, (PBYTE)wwi->buffer + wwi->buffer_read_offset, lpWaveHdr->lpData + lpWaveHdr->dwBytesRecorded); ++ memcpy(lpWaveHdr->lpData + lpWaveHdr->dwBytesRecorded, (PBYTE)wwi->buffer + wwi->buffer_read_offset, nbytes); ++ ++ lpWaveHdr->dwBytesRecorded += nbytes; ++ wwi->buffer_read_offset += nbytes; ++ ++ if (wwi->buffer_read_offset == wwi->buffer_length) { ++ pa_threaded_mainloop_lock(PULSE_ml); ++ pa_stream_drop(wwi->stream); ++ if (pa_stream_readable_size(wwi->stream)) ++ widRecorder_NextFragment(wwi); ++ else { ++ wwi->buffer = NULL; ++ wwi->buffer_length = 0; ++ wwi->buffer_read_offset = 0; ++ } ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ } ++ ++ if (lpWaveHdr->dwBytesRecorded == lpWaveHdr->dwBufferLength) { ++ lpWaveHdr->dwFlags &= ~WHDR_INQUEUE; ++ lpWaveHdr->dwFlags |= WHDR_DONE; ++ wwi->lpQueuePtr = lpWaveHdr->lpNext; ++ widNotifyClient(wwi, WIM_DATA, (DWORD)lpWaveHdr, 0); ++ lpWaveHdr = wwi->lpQueuePtr; ++ } ++ } ++} ++ ++/************************************************************************** ++ * widRecorder [internal] ++ */ ++static DWORD CALLBACK widRecorder(LPVOID lpParam) { ++ WINE_WAVEINST *wwi = (WINE_WAVEINST*)lpParam; ++ LPWAVEHDR lpWaveHdr; ++ enum win_wm_message msg; ++ DWORD param; ++ HANDLE ev; ++ DWORD wait; ++ ++ wwi->state = WINE_WS_STOPPED; ++ SetEvent(wwi->hStartUpEvent); ++ ++ for (;;) { ++ ++ if (wwi->state != WINE_WS_PLAYING) { ++ wait = INFINITE; ++ } else { ++ if (wwi->buffer == NULL && pa_stream_readable_size(wwi->stream)) { ++ pa_threaded_mainloop_lock(PULSE_ml); ++ wait = pa_bytes_to_usec(widRecorder_NextFragment(wwi), &wwi->sample_spec)/1000; ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ } ++ } ++ ++ widRecorder_CopyData(wwi); ++ ++ PULSE_WaitRingMessage(&wwi->msgRing, wait); ++ ++ while (PULSE_RetrieveRingMessage(&wwi->msgRing, &msg, ¶m, &ev)) { ++ TRACE("Received %s %x\n", PULSE_getCmdString(msg), param); ++ ++ switch (msg) { ++ case WINE_WM_FEED: ++ SetEvent(ev); ++ break; ++ case WINE_WM_STARTING: ++ wwi->state = WINE_WS_PLAYING; ++ wait = pa_bytes_to_usec(wwi->lpQueuePtr->dwBufferLength, &wwi->sample_spec)/1000; ++ wwi->last_reset = wwi->timing_info->read_index; ++ pa_threaded_mainloop_lock(PULSE_ml); ++ PULSE_WaitForOperation(pa_stream_cork(wwi->stream, 0, PULSE_StreamSuccessCallback, NULL)); ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ SetEvent(ev); ++ break; ++ case WINE_WM_HEADER: ++ lpWaveHdr = (LPWAVEHDR)param; ++ lpWaveHdr->lpNext = 0; ++ ++ /* insert buffer at the end of queue */ ++ { ++ LPWAVEHDR* wh; ++ for (wh = &(wwi->lpQueuePtr); *wh; wh = &((*wh)->lpNext)); ++ *wh = lpWaveHdr; ++ } ++ break; ++ case WINE_WM_STOPPING: ++ if (wwi->state != WINE_WS_STOPPED) { ++ wwi->state = WINE_WS_STOPPED; ++ pa_threaded_mainloop_lock(PULSE_ml); ++ PULSE_WaitForOperation(pa_stream_cork(wwi->stream, 1, PULSE_StreamSuccessCallback, NULL)); ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ ++ /* return current buffer to app */ ++ lpWaveHdr = wwi->lpQueuePtr; ++ if (lpWaveHdr) { ++ LPWAVEHDR lpNext = lpWaveHdr->lpNext; ++ TRACE("stop %p %p\n", lpWaveHdr, lpWaveHdr->lpNext); ++ lpWaveHdr->dwFlags &= ~WHDR_INQUEUE; ++ lpWaveHdr->dwFlags |= WHDR_DONE; ++ wwi->lpQueuePtr = lpNext; ++ widNotifyClient(wwi, WIM_DATA, (DWORD)lpWaveHdr, 0); ++ } ++ } ++ SetEvent(ev); ++ break; ++ case WINE_WM_RESETTING: ++ if (wwi->state != WINE_WS_STOPPED) { ++ wwi->state = WINE_WS_STOPPED; ++ pa_threaded_mainloop_lock(PULSE_ml); ++ PULSE_WaitForOperation(pa_stream_cork(wwi->stream, 1, PULSE_StreamSuccessCallback, NULL)); ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ } ++ ++ /* return all buffers to the app */ ++ for (lpWaveHdr = wwi->lpPlayPtr ? wwi->lpPlayPtr : wwi->lpQueuePtr; lpWaveHdr; lpWaveHdr = wwi->lpQueuePtr) { ++ lpWaveHdr->dwFlags &= ~WHDR_INQUEUE; ++ lpWaveHdr->dwFlags |= WHDR_DONE; ++ wwi->lpQueuePtr = lpWaveHdr->lpNext; ++ widNotifyClient(wwi, WIM_DATA, (DWORD)lpWaveHdr, 0); ++ } ++ ++ SetEvent(ev); ++ break; ++ case WINE_WM_CLOSING: ++ wwi->hThread = 0; ++ if ((DWORD)param == 1) { ++ /* If we are here, the stream failed */ ++ wwi->state = WINE_WS_FAILED; ++ SetEvent(ev); ++ PULSE_DestroyRingMessage(&wwi->msgRing); ++ widNotifyClient(wwi, WIM_CLOSE, 0L, 0L); ++ wwi->lpPlayPtr = wwi->lpQueuePtr = NULL; ++ pa_threaded_mainloop_lock(PULSE_ml); ++ pa_stream_disconnect(wwi->stream); ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ TRACE("Thread exiting because of failure.\n"); ++ ExitThread(1); ++ } ++ wwi->state = WINE_WS_CLOSED; ++ SetEvent(ev); ++ ExitThread(0); ++ /* shouldn't go here */ ++ default: ++ FIXME("unknown message %d\n", msg); ++ break; ++ } /* switch(msg) */ ++ } /* while(PULSE_RetrieveRingMessage()) */ ++ } /* for (;;) */ ++} ++ ++/************************************************************************** ++ * widOpen [internal] ++ */ ++static DWORD widOpen(WORD wDevID, LPDWORD lpdwUser, LPWAVEOPENDESC lpDesc, DWORD dwFlags) { ++ WINE_WAVEDEV *wdi; ++ WINE_WAVEINST *wwi = NULL; ++ DWORD ret = MMSYSERR_NOERROR; ++ ++ TRACE("(%u, %p, %08X);\n", wDevID, lpDesc, dwFlags); ++ if (lpDesc == NULL) { ++ WARN("Invalid Parameter !\n"); ++ return MMSYSERR_INVALPARAM; ++ } ++ ++ if (wDevID >= PULSE_WidNumDevs) { ++ TRACE("Asked for device %d, but only %d known!\n", wDevID, PULSE_WidNumDevs); ++ return MMSYSERR_BADDEVICEID; ++ } ++ wdi = &WInDev[wDevID]; ++ ++ wwi = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WINE_WAVEINST)); ++ if (!wwi) return MMSYSERR_NOMEM; ++ *lpdwUser = (DWORD)wwi; ++ ++ /* check to see if format is supported and make pa_sample_spec struct */ ++ if (!PULSE_SetupFormat(lpDesc->lpFormat, &wwi->sample_spec)) { ++ WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n", ++ lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels, ++ lpDesc->lpFormat->nSamplesPerSec); ++ ret = WAVERR_BADFORMAT; ++ goto exit; ++ } ++ ++ if (TRACE_ON(wave)) { ++ char t[PA_SAMPLE_SPEC_SNPRINT_MAX]; ++ pa_sample_spec_snprint(t, sizeof(t), &wwi->sample_spec); ++ TRACE("Sample spec '%s'\n", t); ++ } ++ ++ if (dwFlags & WAVE_FORMAT_QUERY) { ++ TRACE("Query format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n", ++ lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels, ++ lpDesc->lpFormat->nSamplesPerSec); ++ ret = MMSYSERR_NOERROR; ++ goto exit; ++ } ++ ++ wwi->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK); ++ wwi->waveDesc = *lpDesc; ++ PULSE_InitRingMessage(&wwi->msgRing); ++ ++ wwi->stream = pa_stream_new(PULSE_context, "WaveIn", &wwi->sample_spec, NULL); ++ if (!wwi->stream) { ++ ret = WAVERR_BADFORMAT; ++ goto exit; ++ } ++ ++ pa_stream_set_state_callback(wwi->stream, PULSE_StreamStateCallback, wwi); ++ ++ pa_threaded_mainloop_lock(PULSE_ml); ++ TRACE("Asking to open %s for recording.\n", wdi->device_name); ++ pa_stream_connect_record(wwi->stream, wdi->device_name, NULL, ++ PA_STREAM_START_CORKED | ++ PA_STREAM_AUTO_TIMING_UPDATE | ++ PA_STREAM_INTERPOLATE_TIMING); ++ ++ for (;;) { ++ pa_context_state_t cstate = pa_context_get_state(PULSE_context); ++ pa_stream_state_t sstate = pa_stream_get_state(wwi->stream); ++ ++ if (cstate == PA_CONTEXT_FAILED || cstate == PA_CONTEXT_TERMINATED || ++ sstate == PA_STREAM_FAILED || sstate == PA_STREAM_TERMINATED) { ++ ERR("Failed to connect context object: %s\n", pa_strerror(pa_context_errno(PULSE_context))); ++ ret = MMSYSERR_NODRIVER; ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ goto exit; ++ } ++ ++ if (sstate == PA_STREAM_READY) ++ break; ++ ++ pa_threaded_mainloop_wait(PULSE_ml); ++ } ++ TRACE("(%p)->stream connected for recording.\n", wwi); ++ ++ PULSE_WaitForOperation(pa_stream_update_timing_info(wwi->stream, PULSE_StreamSuccessCallback, wwi)); ++ ++ wwi->timing_info = pa_stream_get_timing_info(wwi->stream); ++ assert(wwi->timing_info); ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ ++ wwi->hStartUpEvent = CreateEventW(NULL, FALSE, FALSE, NULL); ++ wwi->hThread = CreateThread(NULL, 0, widRecorder, (LPVOID)wwi, 0, &(wwi->dwThreadID)); ++ if (wwi->hThread) ++ SetThreadPriority(wwi->hThread, THREAD_PRIORITY_TIME_CRITICAL); ++ else { ++ ERR("Thread creation for the widRecorder failed!\n"); ++ ret = MMSYSERR_NOMEM; ++ goto exit; ++ } ++ WaitForSingleObject(wwi->hStartUpEvent, INFINITE); ++ CloseHandle(wwi->hStartUpEvent); ++ wwi->hStartUpEvent = INVALID_HANDLE_VALUE; ++ ++ return widNotifyClient(wwi, WIM_OPEN, 0L, 0L); ++ ++exit: ++ if (!wwi) ++ return ret; ++ ++ if (wwi->hStartUpEvent != INVALID_HANDLE_VALUE) ++ CloseHandle(wwi->hStartUpEvent); ++ ++ if (wwi->msgRing.ring_buffer_size > 0) ++ PULSE_DestroyRingMessage(&wwi->msgRing); ++ ++ if (wwi->stream) { ++ if (pa_stream_get_state(wwi->stream) == PA_STREAM_READY) ++ pa_stream_disconnect(wwi->stream); ++ pa_stream_unref(wwi->stream); ++ } ++ HeapFree(GetProcessHeap(), 0, wwi); ++ ++ return ret; ++} ++/************************************************************************** ++ * widClose [internal] ++ */ ++static DWORD widClose(WORD wDevID, WINE_WAVEINST *wwi) { ++ DWORD ret; ++ ++ TRACE("(%u, %p);\n", wDevID, wwi); ++ if (wDevID >= PULSE_WidNumDevs) { ++ WARN("Asked for device %d, but only %d known!\n", wDevID, PULSE_WodNumDevs); ++ return MMSYSERR_INVALHANDLE; ++ } else if (!wwi) { ++ WARN("Stream instance invalid.\n"); ++ return MMSYSERR_INVALHANDLE; ++ } ++ ++ if (wwi->state != WINE_WS_FAILED) { ++ if (wwi->lpQueuePtr) { ++ WARN("buffers recording recording !\n"); ++ return WAVERR_STILLPLAYING; ++ } ++ ++ pa_threaded_mainloop_lock(PULSE_ml); ++ if (pa_stream_get_state(wwi->stream) == PA_STREAM_READY) ++ pa_stream_drop(wwi->stream); ++ pa_stream_disconnect(wwi->stream); ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ ++ if (wwi->hThread != INVALID_HANDLE_VALUE) ++ PULSE_AddRingMessage(&wwi->msgRing, WINE_WM_CLOSING, 0, TRUE); ++ ++ PULSE_DestroyRingMessage(&wwi->msgRing); ++ } ++ ret = widNotifyClient(wwi, WIM_CLOSE, 0L, 0L); ++ ++ pa_stream_unref(wwi->stream); ++ TRACE("Deallocating record instance.\n"); ++ HeapFree(GetProcessHeap(), 0, wwi); ++ return ret; ++} ++ ++/************************************************************************** ++ * widAddBuffer [internal] ++ * ++ */ ++static DWORD widAddBuffer(WINE_WAVEINST* wwi, LPWAVEHDR lpWaveHdr, DWORD dwSize) { ++ TRACE("(%p, %p, %08X);\n", wwi, lpWaveHdr, dwSize); ++ ++ if (!wwi || wwi->state == WINE_WS_FAILED) { ++ WARN("Stream instance invalid.\n"); ++ return MMSYSERR_INVALHANDLE; ++ } ++ ++ if (lpWaveHdr->lpData == NULL || !(lpWaveHdr->dwFlags & WHDR_PREPARED)) ++ return WAVERR_UNPREPARED; ++ ++ if (lpWaveHdr->dwFlags & WHDR_INQUEUE) ++ return WAVERR_STILLPLAYING; ++ ++ lpWaveHdr->dwFlags &= ~WHDR_DONE; ++ lpWaveHdr->dwFlags |= WHDR_INQUEUE; ++ lpWaveHdr->dwBytesRecorded = 0; ++ lpWaveHdr->lpNext = 0; ++ ++ PULSE_AddRingMessage(&wwi->msgRing, WINE_WM_HEADER, (DWORD)lpWaveHdr, FALSE); ++ ++ return MMSYSERR_NOERROR; ++} ++ ++/************************************************************************** ++ * widRecorderMessage [internal] ++ */ ++static DWORD widRecorderMessage(WINE_WAVEINST *wwi, enum win_wm_message message) { ++ if (!wwi || wwi->state == WINE_WS_FAILED) { ++ WARN("Stream instance invalid.\n"); ++ return MMSYSERR_INVALHANDLE; ++ } ++ ++ PULSE_AddRingMessage(&wwi->msgRing, message, 0, TRUE); ++ return MMSYSERR_NOERROR; ++} ++ ++/************************************************************************** ++ * widGetPosition [internal] ++ */ ++static DWORD widGetPosition(WINE_WAVEINST *wwi, LPMMTIME lpTime, DWORD uSize) { ++ DWORD bytes, time; ++ if (!wwi || wwi->state == WINE_WS_FAILED) { ++ WARN("Stream instance invalid.\n"); ++ return MMSYSERR_INVALHANDLE; ++ } ++ ++ if (lpTime == NULL) return MMSYSERR_INVALPARAM; ++ ++ bytes = wwi->timing_info->read_index - wwi->last_reset; ++ time = pa_bytes_to_usec(bytes, &wwi->sample_spec) / 1000; ++ ++ switch (lpTime->wType) { ++ case TIME_SAMPLES: ++ lpTime->u.sample = bytes / pa_frame_size(&wwi->sample_spec); ++ TRACE("TIME_SAMPLES=%u\n", lpTime->u.sample); ++ break; ++ case TIME_MS: ++ lpTime->u.ms = time; ++ TRACE("TIME_MS=%u\n", lpTime->u.ms); ++ break; ++ case TIME_SMPTE: ++ lpTime->u.smpte.fps = 30; ++ lpTime->u.smpte.sec = time/1000; ++ lpTime->u.smpte.min = lpTime->u.smpte.sec / 60; ++ lpTime->u.smpte.sec -= 60 * lpTime->u.smpte.min; ++ lpTime->u.smpte.hour = lpTime->u.smpte.min / 60; ++ lpTime->u.smpte.min -= 60 * lpTime->u.smpte.hour; ++ lpTime->u.smpte.frame = time / lpTime->u.smpte.fps * 1000; ++ TRACE("TIME_SMPTE=%02u:%02u:%02u:%02u\n", ++ lpTime->u.smpte.hour, lpTime->u.smpte.min, ++ lpTime->u.smpte.sec, lpTime->u.smpte.frame); ++ break; ++ default: ++ WARN("Format %d not supported, using TIME_BYTES !\n", lpTime->wType); ++ lpTime->wType = TIME_BYTES; ++ /* fall through */ ++ case TIME_BYTES: ++ lpTime->u.cb = bytes; ++ TRACE("TIME_BYTES=%u\n", lpTime->u.cb); ++ break; ++ } ++ return MMSYSERR_NOERROR; ++} ++ ++/************************************************************************** ++ * widGetDevCaps [internal] ++ */ ++static DWORD widGetDevCaps(DWORD wDevID, LPWAVEINCAPSW lpCaps, DWORD dwSize) { ++ TRACE("(%u, %p, %u);\n", wDevID, lpCaps, dwSize); ++ ++ if (lpCaps == NULL) return MMSYSERR_NOTENABLED; ++ ++ if (wDevID >= PULSE_WidNumDevs) { ++ TRACE("Asked for device %d, but only %d known!\n", wDevID, PULSE_WidNumDevs); ++ return MMSYSERR_INVALHANDLE; ++ } ++ ++ memcpy(lpCaps, &(WInDev[wDevID].caps.in), min(dwSize, sizeof(*lpCaps))); ++ return MMSYSERR_NOERROR; ++} ++ ++/************************************************************************** ++ * widGetNumDevs [internal] ++ * Context-sanity check here, as if we respond with 0, WINE will move on ++ * to the next wavein driver. ++ */ ++static DWORD widGetNumDevs() { ++ if (pa_context_get_state(PULSE_context) != PA_CONTEXT_READY) ++ return 0; ++ ++ return PULSE_WidNumDevs; ++} ++ ++/************************************************************************** ++ * widDevInterfaceSize [internal] ++ */ ++static DWORD widDevInterfaceSize(UINT wDevID, LPDWORD dwParam1) { ++ TRACE("(%u, %p)\n", wDevID, dwParam1); ++ ++ *dwParam1 = MultiByteToWideChar(CP_UNIXCP, 0, WInDev[wDevID].interface_name, -1, ++ NULL, 0 ) * sizeof(WCHAR); ++ return MMSYSERR_NOERROR; ++} ++ ++/************************************************************************** ++ * widDevInterface [internal] ++ */ ++static DWORD widDevInterface(UINT wDevID, PWCHAR dwParam1, DWORD dwParam2) { ++ if (dwParam2 >= MultiByteToWideChar(CP_UNIXCP, 0, WInDev[wDevID].interface_name, -1, ++ NULL, 0 ) * sizeof(WCHAR)) ++ { ++ MultiByteToWideChar(CP_UNIXCP, 0, WInDev[wDevID].interface_name, -1, ++ dwParam1, dwParam2 / sizeof(WCHAR)); ++ return MMSYSERR_NOERROR; ++ } ++ return MMSYSERR_INVALPARAM; ++} ++ ++/************************************************************************** ++ * widDsDesc [internal] ++ */ ++DWORD widDsDesc(UINT wDevID, PDSDRIVERDESC desc) ++{ ++ *desc = WInDev[wDevID].ds_desc; ++ return MMSYSERR_NOERROR; ++} ++ ++/************************************************************************** ++ * widMessage (WINEPULSE.@) ++ */ ++DWORD WINAPI PULSE_widMessage(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 WIDM_OPEN: return widOpen (wDevID, (LPDWORD)dwUser, (LPWAVEOPENDESC)dwParam1, dwParam2); ++ case WIDM_CLOSE: return widClose (wDevID, (WINE_WAVEINST*)dwUser); ++ case WIDM_ADDBUFFER: return widAddBuffer ((WINE_WAVEINST*)dwUser, (LPWAVEHDR)dwParam1, dwParam2); ++ case WIDM_PREPARE: return MMSYSERR_NOTSUPPORTED; ++ case WIDM_UNPREPARE: return MMSYSERR_NOTSUPPORTED; ++ case WIDM_GETDEVCAPS: return widGetDevCaps (wDevID, (LPWAVEINCAPSW)dwParam1, dwParam2); ++ case WIDM_GETNUMDEVS: return widGetNumDevs (); ++ case WIDM_GETPOS: return widGetPosition ((WINE_WAVEINST*)dwUser, (LPMMTIME)dwParam1, dwParam2); ++ case WIDM_RESET: return widRecorderMessage((WINE_WAVEINST*)dwUser, WINE_WM_RESETTING); ++ case WIDM_START: return widRecorderMessage((WINE_WAVEINST*)dwUser, WINE_WM_STARTING); ++ case WIDM_STOP: return widRecorderMessage((WINE_WAVEINST*)dwUser, WINE_WM_STOPPING); ++ case DRV_QUERYDEVICEINTERFACESIZE: return widDevInterfaceSize (wDevID, (LPDWORD)dwParam1); ++ case DRV_QUERYDEVICEINTERFACE: return widDevInterface (wDevID, (PWCHAR)dwParam1, dwParam2); ++ case DRV_QUERYDSOUNDIFACE: return MMSYSERR_NOTSUPPORTED; /* Use emulation, as there is no advantage */ ++ case DRV_QUERYDSOUNDDESC: return widDsDesc (wDevID, (PDSDRIVERDESC)dwParam1); ++ default: ++ FIXME("unknown message %d!\n", wMsg); ++ } ++ return MMSYSERR_NOTSUPPORTED; ++} ++ ++#else /* HAVE_PULSEAUDIO */ ++ ++/************************************************************************** ++ * widMessage (WINEPULSE.@) ++ */ ++DWORD WINAPI PULSE_widMessage(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/waveout.c b/dlls/winepulse.drv/waveout.c +new file mode 100644 +index 0000000..b531ee2 +--- /dev/null ++++ b/dlls/winepulse.drv/waveout.c +@@ -0,0 +1,1176 @@ ++/* ++ * Wine Driver for PulseAudio - WaveOut Functionality ++ * http://pulseaudio.org/ ++ * ++ * Copyright 2009 Arthur Taylor ++ * ++ * Contains code from other wine multimedia 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 "windef.h" ++#include "winbase.h" ++#include "wingdi.h" ++#include "winuser.h" ++#include "winnls.h" ++#include "winerror.h" ++#include "mmddk.h" ++#include "mmreg.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 | ++ * +---------+-------------+---------------+---------------------------------+ ++ */ ++ ++/* ++ * - It is currently unknown if pausing in a loop works the same as expected. ++ */ ++ ++/*======================================================================* ++ * WAVE OUT specific PulseAudio Callbacks * ++ *======================================================================*/ ++ ++/************************************************************************** ++ * WAVEOUT_StreamStartedCallback [internal] ++ * ++ * Called by the pulse mainloop whenever stream playback resumes after an ++ * underflow or an initial start. Requires libpulse >= 0.9.11 ++ */ ++#if PA_API_VERSION >= 12 ++static void WAVEOUT_StreamStartedCallback(pa_stream *s, void *userdata) { ++ WINE_WAVEINST *wwo = (WINE_WAVEINST*)userdata; ++ assert(s && wwo); ++ ++ TRACE("Audio flowing.\n"); ++ ++ if (wwo->buffer_attr.tlength == 0) ++ wwo->buffer_attr.tlength = (uint32_t) -1; ++ ++ if (wwo->hThread != INVALID_HANDLE_VALUE && wwo->msgRing.ring_buffer_size) { ++ PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_STARTING, 0, FALSE); ++ } ++} ++#endif ++ ++/************************************************************************** ++ * WAVEOUT_StreamRequestCallback ++ * ++ * Called by the pulse mainloop whenever it wants audio data. ++ */ ++static void WAVEOUT_StreamRequestCallback(pa_stream *s, size_t nbytes, void *userdata) { ++ WINE_WAVEINST *ww = (WINE_WAVEINST*)userdata; ++ ++ TRACE("Asking to be fed %u bytes\n", nbytes); ++ ++ /* Make sure that the player/recorder is running */ ++ if (ww->hThread != INVALID_HANDLE_VALUE && ww->msgRing.messages) { ++ PULSE_AddRingMessage(&ww->msgRing, WINE_WM_FEED, (DWORD)nbytes, FALSE); ++ } ++} ++ ++/************************************************************************** ++ * WAVEOUT_StreamTimingInfoUpdateCallback [internal] ++ * ++ * Called by the pulse mainloop whenever the timing info gets updated, we ++ * use this to send the started signal */ ++static void WAVEOUT_StreamTimingInfoUpdateCallback(pa_stream *s, void *userdata) { ++ WINE_WAVEINST *wwo = (WINE_WAVEINST*)userdata; ++ assert(s && wwo); ++ ++ if (!wwo->is_releasing && wwo->timing_info && wwo->timing_info->playing) { ++ TRACE("Audio flowing.\n"); ++ ++ if (wwo->buffer_attr.tlength == 0) ++ wwo->buffer_attr.tlength = (uint32_t) -1; ++ ++ if (wwo->hThread != INVALID_HANDLE_VALUE && wwo->msgRing.ring_buffer_size) ++ PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_STARTING, 0, FALSE); ++ } ++} ++ ++/************************************************************************** ++ * WAVEOUT_SinkInputInfoCallback [internal] ++ * ++ * Called by the pulse thread. Used for wodGetVolume. ++ */ ++static void WAVEOUT_SinkInputInfoCallback(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { ++ WINE_WAVEINST* wwo = (WINE_WAVEINST*)userdata; ++ if (!eol && i) { ++ for (wwo->volume.channels = 0; wwo->volume.channels != i->volume.channels; wwo->volume.channels++) ++ wwo->volume.values[wwo->volume.channels] = i->volume.values[wwo->volume.channels]; ++ pa_threaded_mainloop_signal(PULSE_ml, 0); ++ } ++} ++ ++/*======================================================================* ++ * "Low level" WAVE OUT implementation * ++ *======================================================================*/ ++ ++/************************************************************************** ++ * wodPlayer_NotifyClient [internal] ++ */ ++static DWORD wodPlayer_NotifyClient(WINE_WAVEINST* wwo, WORD wMsg, DWORD dwParam1, DWORD dwParam2) { ++ TRACE("wMsg = 0x%04x dwParm1 = %04X dwParam2 = %04X\n", wMsg, dwParam1, dwParam2); ++ ++ switch (wMsg) { ++ case WOM_OPEN: ++ case WOM_CLOSE: ++ case WOM_DONE: ++ if (wwo->wFlags != DCB_NULL && ++ !DriverCallback(wwo->waveDesc.dwCallback, wwo->wFlags, (HDRVR)wwo->waveDesc.hWave, ++ wMsg, wwo->waveDesc.dwInstance, dwParam1, dwParam2)) { ++ WARN("can't notify client !\n"); ++ return MMSYSERR_ERROR; ++ } ++ break; ++ default: ++ FIXME("Unknown callback message %u\n", wMsg); ++ return MMSYSERR_INVALPARAM; ++ } ++ return MMSYSERR_NOERROR; ++} ++ ++/************************************************************************** ++ * wodPlayer_BeginWaveHdr [internal] ++ * ++ * Makes the specified lpWaveHdr the currently playing wave header. ++ * If the specified wave header is a begin loop and we're not already in ++ * a loop, setup the loop. ++ */ ++static void wodPlayer_BeginWaveHdr(WINE_WAVEINST* wwo, LPWAVEHDR lpWaveHdr) { ++ wwo->lpPlayPtr = lpWaveHdr; ++ ++ if (!lpWaveHdr) return; ++ ++ if (lpWaveHdr->dwFlags & WHDR_BEGINLOOP) { ++ if (wwo->lpLoopPtr) { ++ WARN("Already in a loop. Discarding loop on this header (%p)\n", lpWaveHdr); ++ } else { ++ TRACE("Starting loop (%dx) with %p\n", lpWaveHdr->dwLoops, lpWaveHdr); ++ wwo->lpLoopPtr = lpWaveHdr; ++ /* Windows does not touch WAVEHDR.dwLoops, ++ * so we need to make an internal copy */ ++ wwo->dwLoops = lpWaveHdr->dwLoops; ++ } ++ } ++ wwo->dwPartialOffset = 0; ++} ++ ++/************************************************************************** ++ * wodPlayer_PlayPtrNext [internal] ++ * ++ * Advance the play pointer to the next waveheader, looping if required. ++ */ ++static LPWAVEHDR wodPlayer_PlayPtrNext(WINE_WAVEINST* wwo) { ++ LPWAVEHDR lpWaveHdr = wwo->lpPlayPtr; ++ ++ wwo->dwPartialOffset = 0; ++ if ((lpWaveHdr->dwFlags & WHDR_ENDLOOP) && wwo->lpLoopPtr) { ++ /* We're at the end of a loop, loop if required */ ++ if (--wwo->dwLoops > 0) { ++ wwo->lpPlayPtr = wwo->lpLoopPtr; ++ } else { ++ /* Handle overlapping loops correctly */ ++ if (wwo->lpLoopPtr != lpWaveHdr && (lpWaveHdr->dwFlags & WHDR_BEGINLOOP)) { ++ FIXME("Correctly handled case ? (ending loop buffer also starts a new loop)\n"); ++ /* shall we consider the END flag for the closing loop or for ++ * the opening one or for both ??? ++ * code assumes for closing loop only ++ */ ++ } else { ++ lpWaveHdr = lpWaveHdr->lpNext; ++ } ++ wwo->lpLoopPtr = NULL; ++ wodPlayer_BeginWaveHdr(wwo, lpWaveHdr); ++ } ++ } else { ++ /* We're not in a loop. Advance to the next wave header */ ++ wodPlayer_BeginWaveHdr(wwo, lpWaveHdr = lpWaveHdr->lpNext); ++ } ++ ++ return lpWaveHdr; ++} ++ ++/************************************************************************** ++ * wodPlayer_CheckReleasing [internal] ++ * ++ * Check to make sure that playback has not stalled ++ */ ++static void wodPlayer_CheckReleasing(WINE_WAVEINST *wwo) { ++ LPWAVEHDR lpWaveHdr = wwo->lpQueuePtr; ++ ++ pa_threaded_mainloop_lock(PULSE_ml); ++ if (!wwo->timing_info->playing && !wwo->is_releasing && lpWaveHdr && !wwo->lpPlayPtr && wwo->state == WINE_WS_PLAYING) { ++ ++ /* Try and adjust the buffer attributes so there is less latency. ++ * Because of bugs this call does not work on older servers. Once ++ * new version of pulseaudio become ubiquitous we will drop support for ++ * versions before 0.9.15 because they have too many bugs.*/ ++ if (wwo->buffer_attr.tlength == 0 && ++ pa_context_get_server_protocol_version(PULSE_context) >= 15) { ++ wwo->buffer_attr.tlength = wwo->timing_info->write_index; ++ wwo->buffer_attr.prebuf = (uint32_t) -1; ++ wwo->buffer_attr.minreq = (uint32_t) -1; ++ wwo->buffer_attr.maxlength = (uint32_t) -1; ++ WARN("Asking for new buffer tlength of %ums (%u bytes)\n", (unsigned int)(pa_bytes_to_usec(wwo->buffer_attr.tlength, &wwo->sample_spec)/1000), wwo->buffer_attr.tlength); ++ pa_stream_set_buffer_attr(wwo->stream, &wwo->buffer_attr, PULSE_StreamSuccessCallback, wwo); ++ } else { ++ /* Fake playback start earlier, introducing unknown latency */ ++ pa_gettimeofday(&wwo->started_releasing); ++ wwo->is_releasing = TRUE; ++ wwo->releasing_offset = wwo->lpQueuePtr->reserved; ++ WARN("Starting to release early.\n"); ++ } ++ } ++ pa_threaded_mainloop_unlock(PULSE_ml); ++} ++ ++/************************************************************************** ++ * wodPlayer_NotifyCompletions [internal] ++ * ++ * Notifies and remove from queue all wavehdrs which have been played to ++ * the speaker based on a reference time of (theoretical) playback start. If ++ * force is true, we notify all wavehdrs and remove them all from the queue ++ * even if they are unplayed or part of a loop. We return the time to wait ++ * until the next wavehdr needs to be freed, or INFINITE if there are no more ++ * wavehdrs. We use timevals rather than the stream position data that pulse ++ * gives us as realeasing needs to be constant and smooth for good application ++ * behaviour. ++ */ ++static DWORD wodPlayer_NotifyCompletions(WINE_WAVEINST* wwo, BOOL force) { ++ LPWAVEHDR lpWaveHdr = wwo->lpQueuePtr; ++ pa_usec_t time; ++ pa_usec_t wait; ++ ++ time = pa_bytes_to_usec(wwo->releasing_offset, &wwo->sample_spec); ++ if (wwo->is_releasing) ++ time += pa_timeval_age(&wwo->started_releasing); ++ ++ while (lpWaveHdr) { ++ if (!force) { ++ /* Start from lpQueuePtr and keep notifying until: ++ * - we hit an unwritten wavehdr ++ * - we hit the beginning of a running loop ++ * - we hit a wavehdr which hasn't finished playing ++ */ ++ if (lpWaveHdr == wwo->lpPlayPtr) { TRACE("play %p\n", lpWaveHdr); return INFINITE; } ++ if (lpWaveHdr == wwo->lpLoopPtr) { TRACE("loop %p\n", lpWaveHdr); return INFINITE; } ++ ++ /* See if this data has been played, and if not, return when it will have been */ ++ wait = pa_bytes_to_usec(lpWaveHdr->reserved + lpWaveHdr->dwBufferLength, &wwo->sample_spec); ++ if (wait >= time) { ++ wait = ((wait - time) + 999) / 1000; ++ return wait ?: 1; ++ } ++ } ++ ++ /* return the wavehdr */ ++ wwo->lpQueuePtr = lpWaveHdr->lpNext; ++ lpWaveHdr->dwFlags &= ~WHDR_INQUEUE; ++ lpWaveHdr->dwFlags |= WHDR_DONE; ++ ++ wodPlayer_NotifyClient(wwo, WOM_DONE, (DWORD)lpWaveHdr, 0); ++ lpWaveHdr = wwo->lpQueuePtr; ++ } ++ /* No more wavehdrs */ ++ TRACE("Empty queue\n"); ++ return INFINITE; ++} ++ ++/************************************************************************** ++ * wodPlayer_WriteMax [internal] ++ * ++ * Write either how much free space or how much data we have, depending on ++ * which is less ++ */ ++static int wodPlayer_WriteMax(WINE_WAVEINST *wwo, size_t *space) { ++ LPWAVEHDR lpWaveHdr = wwo->lpPlayPtr; ++ size_t toWrite = min(lpWaveHdr->dwBufferLength - wwo->dwPartialOffset, *space); ++ ++ if (toWrite > 0 && pa_stream_write(wwo->stream, lpWaveHdr->lpData + wwo->dwPartialOffset, toWrite, NULL, 0, PA_SEEK_RELATIVE) >= 0) ++ TRACE("Writing wavehdr %p.%u[%u]\n", lpWaveHdr, wwo->dwPartialOffset, lpWaveHdr->dwBufferLength); ++ else ++ return 0; ++ ++ /* Check to see if we wrote all of the wavehdr */ ++ if ((wwo->dwPartialOffset += toWrite) >= lpWaveHdr->dwBufferLength) ++ wodPlayer_PlayPtrNext(wwo); ++ ++ *space -= toWrite; ++ ++ return toWrite; ++} ++ ++/************************************************************************** ++ * wodPlayer_Feed [internal] ++ * ++ * Feed as much sound data as we can into pulse using wodPlayer_WriteMax. ++ * size_t space _must_ have come from either pa_stream_writable_size() or ++ * the value from a stream write callback, as if it isn't you run the risk ++ * of a buffer overflow in which audio data will be lost. ++ */ ++static void wodPlayer_Feed(WINE_WAVEINST* wwo, size_t space) { ++ ++ /* No more room... no need to try to feed */ ++ if (space == 0) return; ++ ++ if (!wwo->stream || !PULSE_context || ++ pa_context_get_state(PULSE_context) != PA_CONTEXT_READY || ++ pa_stream_get_state(wwo->stream) != PA_STREAM_READY) ++ return; ++ ++ pa_threaded_mainloop_lock(PULSE_ml); ++ /* Feed from a partial wavehdr */ ++ if (wwo->lpPlayPtr && wwo->dwPartialOffset != 0) ++ wodPlayer_WriteMax(wwo, &space); ++ ++ /* Feed wavehdrs until we run out of wavehdrs or buffer space */ ++ if (wwo->dwPartialOffset == 0 && wwo->lpPlayPtr) { ++ do { ++ wwo->lpPlayPtr->reserved = wwo->timing_info->write_index; ++ } while (wodPlayer_WriteMax(wwo, &space) > 0 && wwo->lpPlayPtr && space > 0); ++ } ++ pa_threaded_mainloop_unlock(PULSE_ml); ++} ++ ++/************************************************************************** ++ * wodPlayer_Reset [internal] ++ * ++ * wodPlayer helper. Resets current output stream. ++ */ ++static void wodPlayer_Reset(WINE_WAVEINST* wwo) { ++ enum win_wm_message msg; ++ DWORD param; ++ HANDLE ev; ++ ++ TRACE("(%p)\n", wwo); ++ ++ /* remove any buffer */ ++ wodPlayer_NotifyCompletions(wwo, TRUE); ++ ++ wwo->lpPlayPtr = wwo->lpQueuePtr = wwo->lpLoopPtr = NULL; ++ if (wwo->state != WINE_WS_PAUSED) ++ wwo->state = WINE_WS_STOPPED; ++ wwo->dwPartialOffset = 0; ++ ++ if (!wwo->stream || ++ !PULSE_context || ++ pa_context_get_state(PULSE_context) != PA_CONTEXT_READY || ++ pa_stream_get_state(wwo->stream) != PA_STREAM_READY) { ++ return; ++ } ++ ++ pa_threaded_mainloop_lock(PULSE_ml); ++ ++ /* flush the output buffer of written data*/ ++ PULSE_WaitForOperation(pa_stream_flush(wwo->stream, PULSE_StreamSuccessCallback, NULL)); ++ ++ /* Reset the written byte count as some data may have been flushed */ ++ if (wwo->timing_info->write_index_corrupt) ++ PULSE_WaitForOperation(pa_stream_update_timing_info(wwo->stream, PULSE_StreamSuccessCallback, wwo)); ++ wwo->releasing_offset = wwo->last_reset = wwo->timing_info->write_index; ++ if (wwo->is_releasing) ++ pa_gettimeofday(&wwo->started_releasing); ++ ++ /* return all pending headers in queue */ ++ EnterCriticalSection(&wwo->msgRing.msg_crst); ++ while (PULSE_RetrieveRingMessage(&wwo->msgRing, &msg, ¶m, &ev)) { ++ if (msg != WINE_WM_HEADER) { ++ SetEvent(ev); ++ continue; ++ } ++ ((LPWAVEHDR)param)->dwFlags &= ~WHDR_INQUEUE; ++ ((LPWAVEHDR)param)->dwFlags |= WHDR_DONE; ++ wodPlayer_NotifyClient(wwo, WOM_DONE, param, 0); ++ } ++ PULSE_ResetRingMessage(&wwo->msgRing); ++ LeaveCriticalSection(&wwo->msgRing.msg_crst); ++ ++ pa_threaded_mainloop_unlock(PULSE_ml); ++} ++ ++/************************************************************************** ++ * wodPlayer_Underrun [internal] ++ * ++ * wodPlayer helper. Deal with a stream underrun. ++ */ ++static void wodPlayer_Underrun(WINE_WAVEINST* wwo) { ++ ++ pa_threaded_mainloop_lock(PULSE_ml); ++ ++ /* Ask for a timing update */ ++ PULSE_WaitForOperation(pa_stream_update_timing_info(wwo->stream, PULSE_StreamSuccessCallback, wwo)); ++ ++ /* See if we recovered while the message was waiting in the queue */ ++ if (wwo->timing_info->playing) { ++ TRACE("False alarm\n"); ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ return; ++ } ++ ++ if (wwo->lpPlayPtr) { ++ size_t space; ++ ++ TRACE("There is queued data. Trying to recover.\n"); ++ ++ /* Ask for a timing update */ ++ if (wwo->timing_info->write_index_corrupt) ++ PULSE_WaitForOperation(pa_stream_update_timing_info(wwo->stream, PULSE_StreamSuccessCallback, wwo)); ++ space = pa_stream_writable_size(wwo->stream); ++ wodPlayer_Feed(wwo, space); ++ } ++ WARN("Stream underrun!\n"); ++ wwo->is_releasing = FALSE; ++ wwo->releasing_offset = wwo->timing_info->write_index; ++ pa_threaded_mainloop_unlock(PULSE_ml); ++} ++ ++ ++/************************************************************************** ++ * wodPlayer_ProcessMessages [internal] ++ */ ++static DWORD wodPlayer_ProcessMessages(WINE_WAVEINST* wwo) { ++ LPWAVEHDR lpWaveHdr; ++ enum win_wm_message msg; ++ DWORD param, msgcount = 0; ++ HANDLE ev; ++ ++ while (PULSE_RetrieveRingMessage(&wwo->msgRing, &msg, ¶m, &ev)) { ++ TRACE("Received %s %x\n", PULSE_getCmdString(msg), param); ++ msgcount++; ++ ++ switch (msg) { ++ case WINE_WM_PAUSING: ++ wwo->state = WINE_WS_PAUSED; ++ ++ pa_threaded_mainloop_lock(PULSE_ml); ++ PULSE_WaitForOperation(pa_stream_cork(wwo->stream, 1, PULSE_StreamSuccessCallback, wwo)); ++ /* save how far we are, as releasing will restart from here */ ++ if (wwo->is_releasing) ++ wwo->releasing_offset = wwo->timing_info->write_index; ++ wwo->is_releasing = FALSE; ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ SetEvent(ev); ++ break; ++ ++ case WINE_WM_RESTARTING: ++ if (wwo->state == WINE_WS_PAUSED) { ++ wwo->state = WINE_WS_PLAYING; ++ pa_threaded_mainloop_lock(PULSE_ml); ++ PULSE_WaitForOperation(pa_stream_cork(wwo->stream, 0, PULSE_StreamSuccessCallback, wwo)); ++ /* If the serverside buffer was near full before pause, we need to ++ * have space to write soon, so force playback start */ ++ PULSE_WaitForOperation(pa_stream_trigger(wwo->stream, PULSE_StreamSuccessCallback, wwo)); ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ } ++ SetEvent(ev); ++ break; ++ ++ case WINE_WM_HEADER: ++ lpWaveHdr = (LPWAVEHDR)param; ++ /* insert buffer at the end of queue */ ++ { ++ LPWAVEHDR* wh; ++ for (wh = &(wwo->lpQueuePtr); *wh; wh = &((*wh)->lpNext)); ++ *wh = lpWaveHdr; ++ } ++ ++ if (!wwo->lpPlayPtr) ++ wodPlayer_BeginWaveHdr(wwo,lpWaveHdr); ++ if (wwo->state == WINE_WS_STOPPED) ++ wwo->state = WINE_WS_PLAYING; ++ ++ wodPlayer_Feed(wwo, pa_stream_writable_size(wwo->stream)); ++ SetEvent(ev); ++ break; ++ ++ case WINE_WM_RESETTING: ++ wodPlayer_Reset(wwo); ++ SetEvent(ev); ++ break; ++ ++ case WINE_WM_BREAKLOOP: ++ if (wwo->state == WINE_WS_PLAYING && wwo->lpLoopPtr != NULL) ++ /* ensure exit at end of current loop */ ++ wwo->dwLoops = 1; ++ SetEvent(ev); ++ break; ++ ++ case WINE_WM_FEED: /* Sent by the pulse thread */ ++ wodPlayer_Feed(wwo, pa_stream_writable_size(wwo->stream)); ++ SetEvent(ev); ++ break; ++ ++ case WINE_WM_XRUN: /* Sent by the pulse thread */ ++ if ((DWORD)param == 1) { ++ ERR("Buffer overflow!\n"); ++ pa_threaded_mainloop_lock(PULSE_ml); ++ PULSE_WaitForOperation(pa_stream_drain(wwo->stream, PULSE_StreamSuccessCallback, wwo)); ++ wwo->is_releasing = FALSE; ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ } else ++ wodPlayer_Underrun(wwo); ++ SetEvent(ev); ++ break; ++ ++ case WINE_WM_STARTING: /* Sent by the pulse thread */ ++ /* Start releasing wavehdrs if we haven't already */ ++ if (!wwo->is_releasing) { ++ wwo->is_releasing = TRUE; ++ pa_gettimeofday(&wwo->started_releasing); ++ } ++ SetEvent(ev); ++ break; ++ ++ case WINE_WM_CLOSING: /* If param = 1, close because of a failure */ ++ wwo->hThread = NULL; ++ if ((DWORD)param == 1) { ++ /* If we are here, the stream has failed */ ++ wwo->state = WINE_WS_FAILED; ++ SetEvent(ev); ++ PULSE_DestroyRingMessage(&wwo->msgRing); ++ wodPlayer_NotifyCompletions(wwo, TRUE); ++ wodPlayer_NotifyClient(wwo, WOM_CLOSE, 0L, 0L); ++ wwo->lpPlayPtr = wwo->lpQueuePtr = wwo->lpLoopPtr = NULL; ++ pa_threaded_mainloop_lock(PULSE_ml); ++ pa_stream_disconnect(wwo->stream); ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ TRACE("Thread exiting because of failure.\n"); ++ ExitThread(1); ++ /* Stream instance will get de-refferenced upon close */ ++ } ++ wwo->state = WINE_WS_CLOSED; ++ /* sanity check: this should not happen since the device must have been reset before */ ++ if (wwo->lpQueuePtr || wwo->lpPlayPtr) ERR("out of sync\n"); ++ SetEvent(ev); ++ TRACE("Thread exiting.\n"); ++ ExitThread(0); ++ /* shouldn't go here */ ++ ++ default: ++ FIXME("unknown message %d\n", msg); ++ break; ++ } ++ } ++ ++ return msgcount; ++} ++ ++/************************************************************************** ++ * wodPlayer [internal] ++ * ++ * The thread which is responsible for returning WaveHdrs via DriverCallback, ++ * the writing of queued WaveHdrs, and all pause / reset stream management. ++ */ ++static DWORD CALLBACK wodPlayer(LPVOID lpParam) { ++ WINE_WAVEINST *wwo = (WINE_WAVEINST*)lpParam; ++ DWORD dwSleepTime = INFINITE; ++ ++ wwo->state = WINE_WS_STOPPED; ++ SetEvent(wwo->hStartUpEvent); ++ ++/* Wait for the shortest time before an action is required. If there are no ++ * pending actions, wait forever for a command. */ ++ for (;;) { ++ TRACE("Waiting %u ms\n", dwSleepTime); ++ PULSE_WaitRingMessage(&wwo->msgRing, dwSleepTime); ++ ++/* If no messages were processed during the timeout it might be because audio ++ * is not flowing yet, so check. */ ++ if (wodPlayer_ProcessMessages(wwo) == 0) ++ wodPlayer_CheckReleasing(wwo); ++ ++/* If there is audio playing, return headers and get next timeout */ ++ if (wwo->state == WINE_WS_PLAYING) ++ dwSleepTime = wodPlayer_NotifyCompletions(wwo, FALSE); ++ else ++ dwSleepTime = INFINITE; ++ } ++} ++ ++/************************************************************************** ++ * wodOpen [internal] ++ */ ++static DWORD wodOpen(WORD wDevID, LPDWORD lpdwUser, LPWAVEOPENDESC lpDesc, DWORD dwFlags) { ++ WINE_WAVEDEV *wdo; ++ WINE_WAVEINST *wwo = NULL; ++ DWORD ret = MMSYSERR_NOERROR; ++ pa_stream_flags_t stream_flags; ++ ++ TRACE("(%u, %p, %08X);\n", wDevID, lpDesc, dwFlags); ++ if (lpDesc == NULL) { ++ WARN("Invalid Parameter !\n"); ++ return MMSYSERR_INVALPARAM; ++ } ++ ++ if (wDevID >= PULSE_WodNumDevs) { ++ WARN("Asked for device %d, but only %d known!\n", wDevID, PULSE_WodNumDevs); ++ return MMSYSERR_BADDEVICEID; ++ } ++ wdo = &WOutDev[wDevID]; ++ ++ wwo = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WINE_WAVEINST)); ++ if (!wwo) return MMSYSERR_NOMEM; ++ *lpdwUser = (DWORD)wwo; ++ ++ /* check to see if format is supported and make pa_sample_spec struct */ ++ if (!PULSE_SetupFormat(lpDesc->lpFormat, &wwo->sample_spec)) { ++ WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n", ++ lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels, ++ lpDesc->lpFormat->nSamplesPerSec); ++ ret = WAVERR_BADFORMAT; ++ goto exit; ++ } ++ ++ if (TRACE_ON(wave)) { ++ char t[PA_SAMPLE_SPEC_SNPRINT_MAX]; ++ pa_sample_spec_snprint(t, sizeof(t), &wwo->sample_spec); ++ TRACE("Sample spec '%s'\n", t); ++ } ++ ++ if (dwFlags & WAVE_FORMAT_QUERY) { ++ TRACE("Query format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n", ++ lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels, ++ lpDesc->lpFormat->nSamplesPerSec); ++ ret = MMSYSERR_NOERROR; ++ goto exit; ++ } ++ ++ wwo->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK); ++ wwo->waveDesc = *lpDesc; ++ PULSE_InitRingMessage(&wwo->msgRing); ++ ++ wwo->stream = pa_stream_new(PULSE_context, "WaveOut", &wwo->sample_spec, NULL); ++ /* If server doesn't support sample_spec, we will error out here */ ++ if (!wwo->stream) { ++ ret = WAVERR_BADFORMAT; ++ goto exit; ++ } ++ ++ pa_stream_set_write_callback (wwo->stream, WAVEOUT_StreamRequestCallback, wwo); ++ pa_stream_set_state_callback (wwo->stream, PULSE_StreamStateCallback, wwo); ++ pa_stream_set_underflow_callback (wwo->stream, PULSE_StreamUnderflowCallback, wwo); ++ pa_stream_set_overflow_callback (wwo->stream, PULSE_StreamOverflowCallback, wwo); ++ pa_stream_set_moved_callback (wwo->stream, PULSE_StreamMovedCallback, wwo); ++ pa_stream_set_suspended_callback (wwo->stream, PULSE_StreamSuspendedCallback, wwo); ++ ++ stream_flags = PA_STREAM_AUTO_TIMING_UPDATE; ++ ++#if PA_API_VERSION >= 12 ++ if (pa_context_get_server_protocol_version(PULSE_context) >= 15) { ++ pa_stream_set_started_callback(wwo->stream, WAVEOUT_StreamStartedCallback, wwo); ++ stream_flags |= PA_STREAM_ADJUST_LATENCY; ++ } else ++#endif ++ { ++ pa_stream_set_latency_update_callback(wwo->stream, WAVEOUT_StreamTimingInfoUpdateCallback, wwo); ++ } ++ ++ TRACE("Connecting stream for playback on %s.\n", wdo->device_name); ++ pa_threaded_mainloop_lock(PULSE_ml); ++ pa_stream_connect_playback(wwo->stream, wdo->device_name, NULL, stream_flags, NULL, NULL); ++ ++ for (;;) { ++ pa_context_state_t cstate = pa_context_get_state(PULSE_context); ++ pa_stream_state_t sstate = pa_stream_get_state(wwo->stream); ++ ++ if (cstate == PA_CONTEXT_FAILED || cstate == PA_CONTEXT_TERMINATED || ++ sstate == PA_STREAM_FAILED || sstate == PA_STREAM_TERMINATED) { ++ ERR("Failed to connect stream context object: %s\n", pa_strerror(pa_context_errno(PULSE_context))); ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ ret = MMSYSERR_NODRIVER; ++ goto exit; ++ } ++ ++ if (sstate == PA_STREAM_READY) ++ break; ++ ++ pa_threaded_mainloop_wait(PULSE_ml); ++ } ++ TRACE("(%p)->stream connected for playback.\n", wwo); ++ ++ PULSE_WaitForOperation(pa_stream_update_timing_info(wwo->stream, PULSE_StreamSuccessCallback, wwo)); ++ ++ wwo->timing_info = pa_stream_get_timing_info(wwo->stream); ++ assert(wwo->timing_info); ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ ++ wwo->hStartUpEvent = CreateEventW(NULL, FALSE, FALSE, NULL); ++ wwo->hThread = CreateThread(NULL, 0, wodPlayer, (LPVOID)wwo, 0, &(wwo->dwThreadID)); ++ if (wwo->hThread) ++ SetThreadPriority(wwo->hThread, THREAD_PRIORITY_TIME_CRITICAL); ++ else { ++ ERR("Thread creation for the wodPlayer failed!\n"); ++ ret = MMSYSERR_NOMEM; ++ goto exit; ++ } ++ WaitForSingleObject(wwo->hStartUpEvent, INFINITE); ++ CloseHandle(wwo->hStartUpEvent); ++ wwo->hStartUpEvent = INVALID_HANDLE_VALUE; ++ ++ ++ return wodPlayer_NotifyClient (wwo, WOM_OPEN, 0L, 0L); ++ ++exit: ++ if (!wwo) ++ return ret; ++ ++ if (wwo->hStartUpEvent != INVALID_HANDLE_VALUE) ++ CloseHandle(wwo->hStartUpEvent); ++ ++ if (wwo->msgRing.ring_buffer_size > 0) ++ PULSE_DestroyRingMessage(&wwo->msgRing); ++ ++ if (wwo->stream) { ++ if (pa_stream_get_state(wwo->stream) == PA_STREAM_READY) ++ pa_stream_disconnect(wwo->stream); ++ pa_stream_unref(wwo->stream); ++ wwo->stream = NULL; ++ } ++ HeapFree(GetProcessHeap(), 0, wwo); ++ ++ return ret; ++} ++ ++/************************************************************************** ++ * wodClose [internal] ++ */ ++static DWORD wodClose(WINE_WAVEINST *wwo) { ++ DWORD ret; ++ ++ TRACE("(%p);\n", wwo); ++ if (!wwo) { ++ WARN("Stream instance invalid.\n"); ++ return MMSYSERR_INVALHANDLE; ++ } ++ ++ if (wwo->state != WINE_WS_FAILED) { ++ if (wwo->lpQueuePtr && wwo->lpPlayPtr) { ++ WARN("buffers still playing !\n"); ++ return WAVERR_STILLPLAYING; ++ } ++ ++ pa_threaded_mainloop_lock(PULSE_ml); ++ PULSE_WaitForOperation(pa_stream_drain(wwo->stream, PULSE_StreamSuccessCallback, NULL)); ++ pa_stream_disconnect(wwo->stream); ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ ++ if (wwo->hThread != INVALID_HANDLE_VALUE) ++ PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_CLOSING, 0, TRUE); ++ ++ PULSE_DestroyRingMessage(&wwo->msgRing); ++ } ++ ++ if (wwo->stream) ++ pa_stream_unref(wwo->stream); ++ ret = wodPlayer_NotifyClient(wwo, WOM_CLOSE, 0L, 0L); ++ ++ HeapFree(GetProcessHeap(), 0, wwo); ++ ++ return ret; ++} ++ ++/************************************************************************** ++ * wodWrite [internal] ++ */ ++static DWORD wodWrite(WINE_WAVEINST *wwo, LPWAVEHDR lpWaveHdr, DWORD dwSize) { ++ if (!wwo || wwo->state == WINE_WS_FAILED) { ++ WARN("Stream instance invalid.\n"); ++ return MMSYSERR_INVALHANDLE; ++ } ++ ++ if (lpWaveHdr->lpData == NULL || !(lpWaveHdr->dwFlags & WHDR_PREPARED)) ++ return WAVERR_UNPREPARED; ++ ++ if (lpWaveHdr->dwFlags & WHDR_INQUEUE) ++ return WAVERR_STILLPLAYING; ++ ++ lpWaveHdr->dwFlags &= ~WHDR_DONE; ++ lpWaveHdr->dwFlags |= WHDR_INQUEUE; ++ lpWaveHdr->lpNext = 0; ++ lpWaveHdr->reserved = 0; ++ ++ PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_HEADER, (DWORD)lpWaveHdr, FALSE); ++ return MMSYSERR_NOERROR; ++} ++ ++/************************************************************************** ++ * wodPause [internal] ++ */ ++static DWORD wodPause(WINE_WAVEINST *wwo) { ++ if (!wwo || wwo->state == WINE_WS_FAILED) { ++ WARN("Stream instance invalid.\n"); ++ return MMSYSERR_INVALHANDLE; ++ } ++ ++ PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_PAUSING, 0, TRUE); ++ return MMSYSERR_NOERROR; ++} ++ ++/************************************************************************** ++ * wodGetPosition [internal] ++ */ ++static DWORD wodGetPosition(WINE_WAVEINST *wwo, LPMMTIME lpTime, DWORD uSize) { ++ pa_usec_t time, time_temp; ++ size_t bytes, bytes_temp; ++ ++ if (!wwo || wwo->state == WINE_WS_FAILED) { ++ WARN("Stream instance invalid.\n"); ++ return MMSYSERR_INVALHANDLE; ++ } ++ ++ if (lpTime == NULL) return MMSYSERR_INVALPARAM; ++ ++ pa_threaded_mainloop_lock(PULSE_ml); ++ if (wwo->timing_info->read_index_corrupt || wwo->timing_info->write_index_corrupt) ++ PULSE_WaitForOperation(pa_stream_update_timing_info(wwo->stream, PULSE_StreamSuccessCallback, wwo)); ++ ++ bytes = wwo->timing_info->read_index; ++ time = pa_bytes_to_usec(bytes, &wwo->sample_spec); ++ ++ if (wwo->timing_info->playing) { ++ bytes += ((pa_timeval_age(&wwo->timing_info->timestamp) / 1000) * pa_bytes_per_second(&wwo->sample_spec)) / 1000; ++ bytes_temp = (time_temp = wwo->timing_info->sink_usec + wwo->timing_info->transport_usec)/1000 * pa_bytes_per_second(&wwo->sample_spec)/1000; ++ time += pa_timeval_age(&wwo->timing_info->timestamp); ++ } else { ++ time = 0; ++ time_temp = 0; ++ bytes_temp = 0; ++ } ++ ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ ++ if (wwo->last_reset < bytes) { ++ time -= pa_bytes_to_usec(wwo->last_reset, &wwo->sample_spec); ++ bytes -= wwo->last_reset; ++ } ++ if (bytes > bytes_temp) ++ bytes -= bytes_temp; ++ else ++ bytes = 0; ++ ++ if (time > time_temp) ++ time -= time_temp; ++ else ++ time = 0; ++ ++ bytes -= bytes % pa_frame_size(&wwo->sample_spec); ++ time /= 1000; /* In milliseconds now */ ++ ++ switch (lpTime->wType) { ++ case TIME_SAMPLES: ++ lpTime->u.sample = bytes / pa_frame_size(&wwo->sample_spec); ++ TRACE("TIME_SAMPLES=%u\n", lpTime->u.sample); ++ break; ++ case TIME_MS: ++ lpTime->u.ms = time; ++ TRACE("TIME_MS=%u\n", lpTime->u.ms); ++ break; ++ case TIME_SMPTE: ++ lpTime->u.smpte.fps = 30; ++ lpTime->u.smpte.sec = time/1000; ++ lpTime->u.smpte.min = lpTime->u.smpte.sec / 60; ++ lpTime->u.smpte.sec -= 60 * lpTime->u.smpte.min; ++ lpTime->u.smpte.hour = lpTime->u.smpte.min / 60; ++ lpTime->u.smpte.min -= 60 * lpTime->u.smpte.hour; ++ lpTime->u.smpte.frame = time / lpTime->u.smpte.fps * 1000; ++ TRACE("TIME_SMPTE=%02u:%02u:%02u:%02u\n", ++ lpTime->u.smpte.hour, lpTime->u.smpte.min, ++ lpTime->u.smpte.sec, lpTime->u.smpte.frame); ++ break; ++ default: ++ WARN("Format %d not supported, using TIME_BYTES !\n", lpTime->wType); ++ lpTime->wType = TIME_BYTES; ++ /* fall through */ ++ case TIME_BYTES: ++ lpTime->u.cb = bytes; ++ TRACE("TIME_BYTES=%u\n", lpTime->u.cb); ++ break; ++ } ++ return MMSYSERR_NOERROR; ++} ++/************************************************************************** ++ * wodBreakLoop [internal] ++ */ ++static DWORD wodBreakLoop(WINE_WAVEINST *wwo) { ++ if (!wwo || wwo->state == WINE_WS_FAILED) { ++ WARN("Stream instance invalid.\n"); ++ return MMSYSERR_INVALHANDLE; ++ } ++ ++ PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_BREAKLOOP, 0, TRUE); ++ return MMSYSERR_NOERROR; ++} ++ ++/************************************************************************** ++ * 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; ++} ++ ++/************************************************************************** ++ * wodGetNumDevs [internal] ++ * Context-sanity check here, as if we respond with 0, WINE will move on ++ * to the next waveout driver. ++ */ ++static DWORD wodGetNumDevs() { ++ if (!PULSE_ml || !PULSE_context || pa_context_get_state(PULSE_context) != PA_CONTEXT_READY) ++ return 0; ++ ++ return PULSE_WodNumDevs; ++} ++ ++/************************************************************************** ++ * wodGetVolume [internal] ++ */ ++static DWORD wodGetVolume(WINE_WAVEINST *wwo, LPDWORD lpdwVol) { ++ float value1, value2; ++ DWORD wleft, wright; ++ ++ if (!wwo || wwo->state == WINE_WS_FAILED) { ++ WARN("Stream instance invalid.\n"); ++ return MMSYSERR_INVALHANDLE; ++ } ++ ++ TRACE("(%p, %p);\n", wwo, lpdwVol); ++ ++ if (lpdwVol == NULL) ++ return MMSYSERR_NOTENABLED; ++ ++ pa_threaded_mainloop_lock(PULSE_ml); ++ if (wwo->stream && PULSE_context && pa_context_get_state(PULSE_context) == PA_CONTEXT_READY && ++ pa_stream_get_state(wwo->stream) == PA_STREAM_READY) { ++ PULSE_WaitForOperation(pa_context_get_sink_input_info(PULSE_context, pa_stream_get_index(wwo->stream), WAVEOUT_SinkInputInfoCallback, wwo)); ++ } ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ ++ ++ if (wwo->volume.channels == 2) { ++ value1 = pa_sw_volume_to_dB(wwo->volume.values[0]); ++ value2 = pa_sw_volume_to_dB(wwo->volume.values[1]); ++ } else { ++ value1 = pa_sw_volume_to_dB(pa_cvolume_avg(&wwo->volume)); ++ value2 = 0; ++ } ++ ++ if (value1 < -60) ++ wleft = 0; ++ else ++ ++ if (value2 < -60) ++ wright = 0; ++ else ++ wright = 0xFFFFl - ((value2 / -60)*(float)0xFFFFl); ++ ++ if (wleft > 0xFFFFl) ++ wleft = 0xFFFFl; ++ if (wright > 0xFFFFl) ++ wright = 0xFFFFl; ++ ++ *lpdwVol = (WORD)wleft + (WORD)(wright << 16); ++ ++ return MMSYSERR_NOERROR; ++} ++ ++/************************************************************************** ++ * wodSetVolume [internal] ++ */ ++static DWORD wodSetVolume(WINE_WAVEINST *wwo, DWORD dwParam1) { ++ double value1, value2; ++ ++ TRACE("(%p, %08X);\n", wwo, dwParam1); ++ if (!wwo || wwo->state == WINE_WS_FAILED) { ++ WARN("Stream instance invalid.\n"); ++ return MMSYSERR_INVALHANDLE; ++ } ++ ++ /* waveOut volumes are /supposed/ to be logarithmic */ ++ value1 = LOWORD(dwParam1) == 0 ? PA_DECIBEL_MININFTY : ((float)(0xFFFFl - LOWORD(dwParam1))/0xFFFFl) * -60.0; ++ value2 = HIWORD(dwParam1) == 0 ? PA_DECIBEL_MININFTY : ((float)(0xFFFFl - HIWORD(dwParam1))/0xFFFFl) * -60.0; ++ ++ if (wwo->sample_spec.channels == 2) { ++ wwo->volume.channels = 2; ++ wwo->volume.values[0] = pa_sw_volume_from_dB(value1); ++ wwo->volume.values[1] = pa_sw_volume_from_dB(value2); ++ } else { ++ if (value1 != value2) FIXME("Non-stereo streams can't pan!\n"); ++ wwo->volume.channels = wwo->sample_spec.channels; ++ pa_cvolume_set(&wwo->volume, wwo->volume.channels, pa_sw_volume_from_dB(max(value1, value2))); ++ } ++ ++ if (TRACE_ON(wave)) { ++ char s[PA_CVOLUME_SNPRINT_MAX]; ++ pa_cvolume_snprint(s, PA_CVOLUME_SNPRINT_MAX, &wwo->volume); ++ TRACE("%s\n", s); ++ } ++ ++ pa_threaded_mainloop_lock(PULSE_ml); ++ if (!wwo->stream || !PULSE_context || pa_context_get_state(PULSE_context) != PA_CONTEXT_READY || ++ pa_stream_get_state(wwo->stream) != PA_STREAM_READY || !pa_cvolume_valid(&wwo->volume)) { ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ return MMSYSERR_NOERROR; ++ } ++ ++ PULSE_WaitForOperation(pa_context_set_sink_input_volume(PULSE_context, ++ pa_stream_get_index(wwo->stream), &wwo->volume, ++ PULSE_ContextSuccessCallback, wwo)); ++ pa_threaded_mainloop_unlock(PULSE_ml); ++ return MMSYSERR_NOERROR; ++} ++ ++/************************************************************************** ++ * wodRestart [internal] ++ */ ++static DWORD wodRestart(WINE_WAVEINST *wwo) { ++ if (!wwo || wwo->state == WINE_WS_FAILED) { ++ WARN("Stream instance invalid.\n"); ++ return MMSYSERR_INVALHANDLE; ++ } ++ ++ if (wwo->state == WINE_WS_PAUSED) ++ PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_RESTARTING, 0, TRUE); ++ return MMSYSERR_NOERROR; ++} ++ ++/************************************************************************** ++ * wodReset [internal] ++ */ ++static DWORD wodReset(WINE_WAVEINST *wwo) { ++ if (!wwo || wwo->state == WINE_WS_FAILED) { ++ WARN("Stream instance invalid.\n"); ++ return MMSYSERR_INVALHANDLE; ++ } ++ ++ PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_RESETTING, 0, TRUE); ++ 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; ++} ++ ++/************************************************************************** ++ * wodMessage (WINEPULSE.@) ++ */ ++DWORD WINAPI PULSE_wodMessage(UINT wDevID, UINT wMsg, DWORD dwUser, ++ DWORD dwParam1, DWORD dwParam2) { ++/* ++ TRACE("(%u, %s, %08X, %08X, %08X);\n", ++ wDevID, PULSE_getMessage(wMsg), dwUser, dwParam1, 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 wodOpen (wDevID, (LPDWORD)dwUser, (LPWAVEOPENDESC)dwParam1, dwParam2); ++ case WODM_CLOSE: return wodClose ((WINE_WAVEINST*)dwUser); ++ case WODM_WRITE: return wodWrite ((WINE_WAVEINST*)dwUser, (LPWAVEHDR)dwParam1, dwParam2); ++ case WODM_PAUSE: return wodPause ((WINE_WAVEINST*)dwUser); ++ case WODM_GETPOS: return wodGetPosition ((WINE_WAVEINST*)dwUser, (LPMMTIME)dwParam1, dwParam2); ++ case WODM_BREAKLOOP: return wodBreakLoop ((WINE_WAVEINST*)dwUser); ++ 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 wodGetNumDevs (); ++ 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 wodGetVolume ((WINE_WAVEINST*)dwUser, (LPDWORD)dwParam1); ++ case WODM_SETVOLUME: return wodSetVolume ((WINE_WAVEINST*)dwUser, dwParam1); ++ case WODM_RESTART: return wodRestart ((WINE_WAVEINST*)dwUser); ++ case WODM_RESET: return wodReset ((WINE_WAVEINST*)dwUser); ++ 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..1b49460 +--- /dev/null ++++ b/dlls/winepulse.drv/winepulse.drv.spec +@@ -0,0 +1,3 @@ ++@ stdcall -private DriverProc(long long long long long long) PULSE_DriverProc ++@ stdcall -private wodMessage(long long long long long long) PULSE_wodMessage ++@ stdcall -private widMessage(long long long long long long) PULSE_widMessage +diff --git a/dlls/winepulse.drv/winepulse.h b/dlls/winepulse.drv/winepulse.h +new file mode 100644 +index 0000000..6270e04 +--- /dev/null ++++ b/dlls/winepulse.drv/winepulse.h +@@ -0,0 +1,225 @@ ++/* Definitions for PulseAudio Wine Driver ++ * ++ * Copyright 2009 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 | ++ * +---------+-------------+---------------+---------------------------------+ ++ */ ++ ++/* 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_ALL_FORMATS \ ++ 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 */ ++ ++/* events to be sent 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; ++ ++typedef struct WINE_WAVEDEV WINE_WAVEDEV; ++typedef struct WINE_WAVEINST WINE_WAVEINST; ++typedef struct IDsDriverImpl IDsDriverImpl; ++typedef struct IDsDriverBufferImpl IDsDriverBufferImpl; ++ ++struct IDsDriverImpl { ++ /* IUnknown fields */ ++ const IDsDriverVtbl *lpVtbl; ++ LONG ref; ++ ++ IDsDriverBufferImpl *primary; ++ UINT wDevID; ++}; ++ ++struct IDsDriverBufferImpl { ++ const IDsDriverBufferVtbl *lpVtbl; ++ IDsDriverImpl* drv; ++ LONG ref; ++ pa_stream *stream; ++ pa_sample_spec sample_spec; ++ pa_cvolume volume; ++ ++ PBYTE buffer; ++ DWORD buffer_length; ++ DWORD buffer_read_offset; ++ DWORD buffer_play_offset; ++ DWORD fraglen; ++}; ++ ++/* Per-playback/record device */ ++struct WINE_WAVEDEV { ++ char interface_name[MAXPNAMELEN * 2]; ++ char *device_name; ++ pa_cvolume volume; ++ ++ union { ++ WAVEOUTCAPSW out; ++ WAVEINCAPSW in; ++ } caps; ++ ++ /* DirectSound stuff */ ++ DSDRIVERDESC ds_desc; ++ DSDRIVERCAPS ds_caps; ++}; ++ ++/* Per-playback/record instance */ ++struct WINE_WAVEINST { ++ volatile INT state; /* one of the WINE_WS_ manifest constants */ ++ WAVEOPENDESC waveDesc; ++ WORD wFlags; ++ ++ /* PulseAudio specific data */ ++ pa_stream *stream; /* The PulseAudio stream */ ++ const pa_timing_info *timing_info; /* The timing info structure for the stream */ ++ pa_sample_spec sample_spec; /* Sample spec of this stream / device */ ++ pa_cvolume volume; /* Software volume of the stream */ ++ pa_buffer_attr buffer_attr; /* Buffer attribute, may not be used */ ++ ++ /* waveIn / waveOut wavaHdr */ ++ LPWAVEHDR lpQueuePtr; /* Start of queued WAVEHDRs (waiting to be notified) */ ++ LPWAVEHDR lpPlayPtr; /* Start of not yet fully written 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 */ ++ DWORD last_reset; /* When the last reset occured, as pa stream time isn't reset */ ++ BOOL is_releasing; /* Whether we are releasing wavehdrs */ ++ 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 */ ++ ++ /* waveIn specific */ ++ const void *buffer; /* Pointer to the latest data fragment for recording streams */ ++ DWORD buffer_length; /* How large the latest data fragment is */ ++ DWORD buffer_read_offset; /* How far into latest data fragment we last read */ ++ ++ /* Thread communication and synchronization stuff */ ++ HANDLE hStartUpEvent; ++ HANDLE hThread; ++ DWORD dwThreadID; ++ PULSE_MSG_RING msgRing; ++}; ++ ++/* 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_WaitForOperation(pa_operation *o); ++void PULSE_StreamSuccessCallback(pa_stream *s, int success, void *userdata); ++void PULSE_StreamStateCallback(pa_stream *s, void *userdata); ++void PULSE_StreamUnderflowCallback(pa_stream *s, void *userdata); ++void PULSE_StreamOverflowCallback(pa_stream *s, void *userdata); ++void PULSE_StreamSuspendedCallback(pa_stream *s, void *userdata); ++void PULSE_StreamMovedCallback(pa_stream *s, void *userdata); ++void PULSE_ContextSuccessCallback(pa_context *c, int success, void *userdata); ++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); ++ ++/* dsoutput.c */ ++DWORD wodDsCreate(UINT wDevID, PIDSDRIVER* drv); ++DWORD wodDsDesc(UINT wDevID, PDSDRIVERDESC desc); ++#endif