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
37 #include <winepulse.h>
39 #include "wine/debug.h"
41 WINE_DEFAULT_DEBUG_CHANNEL(wave
);
45 /* state diagram for waveOut writing:
47 * +---------+-------------+---------------+---------------------------------+
48 * | state | function | event | new state |
49 * +---------+-------------+---------------+---------------------------------+
50 * | | open() | | STOPPED |
51 * | PAUSED | write() | | PAUSED |
52 * | STOPPED | write() | <thrd create> | PLAYING |
53 * | PLAYING | write() | HEADER | PLAYING |
54 * | (other) | write() | <error> | |
55 * | (any) | pause() | PAUSING | PAUSED |
56 * | PAUSED | restart() | RESTARTING | PLAYING (if no thrd => STOPPED) |
57 * | PAUSED | reset() | RESETTING | PAUSED |
58 * | (other) | reset() | RESETTING | STOPPED |
59 * | (any) | close() | CLOSING | CLOSED |
60 * +---------+-------------+---------------+---------------------------------+
64 * - It is currently unknown if pausing in a loop works the same as expected.
67 /*======================================================================*
68 * WAVE OUT specific PulseAudio Callbacks *
69 *======================================================================*/
71 /**************************************************************************
72 * WAVEOUT_StreamStartedCallback [internal]
74 * Called by the pulse mainloop whenever stream playback resumes after an
75 * underflow or an initial start. Requires libpulse >= 0.9.11
77 #if PA_API_VERSION >= 12
78 static void WAVEOUT_StreamStartedCallback(pa_stream
*s
, void *userdata
) {
79 WINE_WAVEINST
*wwo
= (WINE_WAVEINST
*)userdata
;
82 TRACE("Audio flowing.\n");
84 if (wwo
->buffer_attr
.tlength
== 0)
85 wwo
->buffer_attr
.tlength
= (uint32_t) -1;
87 if (wwo
->hThread
!= INVALID_HANDLE_VALUE
&& wwo
->msgRing
.ring_buffer_size
) {
88 PULSE_AddRingMessage(&wwo
->msgRing
, WINE_WM_STARTING
, 0, FALSE
);
93 /**************************************************************************
94 * WAVEOUT_StreamRequestCallback
96 * Called by the pulse mainloop whenever it wants audio data.
98 static void WAVEOUT_StreamRequestCallback(pa_stream
*s
, size_t nbytes
, void *userdata
) {
99 WINE_WAVEINST
*ww
= (WINE_WAVEINST
*)userdata
;
101 TRACE("Asking to be fed %u bytes\n", nbytes
);
103 /* Make sure that the player/recorder is running */
104 if (ww
->hThread
!= INVALID_HANDLE_VALUE
&& ww
->msgRing
.messages
) {
105 PULSE_AddRingMessage(&ww
->msgRing
, WINE_WM_FEED
, (DWORD
)nbytes
, FALSE
);
109 /**************************************************************************
110 * WAVEOUT_StreamTimingInfoUpdateCallback [internal]
112 * Called by the pulse mainloop whenever the timing info gets updated, we
113 * use this to send the started signal */
114 static void WAVEOUT_StreamTimingInfoUpdateCallback(pa_stream
*s
, void *userdata
) {
115 WINE_WAVEINST
*wwo
= (WINE_WAVEINST
*)userdata
;
118 if (!wwo
->is_releasing
&& wwo
->timing_info
&& wwo
->timing_info
->playing
) {
119 TRACE("Audio flowing.\n");
121 if (wwo
->buffer_attr
.tlength
== 0)
122 wwo
->buffer_attr
.tlength
= (uint32_t) -1;
124 if (wwo
->hThread
!= INVALID_HANDLE_VALUE
&& wwo
->msgRing
.ring_buffer_size
)
125 PULSE_AddRingMessage(&wwo
->msgRing
, WINE_WM_STARTING
, 0, FALSE
);
129 /**************************************************************************
130 * WAVEOUT_SinkInputInfoCallback [internal]
132 * Called by the pulse thread. Used for wodGetVolume.
134 static void WAVEOUT_SinkInputInfoCallback(pa_context
*c
, const pa_sink_input_info
*i
, int eol
, void *userdata
) {
135 WINE_WAVEINST
* wwo
= (WINE_WAVEINST
*)userdata
;
137 for (wwo
->volume
.channels
= 0; wwo
->volume
.channels
!= i
->volume
.channels
; wwo
->volume
.channels
++)
138 wwo
->volume
.values
[wwo
->volume
.channels
] = i
->volume
.values
[wwo
->volume
.channels
];
139 pa_threaded_mainloop_signal(PULSE_ml
, 0);
143 /*======================================================================*
144 * "Low level" WAVE OUT implementation *
145 *======================================================================*/
147 /**************************************************************************
148 * wodPlayer_NotifyClient [internal]
150 static DWORD
wodPlayer_NotifyClient(WINE_WAVEINST
* wwo
, WORD wMsg
, DWORD dwParam1
, DWORD dwParam2
) {
151 TRACE("wMsg = 0x%04x dwParm1 = %04X dwParam2 = %04X\n", wMsg
, dwParam1
, dwParam2
);
157 if (wwo
->wFlags
!= DCB_NULL
&&
158 !DriverCallback(wwo
->waveDesc
.dwCallback
, wwo
->wFlags
, (HDRVR
)wwo
->waveDesc
.hWave
,
159 wMsg
, wwo
->waveDesc
.dwInstance
, dwParam1
, dwParam2
)) {
160 WARN("can't notify client !\n");
161 return MMSYSERR_ERROR
;
165 FIXME("Unknown callback message %u\n", wMsg
);
166 return MMSYSERR_INVALPARAM
;
168 return MMSYSERR_NOERROR
;
171 /**************************************************************************
172 * wodPlayer_BeginWaveHdr [internal]
174 * Makes the specified lpWaveHdr the currently playing wave header.
175 * If the specified wave header is a begin loop and we're not already in
176 * a loop, setup the loop.
178 static void wodPlayer_BeginWaveHdr(WINE_WAVEINST
* wwo
, LPWAVEHDR lpWaveHdr
) {
179 wwo
->lpPlayPtr
= lpWaveHdr
;
181 if (!lpWaveHdr
) return;
183 if (lpWaveHdr
->dwFlags
& WHDR_BEGINLOOP
) {
184 if (wwo
->lpLoopPtr
) {
185 WARN("Already in a loop. Discarding loop on this header (%p)\n", lpWaveHdr
);
187 TRACE("Starting loop (%dx) with %p\n", lpWaveHdr
->dwLoops
, lpWaveHdr
);
188 wwo
->lpLoopPtr
= lpWaveHdr
;
189 /* Windows does not touch WAVEHDR.dwLoops,
190 * so we need to make an internal copy */
191 wwo
->dwLoops
= lpWaveHdr
->dwLoops
;
194 wwo
->dwPartialOffset
= 0;
197 /**************************************************************************
198 * wodPlayer_PlayPtrNext [internal]
200 * Advance the play pointer to the next waveheader, looping if required.
202 static LPWAVEHDR
wodPlayer_PlayPtrNext(WINE_WAVEINST
* wwo
) {
203 LPWAVEHDR lpWaveHdr
= wwo
->lpPlayPtr
;
205 wwo
->dwPartialOffset
= 0;
206 if ((lpWaveHdr
->dwFlags
& WHDR_ENDLOOP
) && wwo
->lpLoopPtr
) {
207 /* We're at the end of a loop, loop if required */
208 if (--wwo
->dwLoops
> 0) {
209 wwo
->lpPlayPtr
= wwo
->lpLoopPtr
;
211 /* Handle overlapping loops correctly */
212 if (wwo
->lpLoopPtr
!= lpWaveHdr
&& (lpWaveHdr
->dwFlags
& WHDR_BEGINLOOP
)) {
213 FIXME("Correctly handled case ? (ending loop buffer also starts a new loop)\n");
214 /* shall we consider the END flag for the closing loop or for
215 * the opening one or for both ???
216 * code assumes for closing loop only
219 lpWaveHdr
= lpWaveHdr
->lpNext
;
221 wwo
->lpLoopPtr
= NULL
;
222 wodPlayer_BeginWaveHdr(wwo
, lpWaveHdr
);
225 /* We're not in a loop. Advance to the next wave header */
226 wodPlayer_BeginWaveHdr(wwo
, lpWaveHdr
= lpWaveHdr
->lpNext
);
232 /**************************************************************************
233 * wodPlayer_CheckReleasing [internal]
235 * Check to make sure that playback has not stalled
237 static void wodPlayer_CheckReleasing(WINE_WAVEINST
*wwo
) {
238 LPWAVEHDR lpWaveHdr
= wwo
->lpQueuePtr
;
240 pa_threaded_mainloop_lock(PULSE_ml
);
241 if (!wwo
->timing_info
->playing
&& !wwo
->is_releasing
&& lpWaveHdr
&& !wwo
->lpPlayPtr
&& wwo
->state
== WINE_WS_PLAYING
) {
243 /* Try and adjust the buffer attributes so there is less latency.
244 * Because of bugs this call does not work on older servers. Once
245 * new version of pulseaudio become ubiquitous we will drop support for
246 * versions before 0.9.15 because they have too many bugs.*/
247 if (wwo
->buffer_attr
.tlength
== 0 &&
248 pa_context_get_server_protocol_version(PULSE_context
) >= 15) {
249 wwo
->buffer_attr
.tlength
= wwo
->timing_info
->write_index
;
250 wwo
->buffer_attr
.prebuf
= (uint32_t) -1;
251 wwo
->buffer_attr
.minreq
= (uint32_t) -1;
252 wwo
->buffer_attr
.maxlength
= (uint32_t) -1;
253 WARN("Asking for new buffer tlength of %ums (%u bytes)\n", (unsigned int)(pa_bytes_to_usec(wwo
->buffer_attr
.tlength
, &wwo
->sample_spec
)/1000), wwo
->buffer_attr
.tlength
);
254 pa_stream_set_buffer_attr(wwo
->stream
, &wwo
->buffer_attr
, PULSE_StreamSuccessCallback
, wwo
);
256 /* Fake playback start earlier, introducing unknown latency */
257 pa_gettimeofday(&wwo
->started_releasing
);
258 wwo
->is_releasing
= TRUE
;
259 wwo
->releasing_offset
= wwo
->lpQueuePtr
->reserved
;
260 WARN("Starting to release early.\n");
263 pa_threaded_mainloop_unlock(PULSE_ml
);
266 /**************************************************************************
267 * wodPlayer_NotifyCompletions [internal]
269 * Notifies and remove from queue all wavehdrs which have been played to
270 * the speaker based on a reference time of (theoretical) playback start. If
271 * force is true, we notify all wavehdrs and remove them all from the queue
272 * even if they are unplayed or part of a loop. We return the time to wait
273 * until the next wavehdr needs to be freed, or INFINITE if there are no more
274 * wavehdrs. We use timevals rather than the stream position data that pulse
275 * gives us as realeasing needs to be constant and smooth for good application
278 static DWORD
wodPlayer_NotifyCompletions(WINE_WAVEINST
* wwo
, BOOL force
) {
279 LPWAVEHDR lpWaveHdr
= wwo
->lpQueuePtr
;
283 time
= pa_bytes_to_usec(wwo
->releasing_offset
, &wwo
->sample_spec
);
284 if (wwo
->is_releasing
)
285 time
+= pa_timeval_age(&wwo
->started_releasing
);
289 /* Start from lpQueuePtr and keep notifying until:
290 * - we hit an unwritten wavehdr
291 * - we hit the beginning of a running loop
292 * - we hit a wavehdr which hasn't finished playing
294 if (lpWaveHdr
== wwo
->lpPlayPtr
) { TRACE("play %p\n", lpWaveHdr
); return INFINITE
; }
295 if (lpWaveHdr
== wwo
->lpLoopPtr
) { TRACE("loop %p\n", lpWaveHdr
); return INFINITE
; }
297 /* See if this data has been played, and if not, return when it will have been */
298 wait
= pa_bytes_to_usec(lpWaveHdr
->reserved
+ lpWaveHdr
->dwBufferLength
, &wwo
->sample_spec
);
300 wait
= ((wait
- time
) + 999) / 1000;
305 /* return the wavehdr */
306 wwo
->lpQueuePtr
= lpWaveHdr
->lpNext
;
307 lpWaveHdr
->dwFlags
&= ~WHDR_INQUEUE
;
308 lpWaveHdr
->dwFlags
|= WHDR_DONE
;
310 wodPlayer_NotifyClient(wwo
, WOM_DONE
, (DWORD
)lpWaveHdr
, 0);
311 lpWaveHdr
= wwo
->lpQueuePtr
;
313 /* No more wavehdrs */
314 TRACE("Empty queue\n");
318 /**************************************************************************
319 * wodPlayer_WriteMax [internal]
321 * Write either how much free space or how much data we have, depending on
324 static int wodPlayer_WriteMax(WINE_WAVEINST
*wwo
, size_t *space
) {
325 LPWAVEHDR lpWaveHdr
= wwo
->lpPlayPtr
;
326 size_t toWrite
= min(lpWaveHdr
->dwBufferLength
- wwo
->dwPartialOffset
, *space
);
328 if (toWrite
> 0 && pa_stream_write(wwo
->stream
, lpWaveHdr
->lpData
+ wwo
->dwPartialOffset
, toWrite
, NULL
, 0, PA_SEEK_RELATIVE
) >= 0)
329 TRACE("Writing wavehdr %p.%u[%u]\n", lpWaveHdr
, wwo
->dwPartialOffset
, lpWaveHdr
->dwBufferLength
);
333 /* Check to see if we wrote all of the wavehdr */
334 if ((wwo
->dwPartialOffset
+= toWrite
) >= lpWaveHdr
->dwBufferLength
)
335 wodPlayer_PlayPtrNext(wwo
);
342 /**************************************************************************
343 * wodPlayer_Feed [internal]
345 * Feed as much sound data as we can into pulse using wodPlayer_WriteMax.
346 * size_t space _must_ have come from either pa_stream_writable_size() or
347 * the value from a stream write callback, as if it isn't you run the risk
348 * of a buffer overflow in which audio data will be lost.
350 static void wodPlayer_Feed(WINE_WAVEINST
* wwo
, size_t space
) {
352 /* No more room... no need to try to feed */
353 if (space
== 0) return;
355 if (!wwo
->stream
|| !PULSE_context
||
356 pa_context_get_state(PULSE_context
) != PA_CONTEXT_READY
||
357 pa_stream_get_state(wwo
->stream
) != PA_STREAM_READY
)
360 pa_threaded_mainloop_lock(PULSE_ml
);
361 /* Feed from a partial wavehdr */
362 if (wwo
->lpPlayPtr
&& wwo
->dwPartialOffset
!= 0)
363 wodPlayer_WriteMax(wwo
, &space
);
365 /* Feed wavehdrs until we run out of wavehdrs or buffer space */
366 if (wwo
->dwPartialOffset
== 0 && wwo
->lpPlayPtr
) {
368 wwo
->lpPlayPtr
->reserved
= wwo
->timing_info
->write_index
;
369 } while (wodPlayer_WriteMax(wwo
, &space
) > 0 && wwo
->lpPlayPtr
&& space
> 0);
371 pa_threaded_mainloop_unlock(PULSE_ml
);
374 /**************************************************************************
375 * wodPlayer_Reset [internal]
377 * wodPlayer helper. Resets current output stream.
379 static void wodPlayer_Reset(WINE_WAVEINST
* wwo
) {
380 enum win_wm_message msg
;
384 TRACE("(%p)\n", wwo
);
386 /* remove any buffer */
387 wodPlayer_NotifyCompletions(wwo
, TRUE
);
389 wwo
->lpPlayPtr
= wwo
->lpQueuePtr
= wwo
->lpLoopPtr
= NULL
;
390 if (wwo
->state
!= WINE_WS_PAUSED
)
391 wwo
->state
= WINE_WS_STOPPED
;
392 wwo
->dwPartialOffset
= 0;
396 pa_context_get_state(PULSE_context
) != PA_CONTEXT_READY
||
397 pa_stream_get_state(wwo
->stream
) != PA_STREAM_READY
) {
401 pa_threaded_mainloop_lock(PULSE_ml
);
403 /* flush the output buffer of written data*/
404 PULSE_WaitForOperation(pa_stream_flush(wwo
->stream
, PULSE_StreamSuccessCallback
, NULL
));
406 /* Reset the written byte count as some data may have been flushed */
407 if (wwo
->timing_info
->write_index_corrupt
)
408 PULSE_WaitForOperation(pa_stream_update_timing_info(wwo
->stream
, PULSE_StreamSuccessCallback
, wwo
));
409 wwo
->releasing_offset
= wwo
->last_reset
= wwo
->timing_info
->write_index
;
410 if (wwo
->is_releasing
)
411 pa_gettimeofday(&wwo
->started_releasing
);
413 /* return all pending headers in queue */
414 EnterCriticalSection(&wwo
->msgRing
.msg_crst
);
415 while (PULSE_RetrieveRingMessage(&wwo
->msgRing
, &msg
, ¶m
, &ev
)) {
416 if (msg
!= WINE_WM_HEADER
) {
420 ((LPWAVEHDR
)param
)->dwFlags
&= ~WHDR_INQUEUE
;
421 ((LPWAVEHDR
)param
)->dwFlags
|= WHDR_DONE
;
422 wodPlayer_NotifyClient(wwo
, WOM_DONE
, param
, 0);
424 PULSE_ResetRingMessage(&wwo
->msgRing
);
425 LeaveCriticalSection(&wwo
->msgRing
.msg_crst
);
427 pa_threaded_mainloop_unlock(PULSE_ml
);
430 /**************************************************************************
431 * wodPlayer_Underrun [internal]
433 * wodPlayer helper. Deal with a stream underrun.
435 static void wodPlayer_Underrun(WINE_WAVEINST
* wwo
) {
437 pa_threaded_mainloop_lock(PULSE_ml
);
439 /* Ask for a timing update */
440 PULSE_WaitForOperation(pa_stream_update_timing_info(wwo
->stream
, PULSE_StreamSuccessCallback
, wwo
));
442 /* See if we recovered while the message was waiting in the queue */
443 if (wwo
->timing_info
->playing
) {
444 TRACE("False alarm\n");
445 pa_threaded_mainloop_unlock(PULSE_ml
);
449 if (wwo
->lpPlayPtr
) {
452 TRACE("There is queued data. Trying to recover.\n");
454 /* Ask for a timing update */
455 if (wwo
->timing_info
->write_index_corrupt
)
456 PULSE_WaitForOperation(pa_stream_update_timing_info(wwo
->stream
, PULSE_StreamSuccessCallback
, wwo
));
457 space
= pa_stream_writable_size(wwo
->stream
);
458 wodPlayer_Feed(wwo
, space
);
460 WARN("Stream underrun!\n");
461 wwo
->is_releasing
= FALSE
;
462 wwo
->releasing_offset
= wwo
->timing_info
->write_index
;
463 pa_threaded_mainloop_unlock(PULSE_ml
);
467 /**************************************************************************
468 * wodPlayer_ProcessMessages [internal]
470 static DWORD
wodPlayer_ProcessMessages(WINE_WAVEINST
* wwo
) {
472 enum win_wm_message msg
;
473 DWORD param
, msgcount
= 0;
476 while (PULSE_RetrieveRingMessage(&wwo
->msgRing
, &msg
, ¶m
, &ev
)) {
477 TRACE("Received %s %x\n", PULSE_getCmdString(msg
), param
);
481 case WINE_WM_PAUSING
:
482 wwo
->state
= WINE_WS_PAUSED
;
484 pa_threaded_mainloop_lock(PULSE_ml
);
485 PULSE_WaitForOperation(pa_stream_cork(wwo
->stream
, 1, PULSE_StreamSuccessCallback
, wwo
));
486 /* save how far we are, as releasing will restart from here */
487 if (wwo
->is_releasing
)
488 wwo
->releasing_offset
= wwo
->timing_info
->write_index
;
489 wwo
->is_releasing
= FALSE
;
490 pa_threaded_mainloop_unlock(PULSE_ml
);
494 case WINE_WM_RESTARTING
:
495 if (wwo
->state
== WINE_WS_PAUSED
) {
496 wwo
->state
= WINE_WS_PLAYING
;
497 pa_threaded_mainloop_lock(PULSE_ml
);
498 PULSE_WaitForOperation(pa_stream_cork(wwo
->stream
, 0, PULSE_StreamSuccessCallback
, wwo
));
499 /* If the serverside buffer was near full before pause, we need to
500 * have space to write soon, so force playback start */
501 PULSE_WaitForOperation(pa_stream_trigger(wwo
->stream
, PULSE_StreamSuccessCallback
, wwo
));
502 pa_threaded_mainloop_unlock(PULSE_ml
);
508 lpWaveHdr
= (LPWAVEHDR
)param
;
509 /* insert buffer at the end of queue */
512 for (wh
= &(wwo
->lpQueuePtr
); *wh
; wh
= &((*wh
)->lpNext
));
517 wodPlayer_BeginWaveHdr(wwo
,lpWaveHdr
);
518 if (wwo
->state
== WINE_WS_STOPPED
)
519 wwo
->state
= WINE_WS_PLAYING
;
521 wodPlayer_Feed(wwo
, pa_stream_writable_size(wwo
->stream
));
525 case WINE_WM_RESETTING
:
526 wodPlayer_Reset(wwo
);
530 case WINE_WM_BREAKLOOP
:
531 if (wwo
->state
== WINE_WS_PLAYING
&& wwo
->lpLoopPtr
!= NULL
)
532 /* ensure exit at end of current loop */
537 case WINE_WM_FEED
: /* Sent by the pulse thread */
538 wodPlayer_Feed(wwo
, pa_stream_writable_size(wwo
->stream
));
542 case WINE_WM_XRUN
: /* Sent by the pulse thread */
543 if ((DWORD
)param
== 1) {
544 ERR("Buffer overflow!\n");
545 pa_threaded_mainloop_lock(PULSE_ml
);
546 PULSE_WaitForOperation(pa_stream_drain(wwo
->stream
, PULSE_StreamSuccessCallback
, wwo
));
547 wwo
->is_releasing
= FALSE
;
548 pa_threaded_mainloop_unlock(PULSE_ml
);
550 wodPlayer_Underrun(wwo
);
554 case WINE_WM_STARTING
: /* Sent by the pulse thread */
555 /* Start releasing wavehdrs if we haven't already */
556 if (!wwo
->is_releasing
) {
557 wwo
->is_releasing
= TRUE
;
558 pa_gettimeofday(&wwo
->started_releasing
);
563 case WINE_WM_CLOSING
: /* If param = 1, close because of a failure */
565 if ((DWORD
)param
== 1) {
566 /* If we are here, the stream has failed */
567 wwo
->state
= WINE_WS_FAILED
;
569 PULSE_DestroyRingMessage(&wwo
->msgRing
);
570 wodPlayer_NotifyCompletions(wwo
, TRUE
);
571 wodPlayer_NotifyClient(wwo
, WOM_CLOSE
, 0L, 0L);
572 wwo
->lpPlayPtr
= wwo
->lpQueuePtr
= wwo
->lpLoopPtr
= NULL
;
573 pa_threaded_mainloop_lock(PULSE_ml
);
574 pa_stream_disconnect(wwo
->stream
);
575 pa_threaded_mainloop_unlock(PULSE_ml
);
576 TRACE("Thread exiting because of failure.\n");
578 /* Stream instance will get de-refferenced upon close */
580 wwo
->state
= WINE_WS_CLOSED
;
581 /* sanity check: this should not happen since the device must have been reset before */
582 if (wwo
->lpQueuePtr
|| wwo
->lpPlayPtr
) ERR("out of sync\n");
584 TRACE("Thread exiting.\n");
586 /* shouldn't go here */
589 FIXME("unknown message %d\n", msg
);
597 /**************************************************************************
598 * wodPlayer [internal]
600 * The thread which is responsible for returning WaveHdrs via DriverCallback,
601 * the writing of queued WaveHdrs, and all pause / reset stream management.
603 static DWORD CALLBACK
wodPlayer(LPVOID lpParam
) {
604 WINE_WAVEINST
*wwo
= (WINE_WAVEINST
*)lpParam
;
605 DWORD dwSleepTime
= INFINITE
;
607 wwo
->state
= WINE_WS_STOPPED
;
608 SetEvent(wwo
->hStartUpEvent
);
610 /* Wait for the shortest time before an action is required. If there are no
611 * pending actions, wait forever for a command. */
613 TRACE("Waiting %u ms\n", dwSleepTime
);
614 PULSE_WaitRingMessage(&wwo
->msgRing
, dwSleepTime
);
616 /* If no messages were processed during the timeout it might be because audio
617 * is not flowing yet, so check. */
618 if (wodPlayer_ProcessMessages(wwo
) == 0)
619 wodPlayer_CheckReleasing(wwo
);
621 /* If there is audio playing, return headers and get next timeout */
622 if (wwo
->state
== WINE_WS_PLAYING
)
623 dwSleepTime
= wodPlayer_NotifyCompletions(wwo
, FALSE
);
625 dwSleepTime
= INFINITE
;
629 /**************************************************************************
632 static DWORD
wodOpen(WORD wDevID
, LPDWORD lpdwUser
, LPWAVEOPENDESC lpDesc
, DWORD dwFlags
) {
634 WINE_WAVEINST
*wwo
= NULL
;
635 DWORD ret
= MMSYSERR_NOERROR
;
636 pa_stream_flags_t stream_flags
;
638 TRACE("(%u, %p, %08X);\n", wDevID
, lpDesc
, dwFlags
);
639 if (lpDesc
== NULL
) {
640 WARN("Invalid Parameter !\n");
641 return MMSYSERR_INVALPARAM
;
644 if (wDevID
>= PULSE_WodNumDevs
) {
645 WARN("Asked for device %d, but only %d known!\n", wDevID
, PULSE_WodNumDevs
);
646 return MMSYSERR_BADDEVICEID
;
648 wdo
= &WOutDev
[wDevID
];
650 wwo
= HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, sizeof(WINE_WAVEINST
));
651 if (!wwo
) return MMSYSERR_NOMEM
;
652 *lpdwUser
= (DWORD
)wwo
;
654 /* check to see if format is supported and make pa_sample_spec struct */
655 if (!PULSE_SetupFormat(lpDesc
->lpFormat
, &wwo
->sample_spec
)) {
656 WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
657 lpDesc
->lpFormat
->wFormatTag
, lpDesc
->lpFormat
->nChannels
,
658 lpDesc
->lpFormat
->nSamplesPerSec
);
659 ret
= WAVERR_BADFORMAT
;
663 if (TRACE_ON(wave
)) {
664 char t
[PA_SAMPLE_SPEC_SNPRINT_MAX
];
665 pa_sample_spec_snprint(t
, sizeof(t
), &wwo
->sample_spec
);
666 TRACE("Sample spec '%s'\n", t
);
669 if (dwFlags
& WAVE_FORMAT_QUERY
) {
670 TRACE("Query format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
671 lpDesc
->lpFormat
->wFormatTag
, lpDesc
->lpFormat
->nChannels
,
672 lpDesc
->lpFormat
->nSamplesPerSec
);
673 ret
= MMSYSERR_NOERROR
;
677 wwo
->wFlags
= HIWORD(dwFlags
& CALLBACK_TYPEMASK
);
678 wwo
->waveDesc
= *lpDesc
;
679 PULSE_InitRingMessage(&wwo
->msgRing
);
681 wwo
->stream
= pa_stream_new(PULSE_context
, "WaveOut", &wwo
->sample_spec
, NULL
);
682 /* If server doesn't support sample_spec, we will error out here */
684 ret
= WAVERR_BADFORMAT
;
688 pa_stream_set_write_callback (wwo
->stream
, WAVEOUT_StreamRequestCallback
, wwo
);
689 pa_stream_set_state_callback (wwo
->stream
, PULSE_StreamStateCallback
, wwo
);
690 pa_stream_set_underflow_callback (wwo
->stream
, PULSE_StreamUnderflowCallback
, wwo
);
691 pa_stream_set_overflow_callback (wwo
->stream
, PULSE_StreamOverflowCallback
, wwo
);
692 pa_stream_set_moved_callback (wwo
->stream
, PULSE_StreamMovedCallback
, wwo
);
693 pa_stream_set_suspended_callback (wwo
->stream
, PULSE_StreamSuspendedCallback
, wwo
);
695 stream_flags
= PA_STREAM_AUTO_TIMING_UPDATE
;
697 #if PA_API_VERSION >= 12
698 if (pa_context_get_server_protocol_version(PULSE_context
) >= 15) {
699 pa_stream_set_started_callback(wwo
->stream
, WAVEOUT_StreamStartedCallback
, wwo
);
700 stream_flags
|= PA_STREAM_ADJUST_LATENCY
;
704 pa_stream_set_latency_update_callback(wwo
->stream
, WAVEOUT_StreamTimingInfoUpdateCallback
, wwo
);
707 TRACE("Connecting stream for playback on %s.\n", wdo
->device_name
);
708 pa_threaded_mainloop_lock(PULSE_ml
);
709 pa_stream_connect_playback(wwo
->stream
, wdo
->device_name
, NULL
, stream_flags
, NULL
, NULL
);
712 pa_context_state_t cstate
= pa_context_get_state(PULSE_context
);
713 pa_stream_state_t sstate
= pa_stream_get_state(wwo
->stream
);
715 if (cstate
== PA_CONTEXT_FAILED
|| cstate
== PA_CONTEXT_TERMINATED
||
716 sstate
== PA_STREAM_FAILED
|| sstate
== PA_STREAM_TERMINATED
) {
717 ERR("Failed to connect stream context object: %s\n", pa_strerror(pa_context_errno(PULSE_context
)));
718 pa_threaded_mainloop_unlock(PULSE_ml
);
719 ret
= MMSYSERR_NODRIVER
;
723 if (sstate
== PA_STREAM_READY
)
726 pa_threaded_mainloop_wait(PULSE_ml
);
728 TRACE("(%p)->stream connected for playback.\n", wwo
);
730 PULSE_WaitForOperation(pa_stream_update_timing_info(wwo
->stream
, PULSE_StreamSuccessCallback
, wwo
));
732 wwo
->timing_info
= pa_stream_get_timing_info(wwo
->stream
);
733 assert(wwo
->timing_info
);
734 pa_threaded_mainloop_unlock(PULSE_ml
);
736 wwo
->hStartUpEvent
= CreateEventW(NULL
, FALSE
, FALSE
, NULL
);
737 wwo
->hThread
= CreateThread(NULL
, 0, wodPlayer
, (LPVOID
)wwo
, 0, &(wwo
->dwThreadID
));
739 SetThreadPriority(wwo
->hThread
, THREAD_PRIORITY_TIME_CRITICAL
);
741 ERR("Thread creation for the wodPlayer failed!\n");
742 ret
= MMSYSERR_NOMEM
;
745 WaitForSingleObject(wwo
->hStartUpEvent
, INFINITE
);
746 CloseHandle(wwo
->hStartUpEvent
);
747 wwo
->hStartUpEvent
= INVALID_HANDLE_VALUE
;
750 return wodPlayer_NotifyClient (wwo
, WOM_OPEN
, 0L, 0L);
756 if (wwo
->hStartUpEvent
!= INVALID_HANDLE_VALUE
)
757 CloseHandle(wwo
->hStartUpEvent
);
759 if (wwo
->msgRing
.ring_buffer_size
> 0)
760 PULSE_DestroyRingMessage(&wwo
->msgRing
);
763 if (pa_stream_get_state(wwo
->stream
) == PA_STREAM_READY
)
764 pa_stream_disconnect(wwo
->stream
);
765 pa_stream_unref(wwo
->stream
);
768 HeapFree(GetProcessHeap(), 0, wwo
);
773 /**************************************************************************
774 * wodClose [internal]
776 static DWORD
wodClose(WINE_WAVEINST
*wwo
) {
779 TRACE("(%p);\n", wwo
);
781 WARN("Stream instance invalid.\n");
782 return MMSYSERR_INVALHANDLE
;
785 if (wwo
->state
!= WINE_WS_FAILED
) {
786 if (wwo
->lpQueuePtr
&& wwo
->lpPlayPtr
) {
787 WARN("buffers still playing !\n");
788 return WAVERR_STILLPLAYING
;
791 pa_threaded_mainloop_lock(PULSE_ml
);
792 PULSE_WaitForOperation(pa_stream_drain(wwo
->stream
, PULSE_StreamSuccessCallback
, NULL
));
793 pa_stream_disconnect(wwo
->stream
);
794 pa_threaded_mainloop_unlock(PULSE_ml
);
796 if (wwo
->hThread
!= INVALID_HANDLE_VALUE
)
797 PULSE_AddRingMessage(&wwo
->msgRing
, WINE_WM_CLOSING
, 0, TRUE
);
799 PULSE_DestroyRingMessage(&wwo
->msgRing
);
803 pa_stream_unref(wwo
->stream
);
804 ret
= wodPlayer_NotifyClient(wwo
, WOM_CLOSE
, 0L, 0L);
806 HeapFree(GetProcessHeap(), 0, wwo
);
811 /**************************************************************************
812 * wodWrite [internal]
814 static DWORD
wodWrite(WINE_WAVEINST
*wwo
, LPWAVEHDR lpWaveHdr
, DWORD dwSize
) {
815 if (!wwo
|| wwo
->state
== WINE_WS_FAILED
) {
816 WARN("Stream instance invalid.\n");
817 return MMSYSERR_INVALHANDLE
;
820 if (lpWaveHdr
->lpData
== NULL
|| !(lpWaveHdr
->dwFlags
& WHDR_PREPARED
))
821 return WAVERR_UNPREPARED
;
823 if (lpWaveHdr
->dwFlags
& WHDR_INQUEUE
)
824 return WAVERR_STILLPLAYING
;
826 lpWaveHdr
->dwFlags
&= ~WHDR_DONE
;
827 lpWaveHdr
->dwFlags
|= WHDR_INQUEUE
;
828 lpWaveHdr
->lpNext
= 0;
829 lpWaveHdr
->reserved
= 0;
831 PULSE_AddRingMessage(&wwo
->msgRing
, WINE_WM_HEADER
, (DWORD
)lpWaveHdr
, FALSE
);
832 return MMSYSERR_NOERROR
;
835 /**************************************************************************
836 * wodPause [internal]
838 static DWORD
wodPause(WINE_WAVEINST
*wwo
) {
839 if (!wwo
|| wwo
->state
== WINE_WS_FAILED
) {
840 WARN("Stream instance invalid.\n");
841 return MMSYSERR_INVALHANDLE
;
844 PULSE_AddRingMessage(&wwo
->msgRing
, WINE_WM_PAUSING
, 0, TRUE
);
845 return MMSYSERR_NOERROR
;
848 /**************************************************************************
849 * wodGetPosition [internal]
851 static DWORD
wodGetPosition(WINE_WAVEINST
*wwo
, LPMMTIME lpTime
, DWORD uSize
) {
852 pa_usec_t time
, time_temp
;
853 size_t bytes
, bytes_temp
;
855 if (!wwo
|| wwo
->state
== WINE_WS_FAILED
) {
856 WARN("Stream instance invalid.\n");
857 return MMSYSERR_INVALHANDLE
;
860 if (lpTime
== NULL
) return MMSYSERR_INVALPARAM
;
862 pa_threaded_mainloop_lock(PULSE_ml
);
863 if (wwo
->timing_info
->read_index_corrupt
|| wwo
->timing_info
->write_index_corrupt
)
864 PULSE_WaitForOperation(pa_stream_update_timing_info(wwo
->stream
, PULSE_StreamSuccessCallback
, wwo
));
866 bytes
= wwo
->timing_info
->read_index
;
867 time
= pa_bytes_to_usec(bytes
, &wwo
->sample_spec
);
869 if (wwo
->timing_info
->playing
) {
870 bytes
+= ((pa_timeval_age(&wwo
->timing_info
->timestamp
) / 1000) * pa_bytes_per_second(&wwo
->sample_spec
)) / 1000;
871 bytes_temp
= (time_temp
= wwo
->timing_info
->sink_usec
+ wwo
->timing_info
->transport_usec
)/1000 * pa_bytes_per_second(&wwo
->sample_spec
)/1000;
872 time
+= pa_timeval_age(&wwo
->timing_info
->timestamp
);
879 pa_threaded_mainloop_unlock(PULSE_ml
);
881 if (wwo
->last_reset
< bytes
) {
882 time
-= pa_bytes_to_usec(wwo
->last_reset
, &wwo
->sample_spec
);
883 bytes
-= wwo
->last_reset
;
885 if (bytes
> bytes_temp
)
890 if (time
> time_temp
)
895 bytes
-= bytes
% pa_frame_size(&wwo
->sample_spec
);
896 time
/= 1000; /* In milliseconds now */
898 switch (lpTime
->wType
) {
900 lpTime
->u
.sample
= bytes
/ pa_frame_size(&wwo
->sample_spec
);
901 TRACE("TIME_SAMPLES=%u\n", lpTime
->u
.sample
);
905 TRACE("TIME_MS=%u\n", lpTime
->u
.ms
);
908 lpTime
->u
.smpte
.fps
= 30;
909 lpTime
->u
.smpte
.sec
= time
/1000;
910 lpTime
->u
.smpte
.min
= lpTime
->u
.smpte
.sec
/ 60;
911 lpTime
->u
.smpte
.sec
-= 60 * lpTime
->u
.smpte
.min
;
912 lpTime
->u
.smpte
.hour
= lpTime
->u
.smpte
.min
/ 60;
913 lpTime
->u
.smpte
.min
-= 60 * lpTime
->u
.smpte
.hour
;
914 lpTime
->u
.smpte
.frame
= time
/ lpTime
->u
.smpte
.fps
* 1000;
915 TRACE("TIME_SMPTE=%02u:%02u:%02u:%02u\n",
916 lpTime
->u
.smpte
.hour
, lpTime
->u
.smpte
.min
,
917 lpTime
->u
.smpte
.sec
, lpTime
->u
.smpte
.frame
);
920 WARN("Format %d not supported, using TIME_BYTES !\n", lpTime
->wType
);
921 lpTime
->wType
= TIME_BYTES
;
924 lpTime
->u
.cb
= bytes
;
925 TRACE("TIME_BYTES=%u\n", lpTime
->u
.cb
);
928 return MMSYSERR_NOERROR
;
930 /**************************************************************************
931 * wodBreakLoop [internal]
933 static DWORD
wodBreakLoop(WINE_WAVEINST
*wwo
) {
934 if (!wwo
|| wwo
->state
== WINE_WS_FAILED
) {
935 WARN("Stream instance invalid.\n");
936 return MMSYSERR_INVALHANDLE
;
939 PULSE_AddRingMessage(&wwo
->msgRing
, WINE_WM_BREAKLOOP
, 0, TRUE
);
940 return MMSYSERR_NOERROR
;
943 /**************************************************************************
944 * wodGetDevCaps [internal]
946 static DWORD
wodGetDevCaps(DWORD wDevID
, LPWAVEOUTCAPSW lpCaps
, DWORD dwSize
) {
947 TRACE("(%u, %p, %u);\n", wDevID
, lpCaps
, dwSize
);
949 if (lpCaps
== NULL
) return MMSYSERR_NOTENABLED
;
951 if (wDevID
>= PULSE_WodNumDevs
) {
952 TRACE("Asked for device %d, but only %d known!\n", wDevID
, PULSE_WodNumDevs
);
953 return MMSYSERR_INVALHANDLE
;
956 memcpy(lpCaps
, &(WOutDev
[wDevID
].caps
.out
), min(dwSize
, sizeof(*lpCaps
)));
957 return MMSYSERR_NOERROR
;
960 /**************************************************************************
961 * wodGetNumDevs [internal]
962 * Context-sanity check here, as if we respond with 0, WINE will move on
963 * to the next waveout driver.
965 static DWORD
wodGetNumDevs() {
966 if (!PULSE_ml
|| !PULSE_context
|| pa_context_get_state(PULSE_context
) != PA_CONTEXT_READY
)
969 return PULSE_WodNumDevs
;
972 /**************************************************************************
973 * wodGetVolume [internal]
975 static DWORD
wodGetVolume(WINE_WAVEINST
*wwo
, LPDWORD lpdwVol
) {
976 float value1
, value2
;
979 if (!wwo
|| wwo
->state
== WINE_WS_FAILED
) {
980 WARN("Stream instance invalid.\n");
981 return MMSYSERR_INVALHANDLE
;
984 TRACE("(%p, %p);\n", wwo
, lpdwVol
);
987 return MMSYSERR_NOTENABLED
;
989 pa_threaded_mainloop_lock(PULSE_ml
);
990 if (wwo
->stream
&& PULSE_context
&& pa_context_get_state(PULSE_context
) == PA_CONTEXT_READY
&&
991 pa_stream_get_state(wwo
->stream
) == PA_STREAM_READY
) {
992 PULSE_WaitForOperation(pa_context_get_sink_input_info(PULSE_context
, pa_stream_get_index(wwo
->stream
), WAVEOUT_SinkInputInfoCallback
, wwo
));
994 pa_threaded_mainloop_unlock(PULSE_ml
);
997 if (wwo
->volume
.channels
== 2) {
998 value1
= pa_sw_volume_to_dB(wwo
->volume
.values
[0]);
999 value2
= pa_sw_volume_to_dB(wwo
->volume
.values
[1]);
1001 value1
= pa_sw_volume_to_dB(pa_cvolume_avg(&wwo
->volume
));
1012 wright
= 0xFFFFl
- ((value2
/ -60)*(float)0xFFFFl
);
1014 if (wleft
> 0xFFFFl
)
1016 if (wright
> 0xFFFFl
)
1019 *lpdwVol
= (WORD
)wleft
+ (WORD
)(wright
<< 16);
1021 return MMSYSERR_NOERROR
;
1024 /**************************************************************************
1025 * wodSetVolume [internal]
1027 static DWORD
wodSetVolume(WINE_WAVEINST
*wwo
, DWORD dwParam1
) {
1028 double value1
, value2
;
1030 TRACE("(%p, %08X);\n", wwo
, dwParam1
);
1031 if (!wwo
|| wwo
->state
== WINE_WS_FAILED
) {
1032 WARN("Stream instance invalid.\n");
1033 return MMSYSERR_INVALHANDLE
;
1036 /* waveOut volumes are /supposed/ to be logarithmic */
1037 value1
= LOWORD(dwParam1
) == 0 ? PA_DECIBEL_MININFTY
: ((float)(0xFFFFl
- LOWORD(dwParam1
))/0xFFFFl
) * -60.0;
1038 value2
= HIWORD(dwParam1
) == 0 ? PA_DECIBEL_MININFTY
: ((float)(0xFFFFl
- HIWORD(dwParam1
))/0xFFFFl
) * -60.0;
1040 if (wwo
->sample_spec
.channels
== 2) {
1041 wwo
->volume
.channels
= 2;
1042 wwo
->volume
.values
[0] = pa_sw_volume_from_dB(value1
);
1043 wwo
->volume
.values
[1] = pa_sw_volume_from_dB(value2
);
1045 if (value1
!= value2
) FIXME("Non-stereo streams can't pan!\n");
1046 wwo
->volume
.channels
= wwo
->sample_spec
.channels
;
1047 pa_cvolume_set(&wwo
->volume
, wwo
->volume
.channels
, pa_sw_volume_from_dB(max(value1
, value2
)));
1050 if (TRACE_ON(wave
)) {
1051 char s
[PA_CVOLUME_SNPRINT_MAX
];
1052 pa_cvolume_snprint(s
, PA_CVOLUME_SNPRINT_MAX
, &wwo
->volume
);
1056 pa_threaded_mainloop_lock(PULSE_ml
);
1057 if (!wwo
->stream
|| !PULSE_context
|| pa_context_get_state(PULSE_context
) != PA_CONTEXT_READY
||
1058 pa_stream_get_state(wwo
->stream
) != PA_STREAM_READY
|| !pa_cvolume_valid(&wwo
->volume
)) {
1059 pa_threaded_mainloop_unlock(PULSE_ml
);
1060 return MMSYSERR_NOERROR
;
1063 PULSE_WaitForOperation(pa_context_set_sink_input_volume(PULSE_context
,
1064 pa_stream_get_index(wwo
->stream
), &wwo
->volume
,
1065 PULSE_ContextSuccessCallback
, wwo
));
1066 pa_threaded_mainloop_unlock(PULSE_ml
);
1067 return MMSYSERR_NOERROR
;
1070 /**************************************************************************
1071 * wodRestart [internal]
1073 static DWORD
wodRestart(WINE_WAVEINST
*wwo
) {
1074 if (!wwo
|| wwo
->state
== WINE_WS_FAILED
) {
1075 WARN("Stream instance invalid.\n");
1076 return MMSYSERR_INVALHANDLE
;
1079 if (wwo
->state
== WINE_WS_PAUSED
)
1080 PULSE_AddRingMessage(&wwo
->msgRing
, WINE_WM_RESTARTING
, 0, TRUE
);
1081 return MMSYSERR_NOERROR
;
1084 /**************************************************************************
1085 * wodReset [internal]
1087 static DWORD
wodReset(WINE_WAVEINST
*wwo
) {
1088 if (!wwo
|| wwo
->state
== WINE_WS_FAILED
) {
1089 WARN("Stream instance invalid.\n");
1090 return MMSYSERR_INVALHANDLE
;
1093 PULSE_AddRingMessage(&wwo
->msgRing
, WINE_WM_RESETTING
, 0, TRUE
);
1094 return MMSYSERR_NOERROR
;
1097 /**************************************************************************
1098 * wodDevInterfaceSize [internal]
1100 static DWORD
wodDevInterfaceSize(UINT wDevID
, LPDWORD dwParam1
) {
1102 *dwParam1
= MultiByteToWideChar(CP_ACP
, 0, WOutDev
[wDevID
].interface_name
, -1, NULL
, 0) * sizeof(WCHAR
);
1103 return MMSYSERR_NOERROR
;
1106 /**************************************************************************
1107 * wodDevInterface [internal]
1109 static DWORD
wodDevInterface(UINT wDevID
, PWCHAR dwParam1
, DWORD dwParam2
) {
1110 if (dwParam2
>= MultiByteToWideChar(CP_ACP
, 0, WOutDev
[wDevID
].interface_name
, -1,
1111 NULL
, 0 ) * sizeof(WCHAR
))
1113 MultiByteToWideChar(CP_ACP
, 0, WOutDev
[wDevID
].interface_name
, -1,
1114 dwParam1
, dwParam2
/ sizeof(WCHAR
));
1115 return MMSYSERR_NOERROR
;
1117 return MMSYSERR_INVALPARAM
;
1120 /**************************************************************************
1121 * wodMessage (WINEPULSE.@)
1123 DWORD WINAPI
PULSE_wodMessage(UINT wDevID
, UINT wMsg
, DWORD dwUser
,
1124 DWORD dwParam1
, DWORD dwParam2
) {
1126 TRACE("(%u, %s, %08X, %08X, %08X);\n",
1127 wDevID, PULSE_getMessage(wMsg), dwUser, dwParam1, dwParam2);
1134 /* FIXME: Pretend this is supported */
1136 case WODM_OPEN
: return wodOpen (wDevID
, (LPDWORD
)dwUser
, (LPWAVEOPENDESC
)dwParam1
, dwParam2
);
1137 case WODM_CLOSE
: return wodClose ((WINE_WAVEINST
*)dwUser
);
1138 case WODM_WRITE
: return wodWrite ((WINE_WAVEINST
*)dwUser
, (LPWAVEHDR
)dwParam1
, dwParam2
);
1139 case WODM_PAUSE
: return wodPause ((WINE_WAVEINST
*)dwUser
);
1140 case WODM_GETPOS
: return wodGetPosition ((WINE_WAVEINST
*)dwUser
, (LPMMTIME
)dwParam1
, dwParam2
);
1141 case WODM_BREAKLOOP
: return wodBreakLoop ((WINE_WAVEINST
*)dwUser
);
1142 case WODM_PREPARE
: return MMSYSERR_NOTSUPPORTED
;
1143 case WODM_UNPREPARE
: return MMSYSERR_NOTSUPPORTED
;
1144 case WODM_GETDEVCAPS
: return wodGetDevCaps (wDevID
, (LPWAVEOUTCAPSW
)dwParam1
, dwParam2
);
1145 case WODM_GETNUMDEVS
: return wodGetNumDevs ();
1146 case WODM_GETPITCH
: return MMSYSERR_NOTSUPPORTED
;
1147 case WODM_SETPITCH
: return MMSYSERR_NOTSUPPORTED
;
1148 case WODM_GETPLAYBACKRATE
: return MMSYSERR_NOTSUPPORTED
; /* support if theoretically possible */
1149 case WODM_SETPLAYBACKRATE
: return MMSYSERR_NOTSUPPORTED
; /* since pulseaudio 0.9.8 */
1150 case WODM_GETVOLUME
: return wodGetVolume ((WINE_WAVEINST
*)dwUser
, (LPDWORD
)dwParam1
);
1151 case WODM_SETVOLUME
: return wodSetVolume ((WINE_WAVEINST
*)dwUser
, dwParam1
);
1152 case WODM_RESTART
: return wodRestart ((WINE_WAVEINST
*)dwUser
);
1153 case WODM_RESET
: return wodReset ((WINE_WAVEINST
*)dwUser
);
1154 case DRV_QUERYDEVICEINTERFACESIZE
: return wodDevInterfaceSize (wDevID
, (LPDWORD
)dwParam1
);
1155 case DRV_QUERYDEVICEINTERFACE
: return wodDevInterface (wDevID
, (PWCHAR
)dwParam1
, dwParam2
);
1156 case DRV_QUERYDSOUNDIFACE
: return wodDsCreate (wDevID
, (PIDSDRIVER
*)dwParam1
);
1157 case DRV_QUERYDSOUNDDESC
: return wodDsDesc (wDevID
, (PDSDRIVERDESC
)dwParam1
);
1159 FIXME("unknown message %d!\n", wMsg
);
1161 return MMSYSERR_NOTSUPPORTED
;
1164 #else /* !HAVE_PULSEAUDIO */
1166 /**************************************************************************
1167 * wodMessage (WINEPULSE.@)
1169 DWORD WINAPI
PULSE_wodMessage(WORD wDevID
, WORD wMsg
, DWORD dwUser
,
1170 DWORD dwParam1
, DWORD dwParam2
) {
1171 FIXME("(%u, %04X, %08X, %08X, %08X):stub\n", wDevID
, wMsg
, dwUser
,
1172 dwParam1
, dwParam2
);
1173 return MMSYSERR_NOTENABLED
;
1176 #endif /* HAVE_PULSEAUDIO */