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