rpms/wine/F-10 wine-pulseaudio-waveout.patch, NONE, 1.1 wine-pulseaudio.patch, NONE, 1.1 .cvsignore, 1.67, 1.68 README-FEDORA-PULSEAUDIO, 1.2, 1.3 sources, 1.68, 1.69 wine.spec, 1.94, 1.95
Andreas Bierfert
awjb at fedoraproject.org
Wed Dec 10 05:27:34 UTC 2008
- Previous message (by thread): rpms/wine/F-9 wine-pulseaudio-waveout.patch, NONE, 1.1 wine-pulseaudio.patch, NONE, 1.1 .cvsignore, 1.66, 1.67 README-FEDORA-PULSEAUDIO, 1.2, 1.3 sources, 1.67, 1.68 wine.spec, 1.96, 1.97
- Next message (by thread): rpms/wine/devel wine-pulseaudio-waveout.patch, NONE, 1.1 wine-pulseaudio.patch, NONE, 1.1 .cvsignore, 1.67, 1.68 README-FEDORA-PULSEAUDIO, 1.2, 1.3 sources, 1.68, 1.69 wine.spec, 1.94, 1.95
- Messages sorted by:
[ date ]
[ thread ]
[ subject ]
[ author ]
Author: awjb
Update of /cvs/pkgs/rpms/wine/F-10
In directory cvs1.fedora.phx.redhat.com:/tmp/cvs-serv29894/F-10
Modified Files:
.cvsignore README-FEDORA-PULSEAUDIO sources wine.spec
Added Files:
wine-pulseaudio-waveout.patch wine-pulseaudio.patch
Log Message:
- version upgrade
- add pulseaudio support
wine-pulseaudio-waveout.patch:
--- NEW FILE wine-pulseaudio-waveout.patch ---
diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c
index 3ef6a03..64274d4 100644
--- a/dlls/winepulse.drv/pulse.c
+++ b/dlls/winepulse.drv/pulse.c
@@ -251,6 +251,75 @@ int PULSE_RetrieveRingMessage(PULSE_MSG_RING* omr,
*/
/******************************************************************
+ * 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) {
+
+ if (wf->nSamplesPerSec<DSBFREQUENCY_MIN||wf->nSamplesPerSec>DSBFREQUENCY_MAX)
+ return FALSE;
+
+ ss->channels=wf->nChannels;
+ ss->rate=wf->nSamplesPerSec;
+
+ if (wf->wFormatTag == WAVE_FORMAT_PCM) {
+ if (ss->channels==1 || ss->channels==2) {
+ switch (wf->wBitsPerSample) {
+ case 8:
+ ss->format = PA_SAMPLE_U8;
+ return TRUE;
+ case 16:
+ ss->format = PA_SAMPLE_S16NE;
+ return TRUE;
+ }
+ }
+ } else if (wf->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
+ WAVEFORMATEXTENSIBLE * wfex = (WAVEFORMATEXTENSIBLE *)wf;
+
+ if (wf->cbSize == 22 &&
+ (IsEqualGUID(&wfex->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))) {
+ if (ss->channels>=1 && ss->channels<=6) {
+ if (wf->wBitsPerSample==wfex->Samples.wValidBitsPerSample) {
+ switch (wf->wBitsPerSample) {
+ case 8:
+ ss->format=PA_SAMPLE_U8;
+ return TRUE;
+ case 16:
+ ss->format=PA_SAMPLE_S16NE;
+ return TRUE;
+ case 43:
+ ss->format=PA_SAMPLE_S32NE;
+ return TRUE;
+ }
+ } else
+ WARN("wBitsPerSample != wValidBitsPerSample not supported yet\n");
+ }
+ } else if (wf->cbSize == 22 &&
+ (IsEqualGUID(&wfex->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))) {
+ if (ss->channels>=1 && ss->channels<=6) {
+ if (wf->wBitsPerSample==wfex->Samples.wValidBitsPerSample && wf->wBitsPerSample == 32) {
+ ss->format=PA_SAMPLE_FLOAT32NE;
+ return TRUE;
+ }
+ }
+ } else
+ WARN("only KSDATAFORMAT_SUBTYPE_PCM and KSDATAFORMAT_SUBTYPE_IEEE_FLOAT "
+ "supported\n");
+ } else if (wf->wFormatTag == WAVE_FORMAT_MULAW || wf->wFormatTag == WAVE_FORMAT_ALAW) {
+ if (wf->wBitsPerSample==8) {
+ ss->format= (wf->wFormatTag==WAVE_FORMAT_MULAW) ? PA_SAMPLE_ULAW : PA_SAMPLE_ALAW;
+ return TRUE;
+ } else
+ ERR("WAVE_FORMAT_MULAW and WAVE_FORMAT_ALAW wBitsPerSample must = 8\n");
+ } else
+ WARN("only WAVE_FORMAT_PCM, WAVE_FORMAT_MULAW, WAVE_FORMAT_ALAW and WAVE_FORMAT_EXTENSIBLE supported\n");
+
+ return FALSE;
+}
+
+/******************************************************************
* PULSE_free_wavedevs [internal]
*
* Free and deallocated all the wavedevs in the array of size allocated
@@ -328,6 +397,23 @@ void PULSE_wait_for_operation(pa_operation *o, WINE_WAVEINST *wd) {
* Common Callbacks
*/
+/**************************************************************************
+ * PULSE_stream_request_callback
+ *
+ * Called by the pulse mainloop whenever it wants or has audio data.
+ */
+void PULSE_stream_request_callback(pa_stream *s, size_t nbytes, void *userdata) {
+ WINE_WAVEINST *ww = (WINE_WAVEINST*)userdata;
+ assert(s && ww);
+
+ TRACE("Asking to feed.\n");
+
+ /* Make sure that the player is running */
+ if (ww->hThread != INVALID_HANDLE_VALUE && ww->msgRing.messages) {
+ PULSE_AddRingMessage(&ww->msgRing, WINE_WM_FEED, (DWORD)nbytes, FALSE);
+ }
+}
+
/******************************************************************
* PULSE_stream_state_callback
*
diff --git a/dlls/winepulse.drv/waveout.c b/dlls/winepulse.drv/waveout.c
index fe7543f..9f0ade5 100644
--- a/dlls/winepulse.drv/waveout.c
+++ b/dlls/winepulse.drv/waveout.c
@@ -70,6 +70,879 @@ WINE_DEFAULT_DEBUG_CHANNEL(wave);
* +---------+-------------+---------------+---------------------------------+
*/
+/*
+ * - It is currently unknown if pausing in a loop works the same as expected.
+ */
+
+/*======================================================================*
+ * WAVE OUT specific PulseAudio Callbacks *
+ *======================================================================*/
+
+#if HAVE_PULSEAUDIO_0_9_11
+/**************************************************************************
+ * PULSE_started_callback [internal]
+ *
+ * Called by the pulse mainloop whenever stream playback resumes after an
+ * underflow or an initial start
+ */
+static void PULSE_started_callback(pa_stream *s, void *userdata) {
+ WINE_WAVEINST *wwo = (WINE_WAVEINST*)userdata;
+ assert(s && wwo);
+
+ TRACE("Audio flowing.\n");
+
+ if (wwo->hThread != INVALID_HANDLE_VALUE && wwo->msgRing.ring_buffer_size) {
+ PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_STARTING, 0, FALSE);
+ }
+}
+#else /* HAVE_PULSEAUDIO_0_9_11 */
+/**************************************************************************
+ * PULSE_timing_info_update_callback [internal]
+ *
+ * Called by the pulse mainloop whenever the timing info gets updated, we
+ * use this to send the started signal */
+static void PULSE_timing_info_update_callback(pa_stream *s, void *userdata) {
+ WINE_WAVEINST *wwo = (WINE_WAVEINST*)userdata;
+ assert(s && wwo);
+
+ if (wwo->is_buffering && wwo->timing_info && wwo->timing_info->playing) {
+ TRACE("Audio flowing.\n");
+
+ if (wwo->hThread != INVALID_HANDLE_VALUE && wwo->msgRing.ring_buffer_size)
+ PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_STARTING, 0, FALSE);
+ }
+}
+#endif
+
+/**************************************************************************
+ * PULSE_suspended_callback [internal]
+ *
+ * Called by the pulse mainloop any time stream playback is intentionally
+ * suspended or resumed from being suspended.
+ */
+static void PULSE_suspended_callback(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_underrun_callback [internal]
+ *
+ * Called by the pulse mainloop when the prebuf runs out of data.
+ */
+static void PULSE_underrun_callback(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);
+}
+
+/*======================================================================*
+ * "Low level" WAVE OUT implementation *
+ *======================================================================*/
+
+/**************************************************************************
+ * wodNotifyClient [internal]
+ */
+static DWORD wodNotifyClient(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 see if data has stopped being fed to us before actual playback
+ * starts. In this case the app wants a smaller buffer than pulse currently is
+ * offering. We ignore this and just start the releasing reference. The
+ * downside is that there is a latency unknown to the app. The upside is that
+ * pulse is good at managing latencies
+ */
+static void wodPlayer_CheckReleasing(WINE_WAVEINST *wwo) {
+ LPWAVEHDR lpWaveHdr = wwo->lpQueuePtr;
+
+ /* If we aren't playing, (only valid on pulse >= 0.9.11) and we have
+ * queued data and we aren't relasing, start releasing if either:
+ * - We have stopped being given wavehdrs, or
+ * - We have 2s worth of audio built up.*/
+ if (wwo->is_buffering && lpWaveHdr && !wwo->is_releasing &&
+ (pa_bytes_to_usec(lpWaveHdr->dwBufferLength, &wwo->sample_spec)/2 < pa_timeval_age(&wwo->last_header)||
+ wwo->timing_info->write_index - lpWaveHdr->reserved > pa_bytes_per_second(&wwo->sample_spec)*2)) {
+
+ pa_gettimeofday(&wwo->started_releasing);
+ wwo->is_releasing = TRUE;
+ wwo->releasing_offset = wwo->lpQueuePtr->reserved;
+ TRACE("Starting to release early: %u\n", wwo->releasing_offset);
+ }
+}
+
+/**************************************************************************
+ * 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.
+ */
+static DWORD wodPlayer_NotifyCompletions(WINE_WAVEINST* wwo, BOOL force) {
+ LPWAVEHDR lpWaveHdr;
+ pa_usec_t time;
+ pa_usec_t wait;
+
+ time = pa_bytes_to_usec(wwo->releasing_offset, &wwo->sample_spec);
+ if (wwo->is_releasing)
+ time += pa_timeval_age(&wwo->started_releasing);
+
+ for (lpWaveHdr = wwo->lpQueuePtr; lpWaveHdr; lpWaveHdr = wwo->lpQueuePtr) {
+ 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) / 1000;
+ return wait ?: 1;
+ }
+ }
+
+ /* return the wavehdr */
+ wwo->lpQueuePtr = lpWaveHdr->lpNext;
+ lpWaveHdr->dwFlags &= ~WHDR_INQUEUE;
+ lpWaveHdr->dwFlags |= WHDR_DONE;
+
+ wodNotifyClient(wwo, WOM_DONE, (DWORD)lpWaveHdr, 0);
+ }
+ /* No more wavehdrs */
+ TRACE("Empty queue\n");
+ return INFINITE;
+}
+
+/**************************************************************************
+ * wodPlayer_WriteMax [internal]
+ * Writes either space or the wavehdr's size into pulse's buffer, and
+ * returning how much data was written.
+ */
+static int wodPlayer_WriteMax(WINE_WAVEINST *wwo, size_t *space) {
+ LPWAVEHDR lpWaveHdr = wwo->lpPlayPtr;
+ size_t toWrite = min(lpWaveHdr->dwBufferLength - wwo->dwPartialOffset, *space);
+ size_t written = 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 0;
+ }
+
+ 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);
+ written = toWrite;
+ }
+
+ /* Check to see if we wrote all of the wavehdr */
+ if ((wwo->dwPartialOffset += written) >= lpWaveHdr->dwBufferLength)
+ wodPlayer_PlayPtrNext(wwo);
+ *space -= written;
+
+ return written;
+}
+
+/**************************************************************************
+ * wodPlayer_Feed [internal]
+ *
+ * Feed as much sound data as we can into pulse using wodPlayer_WriteMax
+ */
+static void wodPlayer_Feed(WINE_WAVEINST* wwo, size_t space) {
+ /* no more room... no need to try to feed */
+ if (space > 0) {
+ /* 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);
+ }
+ }
+}
+
+/**************************************************************************
+ * 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;
+ pa_operation *o;
+
+ 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*/
+ if ((o = pa_stream_flush(wwo->stream, PULSE_stream_success_callback, NULL)))
+ PULSE_wait_for_operation(o, wwo);
+
+ /* Ask for the timing info to be updated (sanity, I don't know if we _have_ to) */
+ if ((o = pa_stream_update_timing_info(wwo->stream, PULSE_stream_success_callback, wwo)))
+ PULSE_wait_for_operation(o, wwo);
+
+ /* Reset the written byte count as some data may have been flushed */
+ 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;
+ wodNotifyClient(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) {
+ size_t space;
+ pa_operation *o;
+
+ wwo->is_buffering = TRUE;
+
+ pa_threaded_mainloop_lock(PULSE_ml);
+
+ if (wwo->lpPlayPtr) {
+ TRACE("There is queued data. Trying to recover.\n");
+ space = pa_stream_writable_size(wwo->stream);
+
+ if (!space) {
+ TRACE("No space to feed. Flushing.\n");
+ if ((o = pa_stream_flush(wwo->stream, PULSE_stream_success_callback, wwo)))
+ PULSE_wait_for_operation(o, wwo);
+ space = pa_stream_writable_size(wwo->stream);
+ }
+ wodPlayer_Feed(wwo, space);
+ }
+
+ /* Ask for a timing update */
+ if ((o = pa_stream_update_timing_info(wwo->stream, PULSE_stream_success_callback, wwo)))
+ PULSE_wait_for_operation(o, wwo);
+
+ pa_threaded_mainloop_unlock(PULSE_ml);
+
+ if (wwo->timing_info->playing) {
+ TRACE("Recovered.\n");
+ wwo->is_buffering = FALSE;
+ } else {
+ ERR("Stream underrun! %i\n", wwo->instance_ref);
+ wwo->is_releasing = FALSE;
+ wwo->releasing_offset = wwo->timing_info->write_index;
+ }
+
+ wwo->releasing_offset = wwo->timing_info->write_index;
+}
+
+
+/**************************************************************************
+ * wodPlayer_ProcessMessages [internal]
+ */
+static void wodPlayer_ProcessMessages(WINE_WAVEINST* wwo) {
+ LPWAVEHDR lpWaveHdr;
+ enum win_wm_message msg;
+ DWORD param;
+ HANDLE ev;
+ pa_operation *o;
+
+ while (PULSE_RetrieveRingMessage(&wwo->msgRing, &msg, ¶m, &ev)) {
+ TRACE("Received %s %x\n", PULSE_getCmdString(msg), param);
+
+ switch (msg) {
+ case WINE_WM_PAUSING:
+ wwo->state = WINE_WS_PAUSED;
+ pa_threaded_mainloop_lock(PULSE_ml);
+ if ((o = pa_stream_cork(wwo->stream, 1, PULSE_stream_success_callback, NULL)))
+ PULSE_wait_for_operation(o, 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_buffering = TRUE;
+ pa_threaded_mainloop_unlock(PULSE_ml);
+
+ SetEvent(ev);
+ break;
+
+ case WINE_WM_RESTARTING:
+ if (wwo->state == WINE_WS_PAUSED) {
+ wwo->state = WINE_WS_PLAYING;
+ if (wwo->is_releasing)
+ pa_gettimeofday(&wwo->started_releasing);
+ pa_threaded_mainloop_lock(PULSE_ml);
+ if ((o = pa_stream_cork(wwo->stream, 0, PULSE_stream_success_callback, NULL)))
+ PULSE_wait_for_operation(o, 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);
+ /* Try and feed now, as we may have missed when pulse first asked */
+ wodPlayer_Feed(wwo, pa_stream_writable_size(wwo->stream));
+ if (wwo->state == WINE_WS_STOPPED)
+ wwo->state = WINE_WS_PLAYING;
+ if (wwo->is_buffering && !wwo->is_releasing)
+ pa_gettimeofday(&wwo->last_header);
+ SetEvent(ev);
+ break;
+
+ case WINE_WM_RESETTING:
+ wodPlayer_Reset(wwo);
+ SetEvent(ev);
+ break;
+
+ case WINE_WM_BREAKLOOP:
+ if (wwo->state == WINE_WS_PLAYING && wwo->lpLoopPtr != NULL)
+ /* ensure exit at end of current loop */
+ wwo->dwLoops = 1;
+ SetEvent(ev);
+ break;
+
+ case WINE_WM_FEED: /* Sent by the pulse thread */
+ wodPlayer_Feed(wwo, (size_t)param);
+ SetEvent(ev);
+ break;
+
+ case WINE_WM_XRUN: /* Sent by the pulse thread */
+ wodPlayer_Underrun(wwo);
+ SetEvent(ev);
+ break;
+
+ case WINE_WM_STARTING: /* Set by the pulse thread */
+ wwo->is_buffering = FALSE;
+ /* 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);
+ wodNotifyClient(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);
+ }
+ wwo->state = WINE_WS_CLOSED;
+ /* sanity check: this should not happen since the device must have been reset before */
+ if (wwo->lpQueuePtr || wwo->lpPlayPtr) ERR("out of sync\n");
+ SetEvent(ev);
+ TRACE("Thread exiting.\n");
+ ExitThread(0);
+ /* shouldn't go here */
+
+ default:
+ FIXME("unknown message %d\n", msg);
+ break;
+ }
+ }
+}
+
+/**************************************************************************
+ * wodPlayer [internal]
+ */
+static DWORD CALLBACK wodPlayer(LPVOID lpParam) {
+ WINE_WAVEINST *wwo = (WINE_WAVEINST*)lpParam;
+ DWORD dwSleepTime = INFINITE;
+
+ wwo->state = WINE_WS_STOPPED;
+ SetEvent(wwo->hStartUpEvent);
+
+ /* Wait for the shortest time before an action is required. If there are
+ * no pending actions, wait forever for a command. */
+ for (;;) {
+ TRACE("Waiting %u ms\n", dwSleepTime);
+ PULSE_WaitRingMessage(&wwo->msgRing, dwSleepTime);
+ wodPlayer_ProcessMessages(wwo);
+ if (wwo->state == WINE_WS_PLAYING) {
+ wodPlayer_CheckReleasing(wwo);
+ dwSleepTime = wodPlayer_NotifyCompletions(wwo, FALSE);
+ } else
+ dwSleepTime = INFINITE;
+ }
+}
+
+/**************************************************************************
+ * wodOpen [internal]
+ */
+static DWORD wodOpen(WORD wDevID, LPDWORD lpdwUser, LPWAVEOPENDESC lpDesc, DWORD dwFlags) {
+ WINE_WAVEDEV *wdo;
+ WINE_WAVEINST *wwo = NULL;
+ pa_operation *o;
+ DWORD x, ret = MMSYSERR_NOERROR;
+ pa_sample_spec test;
+
+ TRACE("(%u, %p, %08X);\n", wDevID, lpDesc, dwFlags);
+ if (lpDesc == NULL) {
+ WARN("Invalid Parameter !\n");
+ return MMSYSERR_INVALPARAM;
+ }
+
+ if (wDevID >= PULSE_WodNumDevs) {
+ TRACE("Asked for device %d, but only %d known!\n", wDevID, PULSE_WodNumDevs);
+ return MMSYSERR_BADDEVICEID;
+ }
+ wdo = &WOutDev[wDevID];
+
+ /* check to see if format is supported and make pa_sample_spec struct */
+ if (!PULSE_setupFormat(lpDesc->lpFormat, &test) &&
+ !pa_sample_spec_valid(&test)) {
+ WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
+ lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
+ lpDesc->lpFormat->nSamplesPerSec);
+ return WAVERR_BADFORMAT;
+ }
+
+ if (TRACE_ON(wave)) {
+ char t[PA_SAMPLE_SPEC_SNPRINT_MAX];
+ pa_sample_spec_snprint(t, sizeof(t), &test);
+ 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);
+ return MMSYSERR_NOERROR;
+ }
+
+ for (x = 0; x < PULSE_MAX_STREAM_INSTANCES; x++) {
+ if (wdo->instance[x] == NULL) {
+ if (!(wdo->instance[x] = wwo = HeapAlloc(GetProcessHeap(), 0, sizeof(WINE_WAVEINST))))
+ return MMSYSERR_NOMEM;
+ TRACE("Allocated new playback instance %u on device %u.\n", x, wDevID);
+ break;
+ }
+ }
+ if (x >= PULSE_MAX_STREAM_INSTANCES) return MMSYSERR_ALLOCATED;
+
+ *lpdwUser = (DWORD)wwo;
+ wwo->instance_ref = x;
+ wwo->sample_spec.format = test.format;
+ wwo->sample_spec.rate = test.rate;
+ wwo->sample_spec.channels = test.channels;
+ if (test.channels == 2) {
+ wwo->volume.channels = 2;
+ wwo->volume.values[0] = wdo->left_vol;
+ wwo->volume.values[1] = wdo->right_vol;
+ } else
+ pa_cvolume_set(&wwo->volume, test.channels, (wdo->left_vol + wdo->right_vol)/2);
+ wwo->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK);
+ wwo->waveDesc = *lpDesc;
+ wwo->lpQueuePtr = wwo->lpPlayPtr = wwo->lpLoopPtr = NULL;
+ wwo->dwPartialOffset = 0;
+ wwo->is_buffering = TRUE;
+ wwo->is_releasing = FALSE;
+ wwo->releasing_offset = 0;
+ wwo->timing_info = NULL;
+ PULSE_InitRingMessage(&wwo->msgRing);
+
+ /* FIXME Find a better name for the stream */
+ wwo->stream = pa_stream_new(PULSE_context, "Wine Playback", &wwo->sample_spec, NULL);
+ assert(wwo->stream);
+
+ pa_stream_set_state_callback(wwo->stream, PULSE_stream_state_callback, wwo);
+ pa_stream_set_write_callback(wwo->stream, PULSE_stream_request_callback, wwo);
+ pa_stream_set_underflow_callback(wwo->stream, PULSE_underrun_callback, wwo);
+ pa_stream_set_suspended_callback(wwo->stream, PULSE_suspended_callback, wwo);
+#if HAVE_PULSEAUDIO_0_9_11
+ pa_stream_set_started_callback(wwo->stream, PULSE_started_callback, wwo);
+#else
+ pa_stream_set_latency_update_callback(wwo->stream, PULSE_timing_info_update_callback, wwo);
+#endif
+
+ /* I don't like this, but... */
+ wwo->buffer_attr = pa_xmalloc(sizeof(pa_buffer_attr));
+ wwo->buffer_attr->maxlength = (uint32_t) -1;
+ wwo->buffer_attr->tlength = pa_bytes_per_second(&wwo->sample_spec)/5;
+ wwo->buffer_attr->prebuf = (uint32_t) -1;
+ wwo->buffer_attr->minreq = (uint32_t) -1;
+
+ TRACE("Connecting stream for playback.\n");
+ pa_threaded_mainloop_lock(PULSE_ml);
+#if HAVE_PULSEAUDIO_0_9_11
+ pa_stream_connect_playback(wwo->stream, wdo->device_name, wwo->buffer_attr, PA_STREAM_ADJUST_LATENCY, &wwo->volume, NULL);
+#else
+ pa_stream_connect_playback(wwo->stream, wdo->device_name, wwo->buffer_attr, PA_STREAM_AUTO_TIMING_UPDATE, &wwo->volume, NULL);
+#endif
+
+ 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 context object: %s\n", pa_strerror(pa_context_errno(PULSE_context)));
+ pa_threaded_mainloop_unlock(PULSE_ml);
+ ret = MMSYSERR_NODRIVER;
+ goto err;
+ }
+
+ if (sstate == PA_STREAM_READY)
+ break;
+
+ pa_threaded_mainloop_wait(PULSE_ml);
+ }
+ TRACE("Stream connected for playback.\n");
+
+ if ((o = pa_stream_update_timing_info(wwo->stream, PULSE_stream_success_callback, wwo)))
+ PULSE_wait_for_operation(o, 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 err;
+ }
+ WaitForSingleObject(wwo->hStartUpEvent, INFINITE);
+ CloseHandle(wwo->hStartUpEvent);
+ wwo->hStartUpEvent = INVALID_HANDLE_VALUE;
+
+
+ return wodNotifyClient (wwo, WOM_OPEN, 0L, 0L);
+
+err:
+ TRACE("Bailing out...\n");
+ wdo->instance[x] = NULL;
+ 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 (pa_stream_get_state(wwo->stream) == PA_STREAM_READY)
+ pa_stream_disconnect(wwo->stream);
+ pa_stream_unref(wwo->stream);
+ if (wwo->buffer_attr)
+ pa_xfree(wwo->buffer_attr);
+ HeapFree(GetProcessHeap(), 0, wwo);
+
+ return ret;
+}
+
+/**************************************************************************
+ * wodClose [internal]
+ */
+static DWORD wodClose(WORD wDevID, WINE_WAVEINST *wwo) {
+ pa_operation *o;
+ DWORD ret;
+
+ TRACE("(%u, %p);\n", wDevID, wwo);
+ if (wDevID >= PULSE_WodNumDevs) {
+ WARN("Asked for device %d, but only %d known!\n", wDevID, PULSE_WodNumDevs);
+ return MMSYSERR_INVALHANDLE;
+ } else 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);
+ if ((o = pa_stream_drain(wwo->stream, PULSE_stream_success_callback, NULL)))
+ PULSE_wait_for_operation(o, wwo);
+ 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);
+
+ pa_threaded_mainloop_lock(PULSE_ml);
+ pa_stream_disconnect(wwo->stream);
+ pa_threaded_mainloop_unlock(PULSE_ml);
+ }
+ ret = wodNotifyClient(wwo, WOM_CLOSE, 0L, 0L);
+
+ if (wwo->buffer_attr)
+ pa_xfree(wwo->buffer_attr);
+ pa_stream_unref(wwo->stream);
+ WOutDev[wDevID].instance[wwo->instance_ref] = NULL;
+ TRACE("Deallocating playback instance %u on device %u.\n", wwo->instance_ref, wDevID);
+ 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;
+
+ if (!wwo || wwo->state == WINE_WS_FAILED) {
+ WARN("Stream instance invalid.\n");
+ return MMSYSERR_INVALHANDLE;
+ }
+
+ if (lpTime == NULL) return MMSYSERR_INVALPARAM;
+
+ time = pa_bytes_to_usec(wwo->releasing_offset, &wwo->sample_spec);
+
+ if (wwo->is_releasing)
+ time += pa_timeval_age(&wwo->started_releasing);
+
+ if (wwo->last_reset > wwo->releasing_offset)
+ wwo->last_reset = 0;
+
+ time -= pa_bytes_to_usec(wwo->last_reset, &wwo->sample_spec);
+ time /= 1000;
+
+ switch (lpTime->wType) {
+ case TIME_SAMPLES:
+ lpTime->u.sample = (time * wwo->sample_spec.rate) / 1000;
+ break;
+ case TIME_MS:
+ lpTime->u.ms = time;
+ 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.fps = 30;
+ 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 = (pa_bytes_per_second(&wwo->sample_spec)*time) / 1000;
+ 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]
*/
@@ -88,6 +961,122 @@ static DWORD wodGetDevCaps(DWORD wDevID, LPWAVEOUTCAPSW lpCaps, DWORD dwSize) {
}
/**************************************************************************
+ * wodGetVolume [internal]
+ */
+static DWORD wodGetVolume(WORD wDevID, LPDWORD lpdwVol) {
+ DWORD wleft, wright;
+ WINE_WAVEDEV* wdo;
+
+ TRACE("(%u, %p);\n", wDevID, lpdwVol);
+
+ if (wDevID >= PULSE_WodNumDevs) {
+ TRACE("Asked for device %d, but only %d known!\n", wDevID, PULSE_WodNumDevs);
+ return MMSYSERR_INVALHANDLE;
+ }
+
+ if (lpdwVol == NULL)
+ return MMSYSERR_NOTENABLED;
+
+ wdo = &WOutDev[wDevID];
+
+ wleft=(long int)(pa_sw_volume_to_linear(wdo->left_vol)*0xFFFFl);
+ wright=(long int)(pa_sw_volume_to_linear(wdo->right_vol)*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(WORD wDevID, DWORD dwParam1) {
+ WINE_WAVEDEV *wdo;
+ WINE_WAVEINST *current;
+ pa_operation *o;
+ DWORD x;
+
+ TRACE("(%u, %08X);\n", wDevID, dwParam1);
+
+ if (wDevID >= PULSE_WodNumDevs) {
+ TRACE("Asked for device %d, but only %d known!\n", wDevID, PULSE_WodNumDevs);
+ return MMSYSERR_INVALHANDLE;
+ }
+
+ wdo = &WOutDev[wDevID];
+
+ wdo->left_vol = pa_sw_volume_from_linear((double)LOWORD(dwParam1)/0xFFFFl);
+ wdo->right_vol = pa_sw_volume_from_linear((double)HIWORD(dwParam1)/0xFFFFl);
+
+ /* Windows assumes that this volume is for the entire device, so we have to
+ * hunt down all current streams of the device and set their volumes.
+ * Streams which are not stereo (channels!=2) don't pan correctly. */
+ for (x = 0; (current = wdo->instance[x]); x++) {
+ switch (current->sample_spec.channels) {
+ case 2:
+ current->volume.channels = 2;
+ current->volume.values[0] = wdo->left_vol;
+ current->volume.values[1] = wdo->right_vol;
+ break;
+ case 1:
+ current->volume.channels = 1;
+ current->volume.values[0] = (wdo->left_vol + wdo->right_vol)/2; /* Is this right? */
+ break;
+ default:
+ /* FIXME How does more than stereo work? */
+ pa_cvolume_set(¤t->volume, current->sample_spec.channels, (wdo->left_vol + wdo->right_vol)/2);
+ }
+
+ if (!current->stream ||
+ !PULSE_context ||
+ pa_context_get_state(PULSE_context) != PA_CONTEXT_READY ||
+ pa_stream_get_state(current->stream) != PA_STREAM_READY ||
+ !pa_cvolume_valid(¤t->volume))
+ continue;
+
+ pa_threaded_mainloop_lock(PULSE_ml);
+ if ((o = pa_context_set_sink_input_volume(PULSE_context,
+ pa_stream_get_index(current->stream), ¤t->volume,
+ PULSE_context_success_callback, current)))
+ PULSE_wait_for_operation(o, current);
+ 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) {
@@ -124,12 +1113,16 @@ static DWORD wodDsDesc(UINT wDevID, PDSDRIVERDESC desc) {
strcpy(desc->szDrvname, "winepulse.drv");
return MMSYSERR_NOERROR;
}
+
/**************************************************************************
* 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:
@@ -137,12 +1130,12 @@ DWORD WINAPI PULSE_wodMessage(UINT wDevID, UINT wMsg, DWORD dwUser,
case DRVM_DISABLE:
/* FIXME: Pretend this is supported */
return 0;
- case WODM_OPEN: return MMSYSERR_NOTSUPPORTED;
- case WODM_CLOSE: return MMSYSERR_NOTSUPPORTED;
- case WODM_WRITE: return MMSYSERR_NOTSUPPORTED;
- case WODM_PAUSE: return MMSYSERR_NOTSUPPORTED;
- case WODM_GETPOS: return MMSYSERR_NOTSUPPORTED;
- case WODM_BREAKLOOP: return MMSYSERR_NOTSUPPORTED;
+ case WODM_OPEN: return wodOpen (wDevID, (LPDWORD)dwUser, (LPWAVEOPENDESC)dwParam1, dwParam2);
+ case WODM_CLOSE: return wodClose (wDevID, (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);
@@ -151,13 +1144,13 @@ DWORD WINAPI PULSE_wodMessage(UINT wDevID, UINT wMsg, DWORD dwUser,
case WODM_SETPITCH: return MMSYSERR_NOTSUPPORTED;
case WODM_GETPLAYBACKRATE: return MMSYSERR_NOTSUPPORTED; /* support if theoretically possible */
case WODM_SETPLAYBACKRATE: return MMSYSERR_NOTSUPPORTED; /* since pulseaudio 0.9.8 */
- case WODM_GETVOLUME: return MMSYSERR_NOTSUPPORTED;
- case WODM_SETVOLUME: return MMSYSERR_NOTSUPPORTED;
- case WODM_RESTART: return MMSYSERR_NOTSUPPORTED;
- case WODM_RESET: return MMSYSERR_NOTSUPPORTED;
+ case WODM_GETVOLUME: return wodGetVolume (wDevID, (LPDWORD)dwParam1);
+ case WODM_SETVOLUME: return wodSetVolume (wDevID, 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_QUERYDSOUNDIFACE: return wodDsCreate (wDevID, (PIDSDRIVER*)dwParam1);
case DRV_QUERYDSOUNDDESC: return wodDsDesc (wDevID, (PDSDRIVERDESC)dwParam1);
default:
FIXME("unknown message %d!\n", wMsg);
diff --git a/dlls/winepulse.drv/winepulse.h b/dlls/winepulse.drv/winepulse.h
index 277221a..64778ca 100644
--- a/dlls/winepulse.drv/winepulse.h
+++ b/dlls/winepulse.drv/winepulse.h
@@ -62,7 +62,7 @@
#define PULSE_MAX_STREAM_INSTANCES 16 /* Sixteen streams per device should be good enough? */
-/* events to be send to device */
+/* 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
@@ -104,7 +104,7 @@ typedef struct {
/* WavaHdr information */
LPWAVEHDR lpQueuePtr; /* start of queued WAVEHDRs (waiting to be notified) */
- LPWAVEHDR lpPlayPtr; /* start of not yet fully played buffers */
+ 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 */
@@ -151,13 +151,11 @@ DWORD PULSE_WodNumDevs;
DWORD PULSE_WidNumDevs;
/* pulse.c */
-void PULSE_set_buffer_attr_callback(pa_stream *stream, int success, void *userdata);
void PULSE_wait_for_operation(pa_operation *o, WINE_WAVEINST *ww);
void PULSE_stream_success_callback(pa_stream *s, int success, void *userdata);
void PULSE_stream_state_callback(pa_stream *s, void *userdata);
void PULSE_context_success_callback(pa_context *c, int success, void *userdata);
void PULSE_stream_request_callback(pa_stream *s, size_t n, void *userdata);
-DWORD PULSE_bytes_to_mmtime(LPMMTIME lpTime, DWORD position, pa_sample_spec *ss);
BOOL PULSE_setupFormat(LPWAVEFORMATEX wf, pa_sample_spec *ss);
int PULSE_InitRingMessage(PULSE_MSG_RING* omr);
int PULSE_DestroyRingMessage(PULSE_MSG_RING* omr);
wine-pulseaudio.patch:
--- NEW FILE wine-pulseaudio.patch ---
diff --git a/configure.ac b/configure.ac
index 91850de..b21be92 100644
--- a/configure.ac
+++ b/configure.ac
@@ -55,6 +55,7 @@ AC_ARG_WITH(oss, AS_HELP_STRING([--without-oss],[do not use the OSS sound
[if test "x$withval" = "xno"; then ac_cv_header_soundcard_h=no; ac_cv_header_sys_soundcard_h=no; ac_cv_header_machine_soundcard_h=no; fi])
AC_ARG_WITH(png, AS_HELP_STRING([--without-png],[do not use PNG]),
[if test "x$withval" = "xno"; then ac_cv_header_png_h=no; fi])
+AC_ARG_WITH(pulse, AC_HELP_STRING([--without-pulse],[do not use PulseAudio sound support]))
AC_ARG_WITH(sane, AS_HELP_STRING([--without-sane],[do not use SANE (scanner support)]))
AC_ARG_WITH(xcomposite,AS_HELP_STRING([--without-xcomposite],[do not use the Xcomposite extension]),
[if test "x$withval" = "xno"; then ac_cv_header_X11_extensions_Xcomposite_h=no; fi])
@@ -1117,6 +1118,29 @@ then
CFLAGS="$save_CFLAGS"
fi
+dnl **** Check for PulseAudio ****
+if test "x$with_pulse" != "xno"; then
+ if test "$PKG_CONFIG" != "false"; then
+ AC_MSG_CHECKING([for pulseaudio >= 0.9.8])
+ if "$PKG_CONFIG" --atleast-version=0.9.8 libpulse; then
+ have_pulseaudio="yes"
+ else
+ have_pulseaudio="no"
+ fi
+ AC_MSG_RESULT([$have_pulseaudio])
+ if test x"$have_pulseaudio" = xyes; then
+ if "$PKG_CONFIG" --atleast-version=0.9.11 libpulse; then
+ AC_DEFINE([HAVE_PULSEAUDIO_0_9_11], 1, [define this if pulseaudio is at least version 0.9.11])
+ else
+ AC_DEFINE([HAVE_PULSEAUDIO_0_9_11], 0, [define this if pulseaudio is at least version 0.9.11])
+ fi
+ ac_pulse_libs=`$PKG_CONFIG --libs libpulse`
+ AC_DEFINE([HAVE_PULSEAUDIO], 1, [define this if you have pulseaudio])
+ AC_SUBST(PULSELIBS, "$ac_pulse_libs")
+ fi
+ fi
+fi
+
dnl **** Check for ALSA 1.x ****
AC_SUBST(ALSALIBS,"")
if test "$ac_cv_header_sys_asoundlib_h" = "yes" -o "$ac_cv_header_alsa_asoundlib_h" = "yes"
@@ -1222,7 +1246,7 @@ dnl **** Check for libodbc ****
WINE_CHECK_SONAME(odbc,SQLConnect,,[AC_DEFINE_UNQUOTED(SONAME_LIBODBC,["libodbc.$LIBEXT"])])
dnl **** Check for any sound system ****
-if test "x$ALSALIBS$AUDIOIOLIBS$COREAUDIO$NASLIBS$ESDLIBS$ac_cv_lib_soname_jack" = "x" -a \
+if test "x$ALSALIBS$AUDIOIOLIBS$COREAUDIO$NASLIBS$ESDLIBS$PULSELIBS$ac_cv_lib_soname_jack" = "x" -a \
"$ac_cv_header_sys_soundcard_h" != "yes" -a \
"$ac_cv_header_machine_soundcard_h" != "yes" -a \
"$ac_cv_header_soundcard_h" != "yes" -a \
@@ -2064,6 +2088,7 @@ WINE_CONFIG_MAKEFILE([dlls/winemp3.acm/Makefile],[dlls/Makedll.rules],[dlls],[AL
WINE_CONFIG_MAKEFILE([dlls/winenas.drv/Makefile],[dlls/Makedll.rules],[dlls],[ALL_DLL_DIRS])
WINE_CONFIG_MAKEFILE([dlls/wineoss.drv/Makefile],[dlls/Makedll.rules],[dlls],[ALL_DLL_DIRS])
WINE_CONFIG_MAKEFILE([dlls/wineps.drv/Makefile],[dlls/Makedll.rules],[dlls],[ALL_DLL_DIRS])
+WINE_CONFIG_MAKEFILE([dlls/winepulse.drv/Makefile],[dlls/Makedll.rules],[dlls],[ALL_DLL_DIRS])
WINE_CONFIG_MAKEFILE([dlls/winequartz.drv/Makefile],[dlls/Makedll.rules],[dlls],[ALL_DLL_DIRS])
WINE_CONFIG_MAKEFILE([dlls/winex11.drv/Makefile],[dlls/Makedll.rules],[dlls],[ALL_DLL_DIRS])
WINE_CONFIG_MAKEFILE([dlls/wing32/Makefile],[dlls/Makedll.rules],[dlls],[ALL_DLL_DIRS])
diff --git a/dlls/winepulse.drv/Makefile.in b/dlls/winepulse.drv/Makefile.in
new file mode 100644
index 0000000..52a6671
--- /dev/null
+++ b/dlls/winepulse.drv/Makefile.in
@@ -0,0 +1,15 @@
+TOPSRCDIR = @top_srcdir@
+TOPOBJDIR = ../..
+SRCDIR = @srcdir@
+VPATH = @srcdir@
+MODULE = winepulse.drv
+IMPORTS = dxguid uuid winmm user32 advapi32 kernel32
+EXTRALIBS = @PULSELIBS@
+
+C_SRCS = \
+ waveout.c \
+ pulse.c
+
+ at MAKE_DLL_RULES@
+
+ at DEPENDENCIES@ # everything below this line is overwritten by make depend
diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c
new file mode 100644
index 0000000..3ef6a03
--- /dev/null
+++ b/dlls/winepulse.drv/pulse.c
@@ -0,0 +1,732 @@
+/*
+ * Wine Driver for PulseAudio
+ * http://pulseaudio.org/
+ *
+ * Copyright 2008 Arthur Taylor <art at ified.ca>
+ *
+ * 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 <stdarg.h>
+#include <stdio.h>
+
+#include "windef.h"
+#include "winbase.h"
+#include "wingdi.h"
+#include "winuser.h"
+#include "mmddk.h"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#include <poll.h>
+
+#ifdef HAVE_PULSEAUDIO
+
+#include "wine/unicode.h"
+#include "wine/debug.h"
+#include "wine/library.h"
+
+#include <winepulse.h>
+#include <pulse/pulseaudio.h>
+WINE_DEFAULT_DEBUG_CHANNEL(wave);
+
+/*
+ * - Need to subscribe to sink/source events and keep the WInDev and WOutDev
+ * structures updated
+ */
+
+/* These strings used only for tracing */
+const char * PULSE_getCmdString(enum win_wm_message msg) {
+ static char unknown[32];
+#define MSG_TO_STR(x) case x: return #x
+ switch(msg) {
+ MSG_TO_STR(WINE_WM_PAUSING);
+ MSG_TO_STR(WINE_WM_RESTARTING);
+ MSG_TO_STR(WINE_WM_RESETTING);
+ MSG_TO_STR(WINE_WM_HEADER);
+ MSG_TO_STR(WINE_WM_BREAKLOOP);
+ MSG_TO_STR(WINE_WM_CLOSING);
+ MSG_TO_STR(WINE_WM_STARTING);
+ MSG_TO_STR(WINE_WM_STOPPING);
+ MSG_TO_STR(WINE_WM_XRUN);
+ MSG_TO_STR(WINE_WM_FEED);
+ }
+#undef MSG_TO_STR
+ sprintf(unknown, "UNKNOWN(0x%08x)", msg);
+ return unknown;
+}
+
+/*======================================================================*
+ * Ring Buffer Functions - copied from winealsa.drv *
+ *======================================================================*/
+
+/* unless someone makes a wineserver kernel module, Unix pipes are faster than win32 events */
+#define USE_PIPE_SYNC
+
+#ifdef USE_PIPE_SYNC
+#define INIT_OMR(omr) do { if (pipe(omr->msg_pipe) < 0) { omr->msg_pipe[0] = omr->msg_pipe[1] = -1; } } while (0)
+#define CLOSE_OMR(omr) do { close(omr->msg_pipe[0]); close(omr->msg_pipe[1]); } while (0)
+#define SIGNAL_OMR(omr) do { int x = 0; write((omr)->msg_pipe[1], &x, sizeof(x)); } while (0)
+#define CLEAR_OMR(omr) do { int x = 0; read((omr)->msg_pipe[0], &x, sizeof(x)); } while (0)
+#define RESET_OMR(omr) do { } while (0)
+#define WAIT_OMR(omr, sleep) \
+ do { struct pollfd pfd; pfd.fd = (omr)->msg_pipe[0]; \
+ pfd.events = POLLIN; poll(&pfd, 1, sleep); } while (0)
+#else
+#define INIT_OMR(omr) do { omr->msg_event = CreateEventW(NULL, FALSE, FALSE, NULL); } while (0)
+#define CLOSE_OMR(omr) do { CloseHandle(omr->msg_event); } while (0)
+#define SIGNAL_OMR(omr) do { SetEvent((omr)->msg_event); } while (0)
+#define CLEAR_OMR(omr) do { } while (0)
+#define RESET_OMR(omr) do { ResetEvent((omr)->msg_event); } while (0)
+#define WAIT_OMR(omr, sleep) \
+ do { WaitForSingleObject((omr)->msg_event, sleep); } while (0)
+#endif
+
+#define PULSE_RING_BUFFER_INCREMENT 64
+
+/******************************************************************
+ * PULSE_InitRingMessage
+ *
+ * Initialize the ring of messages for passing between driver's caller
+ * and playback/record thread
+ */
+int PULSE_InitRingMessage(PULSE_MSG_RING* omr)
+{
+ omr->msg_toget = 0;
+ omr->msg_tosave = 0;
+ INIT_OMR(omr);
+ omr->ring_buffer_size = PULSE_RING_BUFFER_INCREMENT;
+ omr->messages = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,omr->ring_buffer_size * sizeof(PULSE_MSG));
+
+ InitializeCriticalSection(&omr->msg_crst);
+ omr->msg_crst.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": PULSE_MSG_RING.msg_crst");
+ return 0;
+}
+
+/******************************************************************
+ * PULSE_DestroyRingMessage
+ *
+ */
+int PULSE_DestroyRingMessage(PULSE_MSG_RING* omr)
+{
+ CLOSE_OMR(omr);
+ HeapFree(GetProcessHeap(),0,omr->messages);
+ omr->messages = NULL;
+ omr->ring_buffer_size = PULSE_RING_BUFFER_INCREMENT;
+ omr->msg_crst.DebugInfo->Spare[0] = 0;
+ DeleteCriticalSection(&omr->msg_crst);
+ return 0;
+}
+/******************************************************************
+ * PULSE_ResetRingMessage
+ *
+ */
+void PULSE_ResetRingMessage(PULSE_MSG_RING* omr)
+{
+ RESET_OMR(omr);
+}
+
+/******************************************************************
+ * PULSE_WaitRingMessage
+ *
+ */
+void PULSE_WaitRingMessage(PULSE_MSG_RING* omr, DWORD sleep)
+{
+ WAIT_OMR(omr, sleep);
+}
+
+/******************************************************************
+ * PULSE_AddRingMessage
+ *
+ * Inserts a new message into the ring (should be called from DriverProc derived routines)
+ */
+int PULSE_AddRingMessage(PULSE_MSG_RING* omr, enum win_wm_message msg, DWORD param, BOOL wait)
+{
+ HANDLE hEvent = INVALID_HANDLE_VALUE;
+
+ EnterCriticalSection(&omr->msg_crst);
+ if ((omr->msg_toget == ((omr->msg_tosave + 1) % omr->ring_buffer_size)))
+ {
+ int old_ring_buffer_size = omr->ring_buffer_size;
+ omr->ring_buffer_size += PULSE_RING_BUFFER_INCREMENT;
+ omr->messages = HeapReAlloc(GetProcessHeap(),0,omr->messages, omr->ring_buffer_size * sizeof(PULSE_MSG));
+ /* Now we need to rearrange the ring buffer so that the new
+ buffers just allocated are in between omr->msg_tosave and
+ omr->msg_toget.
+ */
+ if (omr->msg_tosave < omr->msg_toget)
+ {
+ memmove(&(omr->messages[omr->msg_toget + PULSE_RING_BUFFER_INCREMENT]),
+ &(omr->messages[omr->msg_toget]),
+ sizeof(PULSE_MSG)*(old_ring_buffer_size - omr->msg_toget)
+ );
+ omr->msg_toget += PULSE_RING_BUFFER_INCREMENT;
+ }
+ }
+ if (wait)
+ {
+ hEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
+ if (hEvent == INVALID_HANDLE_VALUE)
+ {
+ ERR("can't create event !?\n");
+ LeaveCriticalSection(&omr->msg_crst);
+ return 0;
+ }
+ if (omr->msg_toget != omr->msg_tosave && omr->messages[omr->msg_toget].msg != WINE_WM_HEADER)
+ FIXME("two fast messages in the queue!!!!\n"); /* toget = %d(%s), tosave=%d(%s)\n",
+ omr->msg_toget,ALSA_getCmdString(omr->messages[omr->msg_toget].msg),
+ omr->msg_tosave,ALSA_getCmdString(omr->messages[omr->msg_tosave].msg)); */
+
+ /* fast messages have to be added at the start of the queue */
+ omr->msg_toget = (omr->msg_toget + omr->ring_buffer_size - 1) % omr->ring_buffer_size;
+
+ omr->messages[omr->msg_toget].msg = msg;
+ omr->messages[omr->msg_toget].param = param;
+ omr->messages[omr->msg_toget].hEvent = hEvent;
+ }
+ else
+ {
+ omr->messages[omr->msg_tosave].msg = msg;
+ omr->messages[omr->msg_tosave].param = param;
+ omr->messages[omr->msg_tosave].hEvent = INVALID_HANDLE_VALUE;
+ omr->msg_tosave = (omr->msg_tosave + 1) % omr->ring_buffer_size;
+ }
+ LeaveCriticalSection(&omr->msg_crst);
+ /* signal a new message */
+ SIGNAL_OMR(omr);
+ if (wait)
+ {
+ /* wait for playback/record thread to have processed the message */
+ WaitForSingleObject(hEvent, INFINITE);
+ CloseHandle(hEvent);
+ }
+ return 1;
+}
+
+/******************************************************************
+ * PULSE_RetrieveRingMessage
+ *
+ * Get a message from the ring. Should be called by the playback/record thread.
+ */
+int PULSE_RetrieveRingMessage(PULSE_MSG_RING* omr,
+ enum win_wm_message *msg, DWORD *param, HANDLE *hEvent)
+{
+ EnterCriticalSection(&omr->msg_crst);
+
+ if (omr->msg_toget == omr->msg_tosave) /* buffer empty ? */
+ {
+ LeaveCriticalSection(&omr->msg_crst);
+ return 0;
+ }
+
+ *msg = omr->messages[omr->msg_toget].msg;
+ omr->messages[omr->msg_toget].msg = 0;
+ *param = omr->messages[omr->msg_toget].param;
+ *hEvent = omr->messages[omr->msg_toget].hEvent;
+ omr->msg_toget = (omr->msg_toget + 1) % omr->ring_buffer_size;
+ CLEAR_OMR(omr);
+ LeaveCriticalSection(&omr->msg_crst);
+ return 1;
+}
+
+/**************************************************************************
+ * Utility Functions
+ */
+
+/******************************************************************
+ * PULSE_free_wavedevs [internal]
+ *
+ * Free and deallocated all the wavedevs in the array of size allocated
+ */
+static void PULSE_free_wavedevs(WINE_WAVEDEV *wd, DWORD *allocated) {
+ DWORD y, x = *allocated;
+ WINE_WAVEDEV *current_device;
+ WINE_WAVEINST *current_instance;
+
+ TRACE("%i\n", *allocated);
+
+ for (; x>0; ) {
+ current_device = &wd[--x];
+
+ pa_xfree(current_device->device_name);
+
+ for (y = 0; y < PULSE_MAX_STREAM_INSTANCES; y++) {
+ if ((current_instance = current_device->instance[y])) {
+ if (current_instance->hThread != INVALID_HANDLE_VALUE && current_instance->msgRing.messages) {
+ PULSE_AddRingMessage(¤t_instance->msgRing, WINE_WM_CLOSING, 1, TRUE);
+ if (current_instance->hThread != INVALID_HANDLE_VALUE) {
+ /* Overkill? */
+ TerminateThread(current_instance->hThread, 1);
+ current_instance->hThread = INVALID_HANDLE_VALUE;
+ }
+ if (current_instance->stream)
+ pa_stream_unref(current_instance->stream);
+ PULSE_DestroyRingMessage(¤t_instance->msgRing);
+ current_device->instance[current_instance->instance_ref] = NULL;
+ if (current_instance->buffer_attr)
+ pa_xfree(current_instance->buffer_attr);
+ HeapFree(GetProcessHeap(), 0, current_instance);
+ }
+ }
+ }
+ }
+
+ HeapFree(GetProcessHeap(), 0, wd);
+ *allocated = 0;
+}
+
+/******************************************************************
+ * PULSE_wait_for_operation
+ *
+ * Waits for pa operations to complete, ensures success and dereferences the
+ * operations.
+ */
+void PULSE_wait_for_operation(pa_operation *o, WINE_WAVEINST *wd) {
+ assert(o && wd);
+
+ TRACE("Waiting...");
+
+ for (;;) {
+
+ if (!wd->stream ||
+ !PULSE_context ||
+ pa_context_get_state(PULSE_context) != PA_CONTEXT_READY ||
+ pa_stream_get_state(wd->stream) != PA_STREAM_READY) {
+ wd->state = WINE_WS_FAILED;
+ if (wd->hThread != INVALID_HANDLE_VALUE && wd->msgRing.messages)
+ PULSE_AddRingMessage(&wd->msgRing, WINE_WM_CLOSING, 1, FALSE);
+ break;
+ }
+
+ if (pa_operation_get_state(o) != PA_OPERATION_RUNNING)
+ break;
+
+ pa_threaded_mainloop_wait(PULSE_ml);
+ }
+ TRACE(" done\n");
+ pa_operation_unref(o);
+}
+
+/**************************************************************************
+ * Common Callbacks
+ */
+
+/******************************************************************
+ * PULSE_stream_state_callback
+ *
+ * Called by pulse whenever the state of the stream changes.
+ */
+void PULSE_stream_state_callback(pa_stream *s, void *userdata) {
+ WINE_WAVEINST *wd = (WINE_WAVEINST*)userdata;
+ assert(s && wd);
+
+ switch (pa_stream_get_state(s)) {
+
+ case PA_STREAM_READY:
+ TRACE("Stream ready\n");
+ break;
+ case PA_STREAM_TERMINATED:
+ TRACE("Stream terminated\n");
+ break;
+ case PA_STREAM_FAILED:
+ WARN("Stream failed!\n");
+ wd->state = WINE_WS_FAILED;
+ if (wd->hThread != INVALID_HANDLE_VALUE && wd->msgRing.messages)
+ PULSE_AddRingMessage(&wd->msgRing, WINE_WM_CLOSING, 1, FALSE);
+ break;
+ case PA_STREAM_UNCONNECTED:
+ case PA_STREAM_CREATING:
+ return;
+ }
+ pa_threaded_mainloop_signal(PULSE_ml, 0);
+}
+
+/**************************************************************************
+ * PULSE_stream_sucess_callback
+ */
+void PULSE_stream_success_callback(pa_stream *s, int success, void *userdata) {
+ if (!success)
+ WARN("Stream operation failed: %s\n", pa_strerror(pa_context_errno(PULSE_context)));
+
+ pa_threaded_mainloop_signal(PULSE_ml, 0);
+}
+
+/**************************************************************************
+ * PULSE_context_success_callback
+ */
+void PULSE_context_success_callback(pa_context *c, int success, void *userdata) {
+ if (!success)
+ WARN("Context operation failed: %s\n", pa_strerror(pa_context_errno(c)));
+
+ pa_threaded_mainloop_signal(PULSE_ml, 0);
+}
+
+/**************************************************************************
+ * Connection management and sink / source management.
+ */
+
+/**************************************************************************
+ * PULSE_context_state_callback [internal]
+ */
+static void PULSE_context_state_callback(pa_context *c, void *userdata) {
+ assert(c);
+
+ switch (pa_context_get_state(c)) {
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+
+ case PA_CONTEXT_READY:
+ case PA_CONTEXT_TERMINATED:
+ pa_threaded_mainloop_signal(PULSE_ml, 0);
+ break;
+
+ case PA_CONTEXT_FAILED:
+ default:
+ ERR("Conneciton failure: %s\n", pa_strerror(pa_context_errno(c)));
+
+ if (PULSE_context) {
+ pa_context_disconnect(PULSE_context);
+ }
+
+ PULSE_free_wavedevs(WOutDev, &PULSE_WodNumDevs);
+ PULSE_free_wavedevs(WInDev, &PULSE_WidNumDevs);
+
+ pa_threaded_mainloop_signal(PULSE_ml, 0);
+ }
+}
+
+/**************************************************************************
+ * PULSE_add_input_device [internal]
+ *
+ * Creates or adds a device to WInDev based on the pa_source_info, or if
+ * pa_source_info is null, a default.
+ */
+static void PULSE_add_input_device(pa_source_info *i, DWORD *allocated) {
+ WINE_WAVEDEV *wwi;
+ const char *description;
+ int x;
+
+ if (WInDev)
+ wwi = HeapReAlloc(GetProcessHeap(), 0, WInDev, sizeof(WINE_WAVEDEV) * ((*allocated)+1));
+ else
+ wwi = HeapAlloc(GetProcessHeap(), 0, sizeof(WINE_WAVEDEV));
+
+ if (!wwi)
+ return;
+
+ WInDev = wwi;
+ wwi = &WInDev[(*allocated)++];
+
+ if (i) {
+ description = i->description;
+ wwi->device_name = pa_xstrdup(i->name);
+ strcpy(wwi->interface_name, "winepulse: ");
+ memcpy(wwi->interface_name + strlen(wwi->interface_name),
+ i->name, min(strlen(i->name),
+ sizeof(wwi->interface_name) - strlen("winepulse: ")));
+ } else {
+ description = pa_xstrdup("PulseAudio Server Default");
+ wwi->device_name = NULL;
+ strcpy(wwi->interface_name, "winepulse: default");
+ }
+
+ memset(wwi, 0, sizeof(WINE_WAVEDEV));
+ memset(&(wwi->caps.in), 0, sizeof(wwi->caps.in));
+ MultiByteToWideChar(CP_ACP, 0, description, -1, wwi->caps.in.szPname, sizeof(wwi->caps.in.szPname)/sizeof(WCHAR));
+ wwi->caps.in.szPname[sizeof(wwi->caps.in.szPname)/sizeof(WCHAR) - 1] = '\0';
+ wwi->caps.in.wMid = MM_CREATIVE;
+ wwi->caps.in.wPid = MM_CREATIVE_SBP16_WAVEOUT;
+ wwi->caps.in.vDriverVersion = 0x0100;
+ wwi->caps.in.wChannels = 2;
+ wwi->caps.in.dwFormats |=
+ WAVE_FORMAT_1M08 | /* Mono 11025Hz 8-bit */
+ WAVE_FORMAT_1M16 | /* Mono 11025Hz 16-bit */
+ WAVE_FORMAT_1S08 | /* Stereo 11025Hz 8-bit */
+ WAVE_FORMAT_1S16 | /* Stereo 11025Hz 16-bit */
+ WAVE_FORMAT_2M08 | /* Mono 22050Hz 8-bit */
+ WAVE_FORMAT_2M16 | /* Mono 22050Hz 16-bit */
+ WAVE_FORMAT_2S08 | /* Stereo 22050Hz 8-bit */
+ WAVE_FORMAT_2S16 | /* Stereo 22050Hz 16-bit */
+ WAVE_FORMAT_4M08 | /* Mono 44100Hz 8-bit */
+ WAVE_FORMAT_4M16 | /* Mono 44100Hz 16-bit */
+ WAVE_FORMAT_4S08 | /* Stereo 44100Hz 8-bit */
+ WAVE_FORMAT_4S16 | /* Stereo 44100Hz 16-bit */
+ WAVE_FORMAT_48M08 | /* Mono 48000Hz 8-bit */
+ WAVE_FORMAT_48S08 | /* Stereo 48000Hz 8-bit */
+ WAVE_FORMAT_48M16 | /* Mono 48000Hz 16-bit */
+ WAVE_FORMAT_48S16 | /* Stereo 48000Hz 16-bit */
+ WAVE_FORMAT_96M08 | /* Mono 96000Hz 8-bit */
+ WAVE_FORMAT_96S08 | /* Stereo 96000Hz 8-bit */
+ WAVE_FORMAT_96M16 | /* Mono 96000Hz 16-bit */
+ WAVE_FORMAT_96S16 ; /* Stereo 96000Hz 16-bit */
+
+ /* NULL out the instance pointers */
+ for (x = 0; x < PULSE_MAX_STREAM_INSTANCES; x++) wwi->instance[x] = NULL;
+}
+
+/**************************************************************************
+ * PULSE_add_output_device [internal]
+ *
+ * Creates or adds a device to WOutDev based on the pa_sinl_info, or if
+ * pa_sink_info is null, a default.
+ */
+static void PULSE_add_output_device(pa_sink_info *i, DWORD *allocated) {
+ WINE_WAVEDEV *wwo;
+ const char *description;
+ int x;
+
+ if (WOutDev)
+ wwo = HeapReAlloc(GetProcessHeap(), 0, WOutDev, sizeof(WINE_WAVEDEV) * ((*allocated)+1));
+ else
+ wwo = HeapAlloc(GetProcessHeap(), 0, sizeof(WINE_WAVEDEV));
+
+ if (!wwo)
+ return;
+
+ WOutDev = wwo;
+ wwo = &WOutDev[(*allocated)++];
+
+ if (i) {
+ description = i->description;
+ wwo->device_name = pa_xstrdup(i->name);
+ strcpy(wwo->interface_name, "winepulse: ");
+ memcpy(wwo->interface_name + strlen(wwo->interface_name),
+ wwo->device_name, min(strlen(wwo->device_name),
+ sizeof(wwo->interface_name) - strlen("winepulse: ")));
+ } else {
+ description = pa_xstrdup("PulseAudio Server Default");
+ wwo->device_name = NULL;
+ strcpy(wwo->interface_name, "winepulse: default");
+ }
+
+ memset(wwo, 0, sizeof(WINE_WAVEDEV));
+ memset(&(wwo->caps.out), 0, sizeof(wwo->caps.out));
+ MultiByteToWideChar(CP_ACP, 0, description, -1, wwo->caps.out.szPname, sizeof(wwo->caps.out.szPname)/sizeof(WCHAR));
+ wwo->caps.out.szPname[sizeof(wwo->caps.out.szPname)/sizeof(WCHAR) - 1] = '\0';
+ wwo->caps.out.wMid = MM_CREATIVE;
+ wwo->caps.out.wPid = MM_CREATIVE_SBP16_WAVEOUT;
+ wwo->caps.out.vDriverVersion = 0x0100;
+ wwo->caps.out.dwSupport |= WAVECAPS_VOLUME | WAVECAPS_LRVOLUME;
+ wwo->caps.out.wChannels = 2;
+ wwo->caps.out.dwFormats |=
+ WAVE_FORMAT_1M08 | /* Mono 11025Hz 8-bit */
+ WAVE_FORMAT_1M16 | /* Mono 11025Hz 16-bit */
+ WAVE_FORMAT_1S08 | /* Stereo 11025Hz 8-bit */
+ WAVE_FORMAT_1S16 | /* Stereo 11025Hz 16-bit */
+ WAVE_FORMAT_2M08 | /* Mono 22050Hz 8-bit */
+ WAVE_FORMAT_2M16 | /* Mono 22050Hz 16-bit */
+ WAVE_FORMAT_2S08 | /* Stereo 22050Hz 8-bit */
+ WAVE_FORMAT_2S16 | /* Stereo 22050Hz 16-bit */
+ WAVE_FORMAT_4M08 | /* Mono 44100Hz 8-bit */
+ WAVE_FORMAT_4M16 | /* Mono 44100Hz 16-bit */
+ WAVE_FORMAT_4S08 | /* Stereo 44100Hz 8-bit */
+ WAVE_FORMAT_4S16 | /* Stereo 44100Hz 16-bit */
+ WAVE_FORMAT_48M08 | /* Mono 48000Hz 8-bit */
+ WAVE_FORMAT_48S08 | /* Stereo 48000Hz 8-bit */
+ WAVE_FORMAT_48M16 | /* Mono 48000Hz 16-bit */
+ WAVE_FORMAT_48S16 | /* Stereo 48000Hz 16-bit */
+ WAVE_FORMAT_96M08 | /* Mono 96000Hz 8-bit */
+ WAVE_FORMAT_96S08 | /* Stereo 96000Hz 8-bit */
+ WAVE_FORMAT_96M16 | /* Mono 96000HZ 16-bit */
+ WAVE_FORMAT_96S16 ; /* Stereo 96000Hz 16-bit */
+
+ wwo->left_vol = PA_VOLUME_NORM;
+ wwo->right_vol = PA_VOLUME_NORM;
+
+ /* NULL out the instance pointers */
+ for (x = 0; x < PULSE_MAX_STREAM_INSTANCES; x++) wwo->instance[x] = NULL;
+}
+
+/**************************************************************************
+ * PULSE_source_info_callback [internal]
+ *
+ * Calls PULSE_add_input_device to add the source to the list of devices
+ */
+static void PULSE_source_info_callback(pa_context *c, pa_source_info *i, int eol, void *userdata) {
+ assert(c);
+ if (!eol && i)
+ if (i->monitor_of_sink == PA_INVALID_INDEX) /* For now only add non-montior sources */
+ PULSE_add_input_device(i, (DWORD*)userdata);
+
+ pa_threaded_mainloop_signal(PULSE_ml, 0);
+}
+
+/**************************************************************************
+ * PULSE_sink_info_callback [internal]
+ *
+ * Calls PULSE_add_output_device to add the sink to the list of devices
+ */
+static void PULSE_sink_info_callback(pa_context *c, pa_sink_info *i, int eol, void *userdata) {
+ assert(c);
+ if (!eol && i)
+ PULSE_add_output_device(i, (DWORD*)userdata);
+
+ pa_threaded_mainloop_signal(PULSE_ml, 0);
+}
+
+/**************************************************************************
+ * PULSE_WaveInit [internal]
+ *
+ * Connects to the pulseaudio server, tries to discover sinks and sources and
+ * allocates the WaveIn/WaveOut devices.
+ */
+LONG PULSE_WaveInit(void) {
+ pa_operation *o = NULL;
+ DWORD allocated;
+
+ WOutDev = NULL;
+ WInDev = NULL;
+ PULSE_WodNumDevs = 0;
+ PULSE_WidNumDevs = 0;
+ PULSE_context = NULL;
+ PULSE_ml = NULL;
+
+ if (!(PULSE_ml = pa_threaded_mainloop_new())) {
+ WARN("Failed to create mainloop object.");
+ return -1;
+ }
+
+ pa_threaded_mainloop_start(PULSE_ml);
+
+ /* FIXME: better name? */
+ PULSE_context = pa_context_new(pa_threaded_mainloop_get_api(PULSE_ml), "Wine Application");
+ assert(PULSE_context);
+
+ pa_context_set_state_callback(PULSE_context, PULSE_context_state_callback, NULL);
+
+ if (pa_context_get_state(PULSE_context) != PA_CONTEXT_UNCONNECTED)
+ return 0;
+
+ pa_threaded_mainloop_lock(PULSE_ml);
+
+ TRACE("Attempting to connect to pulseaudio server.\n");
+ if (pa_context_connect(PULSE_context, NULL, 0, NULL) < 0) {
+ WARN("failed to connect context object %s\n", pa_strerror(pa_context_errno(PULSE_context)));
+ return -1;
+ }
+
+ /* Wait for connection */
+ for (;;) {
+ pa_context_state_t state = pa_context_get_state(PULSE_context);
+
+ if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) {
+ ERR("Failed to connect to pulseaudio server.\n");
+ pa_context_unref(PULSE_context);
+ pa_threaded_mainloop_unlock(PULSE_ml);
+ pa_threaded_mainloop_stop(PULSE_ml);
+ pa_threaded_mainloop_free(PULSE_ml);
+ return -1;
+ }
+
+ if (state == PA_CONTEXT_READY) {
+ TRACE("Connection succeeded!\n");
+ break;
+ }
+
+ pa_threaded_mainloop_wait(PULSE_ml);
+ }
+
+ /* Ask for all the sinks and create objects in the WOutDev array */
+ allocated=0;
+ /* add a fake output (server default sink) */
+ PULSE_add_output_device(NULL, &allocated);
+ o = pa_context_get_sink_info_list(PULSE_context, (pa_sink_info_cb_t)PULSE_sink_info_callback, &allocated);
+ assert(o);
+ while (pa_operation_get_state(o) != PA_OPERATION_DONE)
+ pa_threaded_mainloop_wait(PULSE_ml);
+ pa_operation_unref(o);
+ pa_threaded_mainloop_unlock(PULSE_ml);
+ TRACE("Allocated %i output device(s).\n",allocated);
+ PULSE_WodNumDevs=allocated;
+ if (PULSE_WodNumDevs == 1) /* Only the fake sink exists */
+ PULSE_free_wavedevs(WOutDev, &PULSE_WodNumDevs);
+
+ /* Repeate for all the sources */
+ allocated=0;
+ /* add a fake input (server default source) */
+ PULSE_add_input_device(NULL, &allocated);
+ pa_threaded_mainloop_lock(PULSE_ml);
+ o = pa_context_get_source_info_list(PULSE_context, (pa_source_info_cb_t)PULSE_source_info_callback, &allocated);
+ assert(o);
+ while (pa_operation_get_state(o) != PA_OPERATION_DONE)
+ pa_threaded_mainloop_wait(PULSE_ml);
+ pa_operation_unref(o);
+ pa_threaded_mainloop_unlock(PULSE_ml);
+ TRACE("Allocated %i input device(s).\n", allocated);
+ PULSE_WidNumDevs=allocated;
+ if (PULSE_WidNumDevs == 1) /* Only the fake source exists */
+ PULSE_free_wavedevs(WInDev, &PULSE_WidNumDevs);
+
+ return 1;
+}
+
+/**************************************************************************
+ * PULSE_WaveClose [internal]
+ *
+ * Disconnect from the server, deallocated the WaveIn/WaveOut devices, stop and
+ * free the mainloop.
+ */
+
+LONG PULSE_WaveClose(void) {
+ pa_threaded_mainloop_lock(PULSE_ml);
+ if (PULSE_context) {
+ pa_context_disconnect(PULSE_context);
+ pa_context_unref(PULSE_context);
+ PULSE_context = NULL;
+ }
+
+ PULSE_free_wavedevs(WOutDev, &PULSE_WodNumDevs);
+ PULSE_free_wavedevs(WInDev, &PULSE_WidNumDevs);
+
+ pa_threaded_mainloop_unlock(PULSE_ml);
+ pa_threaded_mainloop_stop(PULSE_ml);
+ pa_threaded_mainloop_free(PULSE_ml);
+
+ return 1;
+}
+
+#endif /* HAVE_PULSEAUDIO */
+
+/**************************************************************************
+ * DriverProc (WINEPULSE.@)
+ */
+LRESULT CALLBACK PULSE_DriverProc(DWORD_PTR dwDevID, HDRVR hDriv, UINT wMsg,
+ LPARAM dwParam1, LPARAM dwParam2) {
+
+ switch(wMsg) {
+#ifdef HAVE_PULSEAUDIO
+ case DRV_LOAD: PULSE_WaveInit();
+ return 1;
+ case DRV_FREE: return PULSE_WaveClose();
+ case DRV_OPEN: return 1;
+ case DRV_CLOSE: return 1;
+ case DRV_ENABLE: return 1;
+ case DRV_DISABLE: return 1;
+ case DRV_QUERYCONFIGURE: return 1;
+ case DRV_CONFIGURE: MessageBoxA(0, "PulseAudio MultiMedia Driver !", "PulseAudio Driver", MB_OK); return 1;
+ case DRV_INSTALL: return DRVCNF_RESTART;
+ case DRV_REMOVE: return DRVCNF_RESTART;
+#endif
+ default:
+ return DefDriverProc(dwDevID, hDriv, wMsg, dwParam1, dwParam2);
+ }
+}
diff --git a/dlls/winepulse.drv/waveout.c b/dlls/winepulse.drv/waveout.c
new file mode 100644
index 0000000..fe7543f
--- /dev/null
+++ b/dlls/winepulse.drv/waveout.c
@@ -0,0 +1,180 @@
+/*
+ * Wine Driver for PulseAudio - WaveOut Functionality
+ * http://pulseaudio.org/
+ *
+ * Copyright 2002 Eric Pouech
+ * 2002 Marco Pietrobono
+ * 2003 Christian Costa
+ * 2006-2007 Maarten Lankhorst
+ * 2008 Arthur Taylor (PulseAudio version)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "windef.h"
+#include "winbase.h"
+#include "wingdi.h"
+#include "winuser.h"
+#include "winnls.h"
+#include "winerror.h"
+#include "mmddk.h"
+#include "mmreg.h"
+#include "ks.h"
+#include "ksguid.h"
+#include "ksmedia.h"
+#include "dsound.h"
+#include "dsdriver.h"
+
+#include <winepulse.h>
+
+#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() | <thrd create> | PLAYING |
+ * | PLAYING | write() | HEADER | PLAYING |
+ * | (other) | write() | <error> | |
+ * | (any) | pause() | PAUSING | PAUSED |
+ * | PAUSED | restart() | RESTARTING | PLAYING (if no thrd => STOPPED) |
+ * | PAUSED | reset() | RESETTING | PAUSED |
+ * | (other) | reset() | RESETTING | STOPPED |
+ * | (any) | close() | CLOSING | CLOSED |
+ * +---------+-------------+---------------+---------------------------------+
+ */
+
+/**************************************************************************
+ * wodGetDevCaps [internal]
+ */
+static DWORD wodGetDevCaps(DWORD wDevID, LPWAVEOUTCAPSW lpCaps, DWORD dwSize) {
+ TRACE("(%u, %p, %u);\n", wDevID, lpCaps, dwSize);
+
+ if (lpCaps == NULL) return MMSYSERR_NOTENABLED;
+
+ if (wDevID >= PULSE_WodNumDevs) {
+ TRACE("Asked for device %d, but only %d known!\n", wDevID, PULSE_WodNumDevs);
+ return MMSYSERR_INVALHANDLE;
+ }
+
+ memcpy(lpCaps, &(WOutDev[wDevID].caps.out), min(dwSize, sizeof(*lpCaps)));
+ return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ * wodDevInterfaceSize [internal]
+ */
+static DWORD wodDevInterfaceSize(UINT wDevID, LPDWORD dwParam1) {
+
+ *dwParam1 = MultiByteToWideChar(CP_ACP, 0, WOutDev[wDevID].interface_name, -1, NULL, 0) * sizeof(WCHAR);
+ return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ * wodDevInterface [internal]
+ */
+static DWORD wodDevInterface(UINT wDevID, PWCHAR dwParam1, DWORD dwParam2) {
+ if (dwParam2 >= MultiByteToWideChar(CP_ACP, 0, WOutDev[wDevID].interface_name, -1,
+ NULL, 0 ) * sizeof(WCHAR))
+ {
+ MultiByteToWideChar(CP_ACP, 0, WOutDev[wDevID].interface_name, -1,
+ dwParam1, dwParam2 / sizeof(WCHAR));
+ return MMSYSERR_NOERROR;
+ }
+ return MMSYSERR_INVALPARAM;
+}
+
+/*======================================================================*
+ * Low level DSOUND implementation *
+ *======================================================================*/
+static DWORD wodDsCreate(UINT wDevID, PIDSDRIVER* drv) {
+ /* Is this possible ?*/
+ return MMSYSERR_NOTSUPPORTED;
+}
+
+static DWORD wodDsDesc(UINT wDevID, PDSDRIVERDESC desc) {
+ memset(desc, 0, sizeof(*desc));
+ strcpy(desc->szDesc, "Wine PulseAudio DirectSound Driver");
+ strcpy(desc->szDrvname, "winepulse.drv");
+ return MMSYSERR_NOERROR;
+}
+/**************************************************************************
+ * wodMessage (WINEPULSE.@)
+ */
+DWORD WINAPI PULSE_wodMessage(UINT wDevID, UINT wMsg, DWORD dwUser,
+ DWORD dwParam1, DWORD dwParam2) {
+
+ switch (wMsg) {
+ case DRVM_INIT:
+ case DRVM_EXIT:
+ case DRVM_ENABLE:
+ case DRVM_DISABLE:
+ /* FIXME: Pretend this is supported */
+ return 0;
+ case WODM_OPEN: return MMSYSERR_NOTSUPPORTED;
+ case WODM_CLOSE: return MMSYSERR_NOTSUPPORTED;
+ case WODM_WRITE: return MMSYSERR_NOTSUPPORTED;
+ case WODM_PAUSE: return MMSYSERR_NOTSUPPORTED;
+ case WODM_GETPOS: return MMSYSERR_NOTSUPPORTED;
+ case WODM_BREAKLOOP: return MMSYSERR_NOTSUPPORTED;
+ case WODM_PREPARE: return MMSYSERR_NOTSUPPORTED;
+ case WODM_UNPREPARE: return MMSYSERR_NOTSUPPORTED;
+ case WODM_GETDEVCAPS: return wodGetDevCaps (wDevID, (LPWAVEOUTCAPSW)dwParam1, dwParam2);
+ case WODM_GETNUMDEVS: return PULSE_WodNumDevs;
+ case WODM_GETPITCH: return MMSYSERR_NOTSUPPORTED;
+ case WODM_SETPITCH: return MMSYSERR_NOTSUPPORTED;
+ case WODM_GETPLAYBACKRATE: return MMSYSERR_NOTSUPPORTED; /* support if theoretically possible */
+ case WODM_SETPLAYBACKRATE: return MMSYSERR_NOTSUPPORTED; /* since pulseaudio 0.9.8 */
+ case WODM_GETVOLUME: return MMSYSERR_NOTSUPPORTED;
+ case WODM_SETVOLUME: return MMSYSERR_NOTSUPPORTED;
+ case WODM_RESTART: return MMSYSERR_NOTSUPPORTED;
+ case WODM_RESET: return MMSYSERR_NOTSUPPORTED;
+ case DRV_QUERYDEVICEINTERFACESIZE: return wodDevInterfaceSize (wDevID, (LPDWORD)dwParam1);
+ case DRV_QUERYDEVICEINTERFACE: return wodDevInterface (wDevID, (PWCHAR)dwParam1, dwParam2);
+ case DRV_QUERYDSOUNDIFACE: return wodDsCreate (wDevID, (PIDSDRIVER*)dwParam1);
+ case DRV_QUERYDSOUNDDESC: return wodDsDesc (wDevID, (PDSDRIVERDESC)dwParam1);
+ default:
+ FIXME("unknown message %d!\n", wMsg);
+ }
+ return MMSYSERR_NOTSUPPORTED;
+}
+
+#else /* !HAVE_PULSEAUDIO */
+
+/**************************************************************************
+ * wodMessage (WINEPULSE.@)
+ */
+DWORD WINAPI PULSE_wodMessage(WORD wDevID, WORD wMsg, DWORD dwUser,
+ DWORD dwParam1, DWORD dwParam2) {
+ FIXME("(%u, %04X, %08X, %08X, %08X):stub\n", wDevID, wMsg, dwUser,
+ dwParam1, dwParam2);
+ return MMSYSERR_NOTENABLED;
+}
+
+#endif /* HAVE_PULSEAUDIO */
diff --git a/dlls/winepulse.drv/winepulse.drv.spec b/dlls/winepulse.drv/winepulse.drv.spec
new file mode 100644
index 0000000..1707969
--- /dev/null
+++ b/dlls/winepulse.drv/winepulse.drv.spec
@@ -0,0 +1,2 @@
+@ stdcall -private DriverProc(long long long long long long) PULSE_DriverProc
+@ stdcall -private wodMessage(long long long long long long) PULSE_wodMessage
diff --git a/dlls/winepulse.drv/winepulse.h b/dlls/winepulse.drv/winepulse.h
new file mode 100644
index 0000000..277221a
--- /dev/null
+++ b/dlls/winepulse.drv/winepulse.h
@@ -0,0 +1,169 @@
+/* Definitions for PulseAudio Wine Driver
+ *
+ * Copyright 2008 Arthur Taylor
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#ifndef __WINE_CONFIG_H
+# error You must include config.h to use this header
+#endif
+
+#if defined(HAVE_PULSEAUDIO) && !defined(__WINEPULSE_H)
+#define __WINEPULSE_H
+
+#include "mmreg.h"
+#include "dsound.h"
+#include "dsdriver.h"
+
+#include "ks.h"
+#include "ksmedia.h"
+#include "ksguid.h"
+
+#include <pulse/pulseaudio.h>
+
+/* state diagram for waveOut writing:
+ *
+ * +---------+-------------+---------------+---------------------------------+
+ * | state | function | event | new state |
+ * +---------+-------------+---------------+---------------------------------+
+ * | | open() | | STOPPED |
+ * | PAUSED | write() | | PAUSED |
+ * | STOPPED | write() | <thrd create> | PLAYING |
+ * | PLAYING | write() | HEADER | PLAYING |
+ * | (other) | write() | <error> | |
+ * | (any) | pause() | PAUSING | PAUSED |
+ * | PAUSED | restart() | RESTARTING | PLAYING (if no thrd => STOPPED) |
+ * | (any) | reset() | RESETTING | STOPPED |
+ * | (any) | close() | CLOSING | CLOSED |
+ * +---------+-------------+---------------+---------------------------------+
+ */
+
+#undef PULSE_VERBOSE
+
+/* states of the playing device */
+#define WINE_WS_PLAYING 1
+#define WINE_WS_PAUSED 2
+#define WINE_WS_STOPPED 3
+#define WINE_WS_CLOSED 4
+#define WINE_WS_FAILED 5
+
+#define PULSE_MAX_STREAM_INSTANCES 16 /* Sixteen streams per device should be good enough? */
+
+/* events to be send to device */
+enum win_wm_message {
+ WINE_WM_PAUSING = WM_USER + 1, WINE_WM_RESTARTING, WINE_WM_RESETTING, WINE_WM_HEADER,
+ WINE_WM_BREAKLOOP, WINE_WM_CLOSING, WINE_WM_STARTING, WINE_WM_STOPPING, WINE_WM_XRUN, WINE_WM_FEED
+};
+
+typedef struct {
+ enum win_wm_message msg; /* message identifier */
+ DWORD param; /* parameter for this message */
+ HANDLE hEvent; /* if message is synchronous, handle of event for synchro */
+} PULSE_MSG;
+
+/* implement an in-process message ring for better performance
+ * (compared to passing thru the server)
+ * this ring will be used by the input (resp output) record (resp playback) routine
+ */
+typedef struct {
+ PULSE_MSG * messages;
+ int ring_buffer_size;
+ int msg_tosave;
+ int msg_toget;
+/* Either pipe or event is used, but that is defined in pulse.c,
+ * since this is a global header we define both here */
+ int msg_pipe[2];
+ HANDLE msg_event;
+ CRITICAL_SECTION msg_crst;
+} PULSE_MSG_RING;
+
+/* Per-playback/record instance */
+typedef struct {
+ int instance_ref; /* The array index of WINE_WAVEDEV->instance[] that this is */
+ volatile int state; /* one of the WINE_WS_ manifest constants */
+ WAVEOPENDESC waveDesc;
+ WORD wFlags;
+ pa_stream *stream; /* The PulseAudio stream */
+ const pa_timing_info *timing_info;
+ pa_buffer_attr *buffer_attr;
+ pa_sample_spec sample_spec; /* Sample spec of this stream / device */
+ pa_cvolume volume;
+
+ /* WavaHdr information */
+ LPWAVEHDR lpQueuePtr; /* start of queued WAVEHDRs (waiting to be notified) */
+ LPWAVEHDR lpPlayPtr; /* start of not yet fully played buffers */
+ DWORD dwPartialOffset; /* Offset of not yet written bytes in lpPlayPtr */
+ LPWAVEHDR lpLoopPtr; /* pointer of first buffer in loop, if any */
+ DWORD dwLoops; /* private copy of loop counter */
+
+ /* Virtual stream positioning information */
+ DWORD last_reset; /* When the last reset occured, as pa stream time doesn't reset */
+ BOOL is_buffering; /* !is_playing */
+ struct timeval last_header; /* When the last wavehdr was received, only updated when is_buffering is true */
+ BOOL is_releasing; /* Whether we are releasing wavehdrs, may not always be the inverse of is_buffering */
+ struct timeval started_releasing; /* When wavehdr releasing started, for comparison to queued written wavehdrs */
+ DWORD releasing_offset; /* How much audio has been released prior when releasing started in this instance */
+
+ /* Thread communication and synchronization stuff */
+ HANDLE hStartUpEvent;
+ HANDLE hThread;
+ DWORD dwThreadID;
+ PULSE_MSG_RING msgRing;
+} WINE_WAVEINST;
+
+/* Per-playback/record device */
+typedef struct {
+ char *device_name; /* Name of the device used as an identifer on the server */
+ char interface_name[MAXPNAMELEN * 2];
+
+ pa_volume_t left_vol; /* Volume is per device and always stereo */
+ pa_volume_t right_vol;
+
+ union {
+ WAVEOUTCAPSW out;
+ WAVEINCAPSW in;
+ } caps;
+
+ WINE_WAVEINST *instance[PULSE_MAX_STREAM_INSTANCES];
+} WINE_WAVEDEV;
+
+/* We establish one context per instance, so make it global to the lib */
+pa_context *PULSE_context; /* Connection Context */
+pa_threaded_mainloop *PULSE_ml; /* PA Runtime information */
+
+/* WaveIn / WaveOut devices */
+WINE_WAVEDEV *WOutDev;
+WINE_WAVEDEV *WInDev;
+DWORD PULSE_WodNumDevs;
+DWORD PULSE_WidNumDevs;
+
+/* pulse.c */
+void PULSE_set_buffer_attr_callback(pa_stream *stream, int success, void *userdata);
+void PULSE_wait_for_operation(pa_operation *o, WINE_WAVEINST *ww);
+void PULSE_stream_success_callback(pa_stream *s, int success, void *userdata);
+void PULSE_stream_state_callback(pa_stream *s, void *userdata);
+void PULSE_context_success_callback(pa_context *c, int success, void *userdata);
+void PULSE_stream_request_callback(pa_stream *s, size_t n, void *userdata);
+DWORD PULSE_bytes_to_mmtime(LPMMTIME lpTime, DWORD position, pa_sample_spec *ss);
+BOOL PULSE_setupFormat(LPWAVEFORMATEX wf, pa_sample_spec *ss);
+int PULSE_InitRingMessage(PULSE_MSG_RING* omr);
+int PULSE_DestroyRingMessage(PULSE_MSG_RING* omr);
+void PULSE_ResetRingMessage(PULSE_MSG_RING* omr);
+void PULSE_WaitRingMessage(PULSE_MSG_RING* omr, DWORD sleep);
+int PULSE_AddRingMessage(PULSE_MSG_RING* omr, enum win_wm_message msg, DWORD param, BOOL wait);
+int PULSE_RetrieveRingMessage(PULSE_MSG_RING* omr, enum win_wm_message *msg, DWORD *param, HANDLE *hEvent);
+const char * PULSE_getCmdString(enum win_wm_message msg);
+#endif
Index: .cvsignore
===================================================================
RCS file: /cvs/pkgs/rpms/wine/F-10/.cvsignore,v
retrieving revision 1.67
retrieving revision 1.68
diff -u -r1.67 -r1.68
--- .cvsignore 23 Nov 2008 10:44:34 -0000 1.67
+++ .cvsignore 10 Dec 2008 05:27:04 -0000 1.68
@@ -1 +1 @@
-wine-1.1.9-fe.tar.bz2
+wine-1.1.10-fe.tar.bz2
Index: README-FEDORA-PULSEAUDIO
===================================================================
RCS file: /cvs/pkgs/rpms/wine/F-10/README-FEDORA-PULSEAUDIO,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -r1.2 -r1.3
--- README-FEDORA-PULSEAUDIO 25 Jan 2008 18:00:21 -0000 1.2
+++ README-FEDORA-PULSEAUDIO 10 Dec 2008 05:27:04 -0000 1.3
@@ -1,15 +1,19 @@
Wine and Pulseaudio Support
---------------------------
-Currently wine does not have native support for pulseaudio. However different
-ways exist to make wine sound work alongside pulseaudio. Different sound
-outputs can be selected via `winecfg` or in the registry of wine.
+Currently wine does not have native support for pulseaudio. However, some
+patches exist to make wine use a native pulseaudio backend
+(see http://bugs.winehq.org/show_bug.cgi?id=10495). These have been included
+into the fedora wine package. If you have problems please do _not_ report them
+to the wine project.
+Other ways to get wine working with pulseaudio are described below:
ALSA
----
To achieve sound output via the wine alsa driver you need to add
a pulseaudio alsa device to the alsa configuration and activate it in wine. See
http://www.pulseaudio.org/wiki/PerfectSetup#ALSAApplications on how to do this.
+For this alsa-plugins-pulseaudio.i386 should be installed.
Esound
Index: sources
===================================================================
RCS file: /cvs/pkgs/rpms/wine/F-10/sources,v
retrieving revision 1.68
retrieving revision 1.69
diff -u -r1.68 -r1.69
--- sources 23 Nov 2008 10:44:34 -0000 1.68
+++ sources 10 Dec 2008 05:27:04 -0000 1.69
@@ -1 +1 @@
-b671c6fbce7ed91cce9539cf418a2a75 wine-1.1.9-fe.tar.bz2
+e8e6a40936896d488bae6d517ececed9 wine-1.1.10-fe.tar.bz2
Index: wine.spec
===================================================================
RCS file: /cvs/pkgs/rpms/wine/F-10/wine.spec,v
retrieving revision 1.94
retrieving revision 1.95
diff -u -r1.94 -r1.95
--- wine.spec 24 Nov 2008 22:19:32 -0000 1.94
+++ wine.spec 10 Dec 2008 05:27:04 -0000 1.95
@@ -1,6 +1,6 @@
Name: wine
-Version: 1.1.9
-Release: 2%{?dist}
+Version: 1.1.10
+Release: 1%{?dist}
Summary: A Windows 16/32/64 bit emulator
Group: Applications/Emulators
@@ -40,6 +40,9 @@
Source300: wine-mime-msi.desktop
# explain how to use wine with pulseaudio
+# see http://bugs.winehq.org/show_bug.cgi?id=10495
+Patch400: wine-pulseaudio.patch
+Patch401: wine-pulseaudio-waveout.patch
Source402: README-FEDORA-PULSEAUDIO
@@ -90,8 +93,10 @@
BuildRequires: libXi-devel
BuildRequires: libXcursor-devel
# dbus/hal >= FC5
-BuildRequires: dbus-devel hal-devel
-BuildRequires: gnutls-devel
+BuildRequires: dbus-devel hal-devel
+BuildRequires: gnutls-devel
+BuildRequires: pulseaudio-libs-devel
+BuildRequires: autoconf
Requires: wine-core = %{version}-%{release}
Requires: wine-capi = %{version}-%{release}
@@ -216,10 +221,22 @@
Header, include files and library definition files for developing applications
with the Wine Windows(TM) emulation libraries.
+%package pulseaudio
+Summary: Pulseaudio support for wine
+Group: System Environment/Libraries
+Requires: wine-core = %{version}-%{release}
+
+%description pulseaudio
+This package adds a native pulseaudio driver for wine. This is not an official
+wine audio driver. Please do not report bugs regarding this driver at winehq.org.
+
%prep
%setup -q -n %{name}-%{version}-fe
%patch1
%patch2
+%patch400 -p1
+%patch401 -p1
+autoconf -f
%build
# work around gcc bug see #440139
@@ -232,7 +249,8 @@
%configure \
--sysconfdir=%{_sysconfdir}/wine --disable-static \
- --x-includes=%{_includedir} --x-libraries=%{_libdir}
+ --x-includes=%{_includedir} --x-libraries=%{_libdir} \
+ --with-pulse
%{__make} depend
@@ -378,8 +396,6 @@
%doc AUTHORS README-Fedora README VERSION
# do not include huge changelogs .OLD .ALPHA .BETA (#204302)
%doc documentation/README.*
-# pulseaudio workaround documentation
-%doc README-FEDORA-PULSEAUDIO
%{_bindir}/msiexec
%{_bindir}/regedit
%{_bindir}/regsvr32
@@ -832,7 +848,18 @@
%{_libdir}/wine/*.a
%{_libdir}/wine/*.def
+%files pulseaudio
+# pulseaudio workaround documentation
+%doc README-FEDORA-PULSEAUDIO
+%{_libdir}/wine/winepulse.drv.so
+
%changelog
+* Sat Dec 06 2008 Andreas Bierfert <andreas.bierfert[AT]lowlatency.de>
+- 1.1.10-1
+- version upgrade
+- add native pulseaudio driver from winehq bugzilla (#10495)
+ fixes #474435, #344281
+
* Mon Nov 24 2008 Andreas Bierfert <andreas.bierfert[AT]lowlatency.de>
- 1.1.9-2
- fix #469907
- Previous message (by thread): rpms/wine/F-9 wine-pulseaudio-waveout.patch, NONE, 1.1 wine-pulseaudio.patch, NONE, 1.1 .cvsignore, 1.66, 1.67 README-FEDORA-PULSEAUDIO, 1.2, 1.3 sources, 1.67, 1.68 wine.spec, 1.96, 1.97
- Next message (by thread): rpms/wine/devel wine-pulseaudio-waveout.patch, NONE, 1.1 wine-pulseaudio.patch, NONE, 1.1 .cvsignore, 1.67, 1.68 README-FEDORA-PULSEAUDIO, 1.2, 1.3 sources, 1.68, 1.69 wine.spec, 1.94, 1.95
- Messages sorted by:
[ date ]
[ thread ]
[ subject ]
[ author ]
More information about the fedora-extras-commits
mailing list