2 * Wine Driver for PulseAudio - WaveOut Functionality
3 * http://pulseaudio.org/
5 * Copyright 2009 Arthur Taylor <theycallhimart@gmail.com>
7 * Contains code from other wine multimedia drivers.
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
36 #include <winepulse.h>
38 #include "wine/debug.h"
40 WINE_DEFAULT_DEBUG_CHANNEL(wave
);
44 /* state diagram for waveOut writing:
46 * +---------+-------------+---------------+---------------------------------+
47 * | state | function | event | new state |
48 * +---------+-------------+---------------+---------------------------------+
49 * | | open() | | STOPPED |
50 * | PAUSED | write() | | PAUSED |
51 * | STOPPED | write() | <thrd create> | PLAYING |
52 * | PLAYING | write() | HEADER | PLAYING |
53 * | (other) | write() | <error> | |
54 * | (any) | pause() | PAUSING | PAUSED |
55 * | PAUSED | restart() | RESTARTING | PLAYING (if no thrd => STOPPED) |
56 * | PAUSED | reset() | RESETTING | PAUSED |
57 * | (other) | reset() | RESETTING | STOPPED |
58 * | (any) | close() | CLOSING | CLOSED |
59 * +---------+-------------+---------------+---------------------------------+
63 * - It is currently unknown if pausing in a loop works the same as expected.
66 /*======================================================================*
67 * WAVE OUT specific PulseAudio Callbacks *
68 *======================================================================*/
70 /**************************************************************************
71 * WAVEOUT_StreamRequestCallback
73 * Called by the pulse mainloop whenever it wants audio data.
75 static void WAVEOUT_StreamRequestCallback(pa_stream
*s
, size_t nbytes
, void *userdata
) {
76 WINE_WAVEINST
*ww
= (WINE_WAVEINST
*)userdata
;
78 TRACE("Asking to be fed %u bytes\n", nbytes
);
80 /* Make sure that the player/recorder is running */
81 if (ww
->hThread
!= INVALID_HANDLE_VALUE
&& ww
->msgRing
.messages
) {
82 PULSE_AddRingMessage(&ww
->msgRing
, WINE_WM_FEED
, (DWORD
)nbytes
, FALSE
);
86 /**************************************************************************
87 * WAVEOUT_SinkInputInfoCallback [internal]
89 * Called by the pulse thread. Used for wodGetVolume.
91 static void WAVEOUT_SinkInputInfoCallback(pa_context
*c
, const pa_sink_input_info
*i
, int eol
, void *userdata
) {
92 WINE_WAVEINST
* wwo
= (WINE_WAVEINST
*)userdata
;
94 for (wwo
->volume
.channels
= 0; wwo
->volume
.channels
!= i
->volume
.channels
; wwo
->volume
.channels
++)
95 wwo
->volume
.values
[wwo
->volume
.channels
] = i
->volume
.values
[wwo
->volume
.channels
];
96 pa_threaded_mainloop_signal(PULSE_ml
, 0);
100 /*======================================================================*
101 * "Low level" WAVE OUT implementation *
102 *======================================================================*/
104 /**************************************************************************
105 * wodPlayer_NotifyClient [internal]
107 static DWORD
wodPlayer_NotifyClient(WINE_WAVEINST
* wwo
, WORD wMsg
, DWORD dwParam1
, DWORD dwParam2
) {
108 /* TRACE("wMsg = 0x%04x dwParm1 = %04X dwParam2 = %04X\n", wMsg, dwParam1, dwParam2); */
114 if (wwo
->wFlags
!= DCB_NULL
&&
115 !DriverCallback(wwo
->waveDesc
.dwCallback
, wwo
->wFlags
, (HDRVR
)wwo
->waveDesc
.hWave
,
116 wMsg
, wwo
->waveDesc
.dwInstance
, dwParam1
, dwParam2
)) {
117 WARN("can't notify client !\n");
118 return MMSYSERR_ERROR
;
122 FIXME("Unknown callback message %u\n", wMsg
);
123 return MMSYSERR_INVALPARAM
;
125 return MMSYSERR_NOERROR
;
128 /**************************************************************************
129 * wodPlayer_BeginWaveHdr [internal]
131 * Makes the specified lpWaveHdr the currently playing wave header.
132 * If the specified wave header is a begin loop and we're not already in
133 * a loop, setup the loop.
135 static void wodPlayer_BeginWaveHdr(WINE_WAVEINST
* wwo
, LPWAVEHDR lpWaveHdr
) {
136 wwo
->lpPlayPtr
= lpWaveHdr
;
138 if (!lpWaveHdr
) return;
140 if (lpWaveHdr
->dwFlags
& WHDR_BEGINLOOP
) {
141 if (wwo
->lpLoopPtr
) {
142 WARN("Already in a loop. Discarding loop on this header (%p)\n", lpWaveHdr
);
144 TRACE("Starting loop (%dx) with %p\n", lpWaveHdr
->dwLoops
, lpWaveHdr
);
145 wwo
->lpLoopPtr
= lpWaveHdr
;
146 /* Windows does not touch WAVEHDR.dwLoops,
147 * so we need to make an internal copy */
148 wwo
->dwLoops
= lpWaveHdr
->dwLoops
;
151 wwo
->dwPartialOffset
= 0;
154 /**************************************************************************
155 * wodPlayer_PlayPtrNext [internal]
157 * Advance the play pointer to the next waveheader, looping if required.
159 static LPWAVEHDR
wodPlayer_PlayPtrNext(WINE_WAVEINST
* wwo
) {
160 LPWAVEHDR lpWaveHdr
= wwo
->lpPlayPtr
;
162 wwo
->dwPartialOffset
= 0;
163 if ((lpWaveHdr
->dwFlags
& WHDR_ENDLOOP
) && wwo
->lpLoopPtr
) {
164 /* We're at the end of a loop, loop if required */
165 if (--wwo
->dwLoops
> 0) {
166 wwo
->lpPlayPtr
= wwo
->lpLoopPtr
;
168 /* Handle overlapping loops correctly */
169 if (wwo
->lpLoopPtr
!= lpWaveHdr
&& (lpWaveHdr
->dwFlags
& WHDR_BEGINLOOP
)) {
170 FIXME("Correctly handled case ? (ending loop buffer also starts a new loop)\n");
171 /* shall we consider the END flag for the closing loop or for
172 * the opening one or for both ???
173 * code assumes for closing loop only
176 lpWaveHdr
= lpWaveHdr
->lpNext
;
178 wwo
->lpLoopPtr
= NULL
;
179 wodPlayer_BeginWaveHdr(wwo
, lpWaveHdr
);
182 /* We're not in a loop. Advance to the next wave header */
183 wodPlayer_BeginWaveHdr(wwo
, lpWaveHdr
= lpWaveHdr
->lpNext
);
189 /**************************************************************************
190 * wodPlayer_CheckReleasing [internal]
192 * Check to make sure that playback has not stalled. If stalled ask to reduce
193 * the size of the buffer on the pulse server side.
195 static void wodPlayer_CheckReleasing(WINE_WAVEINST
*wwo
) {
198 if (wwo
->buffer_attr
.tlength
== -1) {
199 pa_threaded_mainloop_lock(PULSE_ml
);
200 if (!wwo
->timing_info
->playing
) {
202 /* Calculate how large a buffer the application has made so far */
203 wwo
->buffer_attr
.tlength
= 0;
204 wwo
->buffer_attr
.minreq
= wwo
->lpQueuePtr
->dwBufferLength
;
205 for (lpWaveHdr
= wwo
->lpQueuePtr
; lpWaveHdr
; lpWaveHdr
= lpWaveHdr
->lpNext
)
206 wwo
->buffer_attr
.tlength
+= lpWaveHdr
->dwBufferLength
;
208 WARN("Asking for new buffer target length of %llums (%u bytes)\n",
209 pa_bytes_to_usec(wwo
->buffer_attr
.tlength
, &wwo
->sample_spec
) / 1000,
210 wwo
->buffer_attr
.tlength
);
212 /* Try and adjust the buffer attributes so that playback can start.
213 * Because of bugs pa_stream_set_buffer_attr() does not work on started
214 * streams for server version 0.9.11 to 0.9.14 */
215 PULSE_WaitForOperation(pa_stream_set_buffer_attr(wwo
->stream
, &wwo
->buffer_attr
, PULSE_StreamSuccessCallback
, wwo
));
217 pa_threaded_mainloop_unlock(PULSE_ml
);
221 /**************************************************************************
222 * wodPlayer_NotifyCompletions [internal]
224 * Notifies the client of wavehdr completion starting from lpQueuePtr and
225 * stopping when hitting an unwritten wavehdr, the beginning of a loop or a
226 * wavehdr that has not been played, when referenced to the time parameter.
228 static DWORD
wodPlayer_NotifyCompletions(WINE_WAVEINST
* wwo
, BOOL force
, pa_usec_t time
) {
229 LPWAVEHDR lpWaveHdr
= wwo
->lpQueuePtr
;
234 /* Start from lpQueuePtr and keep notifying until:
235 * - we hit an unwritten wavehdr
236 * - we hit the beginning of a running loop
237 * - we hit a wavehdr which hasn't finished playing
239 if (lpWaveHdr
== wwo
->lpLoopPtr
) { TRACE("loop %p\n", lpWaveHdr
); return INFINITE
; }
240 if (lpWaveHdr
== wwo
->lpPlayPtr
) { TRACE("play %p\n", lpWaveHdr
); return INFINITE
; }
242 /* See if this data has been played, and if not, return when it will have been */
243 wait
= pa_bytes_to_usec(lpWaveHdr
->reserved
+ lpWaveHdr
->dwBufferLength
, &wwo
->sample_spec
);
245 wait
= ((wait
- time
) + (pa_usec_t
)999) / (pa_usec_t
)1000;
249 TRACE("Returning %p.[%i]\n", lpWaveHdr
, (DWORD
)lpWaveHdr
->reserved
);
251 /* return the wavehdr */
252 wwo
->lpQueuePtr
= lpWaveHdr
->lpNext
;
253 lpWaveHdr
->dwFlags
&= ~WHDR_INQUEUE
;
254 lpWaveHdr
->dwFlags
|= WHDR_DONE
;
256 wodPlayer_NotifyClient(wwo
, WOM_DONE
, (DWORD
)lpWaveHdr
, 0);
257 lpWaveHdr
= wwo
->lpQueuePtr
;
259 /* No more wavehdrs */
260 TRACE("Empty queue\n");
264 /**************************************************************************
265 * wodPlayer_WriteMax [internal]
267 * Write either how much free space or how much data we have, depending on
270 static DWORD
wodPlayer_WriteMax(WINE_WAVEINST
*wwo
, size_t *space
) {
271 LPWAVEHDR lpWaveHdr
= wwo
->lpPlayPtr
;
274 nbytes
= min(lpWaveHdr
->dwBufferLength
- wwo
->dwPartialOffset
, *space
);
276 TRACE("Writing wavehdr %p.%u[%u]\n", lpWaveHdr
, wwo
->dwPartialOffset
, lpWaveHdr
->dwBufferLength
);
277 pa_stream_write(wwo
->stream
, lpWaveHdr
->lpData
+ wwo
->dwPartialOffset
, nbytes
, NULL
, 0, PA_SEEK_RELATIVE
);
279 /* Check to see if we wrote all of the wavehdr */
280 if ((wwo
->dwPartialOffset
+= nbytes
) >= lpWaveHdr
->dwBufferLength
)
281 wodPlayer_PlayPtrNext(wwo
);
288 /**************************************************************************
289 * wodPlayer_Feed [internal]
291 * Feed as much sound data as we can into pulse using wodPlayer_WriteMax.
292 * size_t space _must_ have come from either pa_stream_writable_size() or
293 * the value from a stream write callback, as if it isn't you run the risk
294 * of a buffer overflow in which audio data will be lost.
296 static void wodPlayer_Feed(WINE_WAVEINST
* wwo
, size_t space
) {
298 if (!space
|| !wwo
->stream
|| !PULSE_context
||
299 pa_context_get_state(PULSE_context
) != PA_CONTEXT_READY
||
300 pa_stream_get_state(wwo
->stream
) != PA_STREAM_READY
)
303 pa_threaded_mainloop_lock(PULSE_ml
);
304 /* Feed from a partial wavehdr */
305 if (wwo
->lpPlayPtr
&& wwo
->dwPartialOffset
!= 0)
306 wodPlayer_WriteMax(wwo
, &space
);
308 /* Feed wavehdrs until we run out of wavehdrs or buffer space */
309 if (wwo
->dwPartialOffset
== 0 && wwo
->lpPlayPtr
) {
311 wwo
->lpPlayPtr
->reserved
= wwo
->timing_info
->write_index
;
312 } while (wodPlayer_WriteMax(wwo
, &space
) && wwo
->lpPlayPtr
&& space
> 0);
315 pa_threaded_mainloop_unlock(PULSE_ml
);
318 /**************************************************************************
319 * wodPlayer_Reset [internal]
321 * wodPlayer helper. Resets current output stream.
323 static void wodPlayer_Reset(WINE_WAVEINST
* wwo
) {
324 enum win_wm_message msg
;
328 TRACE("(%p)\n", wwo
);
330 /* Remove any buffer */
331 wodPlayer_NotifyCompletions(wwo
, TRUE
, 0);
333 wwo
->lpPlayPtr
= wwo
->lpQueuePtr
= wwo
->lpLoopPtr
= NULL
;
334 if (wwo
->state
!= WINE_WS_PAUSED
)
335 wwo
->state
= WINE_WS_STOPPED
;
337 wwo
->dwPartialOffset
= 0;
341 pa_context_get_state(PULSE_context
) != PA_CONTEXT_READY
||
342 pa_stream_get_state(wwo
->stream
) != PA_STREAM_READY
) {
346 pa_threaded_mainloop_lock(PULSE_ml
);
348 /* Flush the output buffer of written data*/
349 PULSE_WaitForOperation(pa_stream_flush(wwo
->stream
, PULSE_StreamSuccessCallback
, NULL
));
351 /* Reset the written byte count as some data may have been flushed */
352 if (wwo
->timing_info
->write_index_corrupt
)
353 PULSE_WaitForOperation(pa_stream_update_timing_info(wwo
->stream
, PULSE_StreamSuccessCallback
, wwo
));
355 wwo
->dwLastReset
= wwo
->timing_info
->write_index
;
357 /* Return all pending headers in queue */
358 EnterCriticalSection(&wwo
->msgRing
.msg_crst
);
359 while (PULSE_RetrieveRingMessage(&wwo
->msgRing
, &msg
, ¶m
, &ev
)) {
360 if (msg
!= WINE_WM_HEADER
) {
364 ((LPWAVEHDR
)param
)->dwFlags
&= ~WHDR_INQUEUE
;
365 ((LPWAVEHDR
)param
)->dwFlags
|= WHDR_DONE
;
366 wodPlayer_NotifyClient(wwo
, WOM_DONE
, param
, 0);
368 PULSE_ResetRingMessage(&wwo
->msgRing
);
369 LeaveCriticalSection(&wwo
->msgRing
.msg_crst
);
371 pa_threaded_mainloop_unlock(PULSE_ml
);
374 /**************************************************************************
375 * wodPlayer_GetStreamTime [internal]
377 * Returns how many microseconds into the playback the audio stream is. Does
378 * not reset to 0 on Reset() calls. Better than pa_stream_get_time() as it is
381 static pa_usec_t
wodPlayer_GetStreamTime(WINE_WAVEINST
*wwo
) {
382 pa_usec_t time
, temp
;
383 const pa_timing_info
*t
;
385 t
= wwo
->timing_info
;
387 pa_threaded_mainloop_lock(PULSE_ml
);
389 time
= pa_bytes_to_usec(t
->read_index
, &wwo
->sample_spec
);
390 if (t
->read_index_corrupt
) {
391 WARN("Read index corrupt?!\n");
392 pa_threaded_mainloop_unlock(PULSE_ml
);
397 time
+= pa_timeval_age(&t
->timestamp
);
398 temp
= t
->transport_usec
+ t
->configured_sink_usec
;
399 if (temp
> wwo
->buffer_attr
.tlength
) temp
= wwo
->buffer_attr
.tlength
;
400 if (time
> temp
) time
-= temp
; else time
= 0;
403 /* Make sure we haven't claimed to have played more than we have written */
404 temp
= pa_bytes_to_usec(t
->write_index
, &wwo
->sample_spec
);
405 if (time
> temp
) time
= temp
;
407 /* No queued buffer shows an underrun, so we lie */
408 if (!wwo
->lpQueuePtr
) time
= temp
;
410 pa_threaded_mainloop_unlock(PULSE_ml
);
415 /**************************************************************************
416 * wodPlayer_ProcessMessages [internal]
418 static void wodPlayer_ProcessMessages(WINE_WAVEINST
* wwo
) {
420 enum win_wm_message msg
;
424 while (PULSE_RetrieveRingMessage(&wwo
->msgRing
, &msg
, ¶m
, &ev
)) {
425 TRACE("Received %s %x\n", PULSE_getCmdString(msg
), param
);
428 case WINE_WM_PAUSING
:
429 wwo
->state
= WINE_WS_PAUSED
;
430 pa_threaded_mainloop_lock(PULSE_ml
);
431 PULSE_WaitForOperation(pa_stream_cork(wwo
->stream
, 1, PULSE_StreamSuccessCallback
, wwo
));
432 pa_threaded_mainloop_unlock(PULSE_ml
);
436 case WINE_WM_RESTARTING
:
437 if (wwo
->state
== WINE_WS_PAUSED
) {
438 wwo
->state
= WINE_WS_PLAYING
;
439 pa_threaded_mainloop_lock(PULSE_ml
);
440 PULSE_WaitForOperation(pa_stream_cork(wwo
->stream
, 0, PULSE_StreamSuccessCallback
, wwo
));
441 /* If the serverside buffer was near full before pausing, we
442 * need to have space to write soon, so force playback start */
443 PULSE_WaitForOperation(pa_stream_trigger(wwo
->stream
, PULSE_StreamSuccessCallback
, wwo
));
444 pa_threaded_mainloop_unlock(PULSE_ml
);
450 lpWaveHdr
= (LPWAVEHDR
)param
;
451 /* insert buffer at the end of queue */
454 for (wh
= &(wwo
->lpQueuePtr
); *wh
; wh
= &((*wh
)->lpNext
));
459 wodPlayer_BeginWaveHdr(wwo
,lpWaveHdr
);
460 if (wwo
->state
== WINE_WS_STOPPED
)
461 wwo
->state
= WINE_WS_PLAYING
;
463 wodPlayer_Feed(wwo
, pa_stream_writable_size(wwo
->stream
));
467 case WINE_WM_RESETTING
:
468 wodPlayer_Reset(wwo
);
472 case WINE_WM_BREAKLOOP
:
473 if (wwo
->state
== WINE_WS_PLAYING
&& wwo
->lpLoopPtr
!= NULL
)
474 /* ensure exit at end of current loop */
479 case WINE_WM_FEED
: /* Sent by the pulse thread */
480 wodPlayer_Feed(wwo
, pa_stream_writable_size(wwo
->stream
));
484 case WINE_WM_XRUN
: /* Sent by the pulse thread */
485 WARN("Trying to recover from underrun.\n");
486 /* Return all the queued wavehdrs, so the app will send more data */
487 wodPlayer_NotifyCompletions(wwo
, FALSE
, (pa_usec_t
)-1);
492 case WINE_WM_CLOSING
:
494 wwo
->state
= WINE_WS_CLOSED
;
495 /* sanity check: this should not happen since the device must have been reset before */
496 if (wwo
->lpQueuePtr
|| wwo
->lpPlayPtr
) ERR("out of sync\n");
498 TRACE("Thread exiting.\n");
500 /* shouldn't go here */
503 FIXME("unknown message %d\n", msg
);
509 /**************************************************************************
510 * wodPlayer [internal]
512 * The thread which is responsible for returning WaveHdrs via DriverCallback,
513 * the writing of queued WaveHdrs, and all pause / reset stream management.
515 static DWORD CALLBACK
wodPlayer(LPVOID lpParam
) {
516 WINE_WAVEINST
*wwo
= (WINE_WAVEINST
*)lpParam
;
517 DWORD dwSleepTime
= INFINITE
;
520 wwo
->state
= WINE_WS_STOPPED
;
521 SetEvent(wwo
->hStartUpEvent
);
523 /* Wait for the shortest time before an action is required. If there are
524 * no pending actions, wait forever for a command. */
526 TRACE("Waiting %u ms\n", dwSleepTime
);
527 PULSE_WaitRingMessage(&wwo
->msgRing
, dwSleepTime
);
529 delta_write
= wwo
->timing_info
->write_index
;
530 wodPlayer_ProcessMessages(wwo
);
532 /* Check for a stall situaiton */
533 if (delta_write
== wwo
->timing_info
->write_index
534 && wwo
->lpQueuePtr
&& !wwo
->lpPlayPtr
535 && wwo
->state
!= WINE_WS_STOPPED
)
536 wodPlayer_CheckReleasing(wwo
);
538 /* If there is audio playing, return headers and get next timeout */
539 if (wwo
->state
== WINE_WS_PLAYING
) {
540 dwSleepTime
= wodPlayer_NotifyCompletions(wwo
, FALSE
, wodPlayer_GetStreamTime(wwo
));
542 dwSleepTime
= INFINITE
;
548 /**************************************************************************
551 * Create a new pa_stream and connect it to a sink while creating a new
552 * WINE_WAVEINST to represent the device to the windows application.
554 static DWORD
wodOpen(WORD wDevID
, DWORD_PTR lpdwUser
, LPWAVEOPENDESC lpDesc
, DWORD dwFlags
) {
556 WINE_WAVEINST
*wwo
= NULL
;
557 DWORD ret
= MMSYSERR_NOERROR
;
559 TRACE("(%u, %p, %08X);\n", wDevID
, lpDesc
, dwFlags
);
560 if (lpDesc
== NULL
) {
561 WARN("Invalid Parameter!\n");
562 return MMSYSERR_INVALPARAM
;
565 if (wDevID
>= PULSE_WodNumDevs
) {
566 WARN("Asked for device %d, but only %d known!\n", wDevID
, PULSE_WodNumDevs
);
567 return MMSYSERR_BADDEVICEID
;
569 wdo
= &WOutDev
[wDevID
];
571 wwo
= HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, sizeof(WINE_WAVEINST
));
573 WARN("Out of memory?!\n");
574 return MMSYSERR_NOMEM
;
576 *(WINE_WAVEINST
**)lpdwUser
= wwo
;
578 /* check to see if format is supported and make pa_sample_spec struct */
579 if (!PULSE_SetupFormat(lpDesc
->lpFormat
, &wwo
->sample_spec
)) {
580 WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
581 lpDesc
->lpFormat
->wFormatTag
, lpDesc
->lpFormat
->nChannels
,
582 lpDesc
->lpFormat
->nSamplesPerSec
);
583 ret
= WAVERR_BADFORMAT
;
587 /* Check to see if this was just a query */
588 if (dwFlags
& WAVE_FORMAT_QUERY
) {
589 TRACE("Query format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
590 lpDesc
->lpFormat
->wFormatTag
, lpDesc
->lpFormat
->nChannels
,
591 lpDesc
->lpFormat
->nSamplesPerSec
);
592 ret
= MMSYSERR_NOERROR
;
596 if (TRACE_ON(wave
)) {
597 char t
[PA_SAMPLE_SPEC_SNPRINT_MAX
];
598 pa_sample_spec_snprint(t
, sizeof(t
), &wwo
->sample_spec
);
599 TRACE("Sample spec '%s'\n", t
);
602 wwo
->wFlags
= HIWORD(dwFlags
& CALLBACK_TYPEMASK
);
603 wwo
->waveDesc
= *lpDesc
;
604 PULSE_InitRingMessage(&wwo
->msgRing
);
606 wwo
->stream
= pa_stream_new(PULSE_context
, "WaveOut", &wwo
->sample_spec
, NULL
);
607 /* If server doesn't support sample_spec, it will error out here (re: 24bit) */
609 ret
= WAVERR_BADFORMAT
;
613 /* Setup callbacks */
614 pa_stream_set_write_callback (wwo
->stream
, WAVEOUT_StreamRequestCallback
, wwo
);
615 pa_stream_set_state_callback (wwo
->stream
, PULSE_StreamStateCallback
, wwo
);
616 pa_stream_set_underflow_callback (wwo
->stream
, PULSE_StreamUnderflowCallback
, wwo
);
617 pa_stream_set_moved_callback (wwo
->stream
, PULSE_StreamMovedCallback
, wwo
);
618 pa_stream_set_suspended_callback (wwo
->stream
, PULSE_StreamSuspendedCallback
, wwo
);
620 /* Blank Buffer Attributes */
621 wwo
->buffer_attr
.prebuf
= (uint32_t)-1;
622 wwo
->buffer_attr
.tlength
= (uint32_t)-1;
623 wwo
->buffer_attr
.minreq
= (uint32_t)-1;
624 wwo
->buffer_attr
.maxlength
= (uint32_t)-1;
626 /* Try and connect */
627 TRACE("Connecting stream for playback on %s.\n", wdo
->device_name
);
628 pa_threaded_mainloop_lock(PULSE_ml
);
629 pa_stream_connect_playback(wwo
->stream
, wdo
->device_name
, NULL
, PA_STREAM_AUTO_TIMING_UPDATE
| PA_STREAM_ADJUST_LATENCY
, NULL
, NULL
);
631 /* Wait for connection */
633 pa_context_state_t cstate
= pa_context_get_state(PULSE_context
);
634 pa_stream_state_t sstate
= pa_stream_get_state(wwo
->stream
);
636 if (cstate
== PA_CONTEXT_FAILED
|| cstate
== PA_CONTEXT_TERMINATED
||
637 sstate
== PA_STREAM_FAILED
|| sstate
== PA_STREAM_TERMINATED
) {
638 ERR("Failed to connect stream context object: %s\n", pa_strerror(pa_context_errno(PULSE_context
)));
639 pa_threaded_mainloop_unlock(PULSE_ml
);
640 ret
= MMSYSERR_NODRIVER
;
644 if (sstate
== PA_STREAM_READY
)
647 pa_threaded_mainloop_wait(PULSE_ml
);
649 TRACE("(%p)->stream connected for playback.\n", wwo
);
651 /* Get the pa_timing_info structure */
652 PULSE_WaitForOperation(pa_stream_update_timing_info(wwo
->stream
, PULSE_StreamSuccessCallback
, wwo
));
653 wwo
->timing_info
= pa_stream_get_timing_info(wwo
->stream
);
654 assert(wwo
->timing_info
);
655 pa_threaded_mainloop_unlock(PULSE_ml
);
657 /* Create and start the wodPlayer() thread to manage playback */
658 wwo
->hStartUpEvent
= CreateEventW(NULL
, FALSE
, FALSE
, NULL
);
659 wwo
->hThread
= CreateThread(NULL
, 0, wodPlayer
, (LPVOID
)wwo
, 0, &(wwo
->dwThreadID
));
661 SetThreadPriority(wwo
->hThread
, THREAD_PRIORITY_TIME_CRITICAL
);
663 ERR("Thread creation for the wodPlayer failed!\n");
664 ret
= MMSYSERR_NOMEM
;
667 WaitForSingleObject(wwo
->hStartUpEvent
, INFINITE
);
668 CloseHandle(wwo
->hStartUpEvent
);
669 wwo
->hStartUpEvent
= INVALID_HANDLE_VALUE
;
671 return wodPlayer_NotifyClient (wwo
, WOM_OPEN
, 0L, 0L);
677 if (wwo
->hStartUpEvent
!= INVALID_HANDLE_VALUE
)
678 CloseHandle(wwo
->hStartUpEvent
);
680 if (wwo
->msgRing
.ring_buffer_size
> 0)
681 PULSE_DestroyRingMessage(&wwo
->msgRing
);
684 if (pa_stream_get_state(wwo
->stream
) == PA_STREAM_READY
)
685 pa_stream_disconnect(wwo
->stream
);
686 pa_stream_unref(wwo
->stream
);
689 HeapFree(GetProcessHeap(), 0, wwo
);
694 /**************************************************************************
695 * wodClose [internal]
697 static DWORD
wodClose(WINE_WAVEINST
*wwo
) {
700 TRACE("(%p);\n", wwo
);
702 WARN("Stream instance invalid.\n");
703 return MMSYSERR_INVALHANDLE
;
706 if (wwo
->state
!= WINE_WS_FAILED
) {
707 if (wwo
->lpQueuePtr
&& wwo
->lpPlayPtr
) {
708 WARN("buffers still playing !\n");
709 return WAVERR_STILLPLAYING
;
712 pa_threaded_mainloop_lock(PULSE_ml
);
713 PULSE_WaitForOperation(pa_stream_drain(wwo
->stream
, PULSE_StreamSuccessCallback
, NULL
));
714 pa_stream_disconnect(wwo
->stream
);
715 pa_threaded_mainloop_unlock(PULSE_ml
);
717 if (wwo
->hThread
!= INVALID_HANDLE_VALUE
)
718 PULSE_AddRingMessage(&wwo
->msgRing
, WINE_WM_CLOSING
, 0, TRUE
);
720 PULSE_DestroyRingMessage(&wwo
->msgRing
);
724 pa_stream_unref(wwo
->stream
);
725 ret
= wodPlayer_NotifyClient(wwo
, WOM_CLOSE
, 0L, 0L);
727 HeapFree(GetProcessHeap(), 0, wwo
);
732 /**************************************************************************
733 * wodWrite [internal]
735 static DWORD
wodWrite(WINE_WAVEINST
*wwo
, LPWAVEHDR lpWaveHdr
, DWORD dwSize
) {
736 if (!wwo
|| wwo
->state
== WINE_WS_FAILED
) {
737 WARN("Stream instance invalid.\n");
738 return MMSYSERR_INVALHANDLE
;
741 if (lpWaveHdr
->lpData
== NULL
|| !(lpWaveHdr
->dwFlags
& WHDR_PREPARED
))
742 return WAVERR_UNPREPARED
;
744 if (lpWaveHdr
->dwFlags
& WHDR_INQUEUE
)
745 return WAVERR_STILLPLAYING
;
747 lpWaveHdr
->dwFlags
&= ~WHDR_DONE
;
748 lpWaveHdr
->dwFlags
|= WHDR_INQUEUE
;
749 lpWaveHdr
->lpNext
= 0;
750 lpWaveHdr
->reserved
= 0;
752 PULSE_AddRingMessage(&wwo
->msgRing
, WINE_WM_HEADER
, (DWORD
)lpWaveHdr
, FALSE
);
753 return MMSYSERR_NOERROR
;
756 /**************************************************************************
757 * wodPause [internal]
759 static DWORD
wodPause(WINE_WAVEINST
*wwo
) {
760 if (!wwo
|| wwo
->state
== WINE_WS_FAILED
) {
761 WARN("Stream instance invalid.\n");
762 return MMSYSERR_INVALHANDLE
;
765 PULSE_AddRingMessage(&wwo
->msgRing
, WINE_WM_PAUSING
, 0, TRUE
);
766 return MMSYSERR_NOERROR
;
769 /**************************************************************************
770 * wodGetPosition [internal]
772 static DWORD
wodGetPosition(WINE_WAVEINST
*wwo
, LPMMTIME lpTime
, DWORD uSize
) {
773 pa_usec_t time
, temp
;
775 if (!wwo
|| wwo
->state
== WINE_WS_FAILED
) {
776 WARN("Stream instance invalid.\n");
777 return MMSYSERR_INVALHANDLE
;
780 if (lpTime
== NULL
) return MMSYSERR_INVALPARAM
;
782 time
= wodPlayer_GetStreamTime(wwo
);
784 temp
= pa_bytes_to_usec(wwo
->dwLastReset
, &wwo
->sample_spec
);
785 if (time
> temp
) time
-= temp
; else time
= 0;
787 return PULSE_UsecToMMTime(time
, lpTime
, &wwo
->sample_spec
);
789 /**************************************************************************
790 * wodBreakLoop [internal]
792 static DWORD
wodBreakLoop(WINE_WAVEINST
*wwo
) {
793 if (!wwo
|| wwo
->state
== WINE_WS_FAILED
) {
794 WARN("Stream instance invalid.\n");
795 return MMSYSERR_INVALHANDLE
;
798 PULSE_AddRingMessage(&wwo
->msgRing
, WINE_WM_BREAKLOOP
, 0, TRUE
);
799 return MMSYSERR_NOERROR
;
802 /**************************************************************************
803 * wodGetDevCaps [internal]
805 static DWORD
wodGetDevCaps(DWORD wDevID
, LPWAVEOUTCAPSW lpCaps
, DWORD dwSize
) {
806 TRACE("(%u, %p, %u);\n", wDevID
, lpCaps
, dwSize
);
808 if (lpCaps
== NULL
) return MMSYSERR_NOTENABLED
;
810 if (wDevID
>= PULSE_WodNumDevs
) {
811 TRACE("Asked for device %d, but only %d known!\n", wDevID
, PULSE_WodNumDevs
);
812 return MMSYSERR_INVALHANDLE
;
815 memcpy(lpCaps
, &(WOutDev
[wDevID
].caps
.out
), min(dwSize
, sizeof(*lpCaps
)));
816 return MMSYSERR_NOERROR
;
819 /**************************************************************************
820 * wodGetNumDevs [internal]
821 * Context-sanity check here, as if we respond with 0, WINE will move on
822 * to the next waveout driver.
824 static DWORD
wodGetNumDevs(void) {
825 if (!PULSE_ml
|| !PULSE_context
|| pa_context_get_state(PULSE_context
) != PA_CONTEXT_READY
)
828 return PULSE_WodNumDevs
;
831 /**************************************************************************
832 * wodGetVolume [internal]
834 static DWORD
wodGetVolume(WINE_WAVEINST
*wwo
, LPDWORD lpdwVol
) {
835 double value1
, value2
;
838 if (!wwo
|| wwo
->state
== WINE_WS_FAILED
) {
839 WARN("Stream instance invalid.\n");
840 return MMSYSERR_INVALHANDLE
;
843 TRACE("(%p, %p);\n", wwo
, lpdwVol
);
846 return MMSYSERR_NOTENABLED
;
848 pa_threaded_mainloop_lock(PULSE_ml
);
849 if (wwo
->stream
&& PULSE_context
&& pa_context_get_state(PULSE_context
) == PA_CONTEXT_READY
&&
850 pa_stream_get_state(wwo
->stream
) == PA_STREAM_READY
) {
851 PULSE_WaitForOperation(pa_context_get_sink_input_info(PULSE_context
, pa_stream_get_index(wwo
->stream
), WAVEOUT_SinkInputInfoCallback
, wwo
));
853 pa_threaded_mainloop_unlock(PULSE_ml
);
856 if (wwo
->volume
.channels
== 2) {
857 value1
= pa_sw_volume_to_linear(wwo
->volume
.values
[0]);
858 value2
= pa_sw_volume_to_linear(wwo
->volume
.values
[1]);
860 value1
= pa_sw_volume_to_linear(pa_cvolume_avg(&wwo
->volume
));
864 wleft
= 0xFFFFl
* value1
;
865 wright
= 0xFFFFl
* value2
;
869 if (wright
> 0xFFFFl
)
872 *lpdwVol
= (WORD
)wleft
+ (WORD
)(wright
<< 16);
874 return MMSYSERR_NOERROR
;
877 /**************************************************************************
878 * wodSetVolume [internal]
880 static DWORD
wodSetVolume(WINE_WAVEINST
*wwo
, DWORD dwParam1
) {
881 double value1
, value2
;
883 TRACE("(%p, %08X);\n", wwo
, dwParam1
);
884 if (!wwo
|| wwo
->state
== WINE_WS_FAILED
) {
885 WARN("Stream instance invalid.\n");
886 return MMSYSERR_INVALHANDLE
;
889 value1
= (double)LOWORD(dwParam1
)/(double)0xFFFFl
;
890 value2
= (double)HIWORD(dwParam1
)/(double)0xFFFFl
;
892 if (wwo
->sample_spec
.channels
== 2) {
893 wwo
->volume
.channels
= 2;
894 wwo
->volume
.values
[0] = pa_sw_volume_from_linear(value1
);
895 wwo
->volume
.values
[1] = pa_sw_volume_from_linear(value2
);
897 if (value1
!= value2
) FIXME("Non-stereo streams can't pan!\n");
898 wwo
->volume
.channels
= wwo
->sample_spec
.channels
;
899 pa_cvolume_set(&wwo
->volume
, wwo
->volume
.channels
, pa_sw_volume_from_linear(value1
> value2
? value1
: value2
));
902 if (TRACE_ON(wave
)) {
903 char s
[PA_CVOLUME_SNPRINT_MAX
];
904 pa_cvolume_snprint(s
, PA_CVOLUME_SNPRINT_MAX
, &wwo
->volume
);
908 pa_threaded_mainloop_lock(PULSE_ml
);
909 if (!wwo
->stream
|| !PULSE_context
|| pa_context_get_state(PULSE_context
) != PA_CONTEXT_READY
||
910 pa_stream_get_state(wwo
->stream
) != PA_STREAM_READY
|| !pa_cvolume_valid(&wwo
->volume
)) {
911 pa_threaded_mainloop_unlock(PULSE_ml
);
912 return MMSYSERR_NOERROR
;
915 PULSE_WaitForOperation(pa_context_set_sink_input_volume(PULSE_context
,
916 pa_stream_get_index(wwo
->stream
), &wwo
->volume
,
917 PULSE_ContextSuccessCallback
, wwo
));
918 pa_threaded_mainloop_unlock(PULSE_ml
);
919 return MMSYSERR_NOERROR
;
922 /**************************************************************************
923 * wodRestart [internal]
925 static DWORD
wodRestart(WINE_WAVEINST
*wwo
) {
926 if (!wwo
|| wwo
->state
== WINE_WS_FAILED
) {
927 WARN("Stream instance invalid.\n");
928 return MMSYSERR_INVALHANDLE
;
931 if (wwo
->state
== WINE_WS_PAUSED
)
932 PULSE_AddRingMessage(&wwo
->msgRing
, WINE_WM_RESTARTING
, 0, TRUE
);
933 return MMSYSERR_NOERROR
;
936 /**************************************************************************
937 * wodReset [internal]
939 static DWORD
wodReset(WINE_WAVEINST
*wwo
) {
940 if (!wwo
|| wwo
->state
== WINE_WS_FAILED
) {
941 WARN("Stream instance invalid.\n");
942 return MMSYSERR_INVALHANDLE
;
945 PULSE_AddRingMessage(&wwo
->msgRing
, WINE_WM_RESETTING
, 0, TRUE
);
946 return MMSYSERR_NOERROR
;
949 /**************************************************************************
950 * wodDevInterfaceSize [internal]
952 static DWORD
wodDevInterfaceSize(UINT wDevID
, LPDWORD dwParam1
) {
954 *dwParam1
= MultiByteToWideChar(CP_ACP
, 0, WOutDev
[wDevID
].interface_name
, -1, NULL
, 0) * sizeof(WCHAR
);
955 return MMSYSERR_NOERROR
;
958 /**************************************************************************
959 * wodDevInterface [internal]
961 static DWORD
wodDevInterface(UINT wDevID
, PWCHAR dwParam1
, DWORD dwParam2
) {
962 if (dwParam2
>= MultiByteToWideChar(CP_ACP
, 0, WOutDev
[wDevID
].interface_name
, -1,
963 NULL
, 0 ) * sizeof(WCHAR
))
965 MultiByteToWideChar(CP_ACP
, 0, WOutDev
[wDevID
].interface_name
, -1,
966 dwParam1
, dwParam2
/ sizeof(WCHAR
));
967 return MMSYSERR_NOERROR
;
969 return MMSYSERR_INVALPARAM
;
972 DWORD
wodDsDesc(UINT wDevID
, PDSDRIVERDESC desc
) {
973 TRACE("(%u, %p)\n", wDevID
, desc
);
974 *desc
= WOutDev
[wDevID
].ds_desc
;
975 return MMSYSERR_NOERROR
;
978 /**************************************************************************
979 * wodMessage (WINEPULSE.@)
981 DWORD WINAPI
PULSE_wodMessage(UINT wDevID
, UINT wMsg
, DWORD_PTR dwUser
, DWORD_PTR dwParam1
, DWORD_PTR dwParam2
) {
991 /* WaveOut Playback related functions */
992 case WODM_OPEN
: return wodOpen (wDevID
, dwUser
, (LPWAVEOPENDESC
)dwParam1
, dwParam2
);
993 case WODM_CLOSE
: return wodClose ((WINE_WAVEINST
*)dwUser
);
994 case WODM_WRITE
: return wodWrite ((WINE_WAVEINST
*)dwUser
, (LPWAVEHDR
)dwParam1
, dwParam2
);
995 case WODM_PAUSE
: return wodPause ((WINE_WAVEINST
*)dwUser
);
996 case WODM_GETPOS
: return wodGetPosition ((WINE_WAVEINST
*)dwUser
, (LPMMTIME
)dwParam1
, dwParam2
);
997 case WODM_BREAKLOOP
: return wodBreakLoop ((WINE_WAVEINST
*)dwUser
);
998 case WODM_RESTART
: return wodRestart ((WINE_WAVEINST
*)dwUser
);
999 case WODM_RESET
: return wodReset ((WINE_WAVEINST
*)dwUser
);
1001 case WODM_GETVOLUME
: return wodGetVolume ((WINE_WAVEINST
*)dwUser
, (LPDWORD
)dwParam1
);
1002 case WODM_SETVOLUME
: return wodSetVolume ((WINE_WAVEINST
*)dwUser
, dwParam1
);
1005 case WODM_UNPREPARE
:
1010 case WODM_GETPLAYBACKRATE
:
1011 case WODM_SETPLAYBACKRATE
:
1012 return MMSYSERR_NOTSUPPORTED
;
1014 /* Device enumeration, directsound and capabilities */
1015 case WODM_GETDEVCAPS
: return wodGetDevCaps (wDevID
, (LPWAVEOUTCAPSW
)dwParam1
, dwParam2
);
1016 case WODM_GETNUMDEVS
: return wodGetNumDevs ();
1017 case DRV_QUERYDEVICEINTERFACESIZE
: return wodDevInterfaceSize (wDevID
, (LPDWORD
)dwParam1
);
1018 case DRV_QUERYDEVICEINTERFACE
: return wodDevInterface (wDevID
, (PWCHAR
)dwParam1
, dwParam2
);
1019 case DRV_QUERYDSOUNDIFACE
: return MMSYSERR_NOTSUPPORTED
;
1020 case DRV_QUERYDSOUNDDESC
: return wodDsDesc (wDevID
, (PDSDRIVERDESC
)dwParam1
);
1023 FIXME("unknown message %d!\n", wMsg
);
1025 return MMSYSERR_NOTSUPPORTED
;
1028 #else /* !HAVE_PULSEAUDIO */
1030 /**************************************************************************
1031 * wodMessage (WINEPULSE.@)
1033 DWORD WINAPI
PULSE_wodMessage(WORD wDevID
, WORD wMsg
, DWORD dwUser
,
1034 DWORD dwParam1
, DWORD dwParam2
) {
1035 FIXME("(%u, %04X, %08X, %08X, %08X):stub\n", wDevID
, wMsg
, dwUser
,
1036 dwParam1
, dwParam2
);
1037 return MMSYSERR_NOTENABLED
;
1040 #endif /* HAVE_PULSEAUDIO */