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