2 * Wine Driver for PulseAudio - WaveIn 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
35 #include <winepulse.h>
37 #include "wine/debug.h"
39 WINE_DEFAULT_DEBUG_CHANNEL(wave
);
43 /*======================================================================*
44 * WAVE IN specific PulseAudio Callbacks *
45 *======================================================================*/
47 /**************************************************************************
48 * widNotifyClient [internal]
50 static DWORD
widNotifyClient(WINE_WAVEINST
* wwi
, WORD wMsg
, DWORD dwParam1
, DWORD dwParam2
) {
51 TRACE("wMsg = 0x%04x dwParm1 = %04X dwParam2 = %04X\n", wMsg
, dwParam1
, dwParam2
);
57 if (wwi
->wFlags
!= DCB_NULL
&&
58 !DriverCallback(wwi
->waveDesc
.dwCallback
, wwi
->wFlags
, (HDRVR
)wwi
->waveDesc
.hWave
,
59 wMsg
, wwi
->waveDesc
.dwInstance
, dwParam1
, dwParam2
)) {
60 WARN("can't notify client !\n");
61 return MMSYSERR_ERROR
;
65 FIXME("Unknown callback message %u\n", wMsg
);
66 return MMSYSERR_INVALPARAM
;
68 return MMSYSERR_NOERROR
;
71 /**************************************************************************
72 * widRecorder_NextFragment [internal]
74 * Gets the next fragment of data from the server.
76 static size_t widRecorder_NextFragment(WINE_WAVEINST
*wwi
) {
82 pa_stream_drop(wwi
->stream
);
84 pa_stream_peek(wwi
->stream
, &wwi
->buffer
, &nbytes
);
85 wwi
->buffer_length
= nbytes
;
86 wwi
->buffer_read_offset
= 0;
92 /**************************************************************************
93 * widRecorder_CopyData [internal]
95 * Copys data from the fragments pulse returns to queued buffers.
97 static void widRecorder_CopyData(WINE_WAVEINST
*wwi
) {
98 LPWAVEHDR lpWaveHdr
= wwi
->lpQueuePtr
;
101 while (lpWaveHdr
&& wwi
->state
== WINE_WS_PLAYING
) {
103 nbytes
= min(wwi
->buffer_length
- wwi
->buffer_read_offset
, lpWaveHdr
->dwBufferLength
- lpWaveHdr
->dwBytesRecorded
);
104 if (nbytes
== 0) break;
106 TRACE("%u bytes from %p to %p\n",
108 (PBYTE
)wwi
->buffer
+ wwi
->buffer_read_offset
,
109 lpWaveHdr
->lpData
+ lpWaveHdr
->dwBytesRecorded
);
111 memcpy(lpWaveHdr
->lpData
+ lpWaveHdr
->dwBytesRecorded
, (PBYTE
)wwi
->buffer
+ wwi
->buffer_read_offset
, nbytes
);
113 lpWaveHdr
->dwBytesRecorded
+= nbytes
;
114 wwi
->buffer_read_offset
+= nbytes
;
116 if (wwi
->buffer_read_offset
== wwi
->buffer_length
) {
117 pa_threaded_mainloop_lock(PULSE_ml
);
118 pa_stream_drop(wwi
->stream
);
119 if (pa_stream_readable_size(wwi
->stream
))
120 widRecorder_NextFragment(wwi
);
123 wwi
->buffer_length
= 0;
124 wwi
->buffer_read_offset
= 0;
126 pa_threaded_mainloop_unlock(PULSE_ml
);
129 if (lpWaveHdr
->dwBytesRecorded
== lpWaveHdr
->dwBufferLength
) {
130 lpWaveHdr
->dwFlags
&= ~WHDR_INQUEUE
;
131 lpWaveHdr
->dwFlags
|= WHDR_DONE
;
132 wwi
->lpQueuePtr
= lpWaveHdr
->lpNext
;
133 widNotifyClient(wwi
, WIM_DATA
, (DWORD
)lpWaveHdr
, 0);
134 lpWaveHdr
= wwi
->lpQueuePtr
;
139 /**************************************************************************
140 * widRecorder [internal]
142 static DWORD CALLBACK
widRecorder(LPVOID lpParam
) {
143 WINE_WAVEINST
*wwi
= (WINE_WAVEINST
*)lpParam
;
145 enum win_wm_message msg
;
148 DWORD wait
= INFINITE
;
150 wwi
->state
= WINE_WS_STOPPED
;
151 SetEvent(wwi
->hStartUpEvent
);
155 if (wwi
->state
!= WINE_WS_PLAYING
) {
158 if (wwi
->buffer
== NULL
&& pa_stream_readable_size(wwi
->stream
)) {
159 pa_threaded_mainloop_lock(PULSE_ml
);
160 wait
= pa_bytes_to_usec(widRecorder_NextFragment(wwi
), &wwi
->sample_spec
)/1000;
161 pa_threaded_mainloop_unlock(PULSE_ml
);
165 widRecorder_CopyData(wwi
);
167 PULSE_WaitRingMessage(&wwi
->msgRing
, wait
);
169 while (PULSE_RetrieveRingMessage(&wwi
->msgRing
, &msg
, ¶m
, &ev
)) {
170 TRACE("Received %s %x\n", PULSE_getCmdString(msg
), param
);
176 case WINE_WM_STARTING
:
177 wwi
->state
= WINE_WS_PLAYING
;
179 wait
= pa_bytes_to_usec(wwi
->lpQueuePtr
->dwBufferLength
, &wwi
->sample_spec
)/1000;
182 wwi
->dwLastReset
= wwi
->timing_info
->read_index
;
183 pa_threaded_mainloop_lock(PULSE_ml
);
184 PULSE_WaitForOperation(pa_stream_cork(wwi
->stream
, 0, PULSE_StreamSuccessCallback
, NULL
));
185 pa_threaded_mainloop_unlock(PULSE_ml
);
189 lpWaveHdr
= (LPWAVEHDR
)param
;
190 lpWaveHdr
->lpNext
= 0;
192 /* insert buffer at the end of queue */
195 for (wh
= &(wwi
->lpQueuePtr
); *wh
; wh
= &((*wh
)->lpNext
));
199 case WINE_WM_STOPPING
:
200 if (wwi
->state
!= WINE_WS_STOPPED
) {
201 wwi
->state
= WINE_WS_STOPPED
;
202 pa_threaded_mainloop_lock(PULSE_ml
);
203 PULSE_WaitForOperation(pa_stream_cork(wwi
->stream
, 1, PULSE_StreamSuccessCallback
, NULL
));
204 pa_threaded_mainloop_unlock(PULSE_ml
);
206 /* return current buffer to app */
207 lpWaveHdr
= wwi
->lpQueuePtr
;
209 LPWAVEHDR lpNext
= lpWaveHdr
->lpNext
;
210 TRACE("stop %p %p\n", lpWaveHdr
, lpWaveHdr
->lpNext
);
211 lpWaveHdr
->dwFlags
&= ~WHDR_INQUEUE
;
212 lpWaveHdr
->dwFlags
|= WHDR_DONE
;
213 wwi
->lpQueuePtr
= lpNext
;
214 widNotifyClient(wwi
, WIM_DATA
, (DWORD
)lpWaveHdr
, 0);
219 case WINE_WM_RESETTING
:
220 if (wwi
->state
!= WINE_WS_STOPPED
) {
221 wwi
->state
= WINE_WS_STOPPED
;
222 pa_threaded_mainloop_lock(PULSE_ml
);
223 PULSE_WaitForOperation(pa_stream_cork(wwi
->stream
, 1, PULSE_StreamSuccessCallback
, NULL
));
224 pa_threaded_mainloop_unlock(PULSE_ml
);
227 /* return all buffers to the app */
228 for (lpWaveHdr
= wwi
->lpPlayPtr
? wwi
->lpPlayPtr
: wwi
->lpQueuePtr
; lpWaveHdr
; lpWaveHdr
= wwi
->lpQueuePtr
) {
229 lpWaveHdr
->dwFlags
&= ~WHDR_INQUEUE
;
230 lpWaveHdr
->dwFlags
|= WHDR_DONE
;
231 wwi
->lpQueuePtr
= lpWaveHdr
->lpNext
;
232 widNotifyClient(wwi
, WIM_DATA
, (DWORD
)lpWaveHdr
, 0);
237 case WINE_WM_CLOSING
:
239 if ((DWORD
)param
== 1) {
240 /* If we are here, the stream failed */
241 wwi
->state
= WINE_WS_FAILED
;
243 PULSE_DestroyRingMessage(&wwi
->msgRing
);
244 widNotifyClient(wwi
, WIM_CLOSE
, 0L, 0L);
245 wwi
->lpPlayPtr
= wwi
->lpQueuePtr
= NULL
;
246 pa_threaded_mainloop_lock(PULSE_ml
);
247 pa_stream_disconnect(wwi
->stream
);
248 pa_threaded_mainloop_unlock(PULSE_ml
);
249 TRACE("Thread exiting because of failure.\n");
252 wwi
->state
= WINE_WS_CLOSED
;
255 /* shouldn't go here */
257 FIXME("unknown message %d\n", msg
);
260 } /* while(PULSE_RetrieveRingMessage()) */
264 /**************************************************************************
267 static DWORD
widOpen(WORD wDevID
, DWORD_PTR
*lpdwUser
, LPWAVEOPENDESC lpDesc
, DWORD dwFlags
) {
269 WINE_WAVEINST
*wwi
= NULL
;
270 DWORD ret
= MMSYSERR_NOERROR
;
272 TRACE("(%u, %p, %08X);\n", wDevID
, lpDesc
, dwFlags
);
273 if (lpDesc
== NULL
) {
274 WARN("Invalid Parameter !\n");
275 return MMSYSERR_INVALPARAM
;
278 if (wDevID
>= PULSE_WidNumDevs
) {
279 TRACE("Asked for device %d, but only %d known!\n", wDevID
, PULSE_WidNumDevs
);
280 return MMSYSERR_BADDEVICEID
;
282 wdi
= &WInDev
[wDevID
];
284 wwi
= HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, sizeof(WINE_WAVEINST
));
285 if (!wwi
) return MMSYSERR_NOMEM
;
286 *lpdwUser
= (DWORD_PTR
)wwi
;
288 /* check to see if format is supported and make pa_sample_spec struct */
289 if (!PULSE_SetupFormat(lpDesc
->lpFormat
, &wwi
->sample_spec
)) {
290 WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
291 lpDesc
->lpFormat
->wFormatTag
, lpDesc
->lpFormat
->nChannels
,
292 lpDesc
->lpFormat
->nSamplesPerSec
);
293 ret
= WAVERR_BADFORMAT
;
297 if (TRACE_ON(wave
)) {
298 char t
[PA_SAMPLE_SPEC_SNPRINT_MAX
];
299 pa_sample_spec_snprint(t
, sizeof(t
), &wwi
->sample_spec
);
300 TRACE("Sample spec '%s'\n", t
);
303 if (dwFlags
& WAVE_FORMAT_QUERY
) {
304 TRACE("Query format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
305 lpDesc
->lpFormat
->wFormatTag
, lpDesc
->lpFormat
->nChannels
,
306 lpDesc
->lpFormat
->nSamplesPerSec
);
307 ret
= MMSYSERR_NOERROR
;
311 wwi
->wFlags
= HIWORD(dwFlags
& CALLBACK_TYPEMASK
);
312 wwi
->waveDesc
= *lpDesc
;
313 PULSE_InitRingMessage(&wwi
->msgRing
);
315 wwi
->stream
= pa_stream_new(PULSE_context
, "WaveIn", &wwi
->sample_spec
, NULL
);
317 ret
= WAVERR_BADFORMAT
;
321 pa_stream_set_state_callback(wwi
->stream
, PULSE_StreamStateCallback
, wwi
);
323 wwi
->buffer_attr
.maxlength
= (uint32_t)-1;
324 wwi
->buffer_attr
.fragsize
= pa_bytes_per_second(&wwi
->sample_spec
) / 100;
326 pa_threaded_mainloop_lock(PULSE_ml
);
327 TRACE("Asking to open %s for recording.\n", wdi
->device_name
);
328 pa_stream_connect_record(wwi
->stream
, wdi
->device_name
, &wwi
->buffer_attr
,
329 PA_STREAM_START_CORKED
|
330 PA_STREAM_AUTO_TIMING_UPDATE
);
333 pa_context_state_t cstate
= pa_context_get_state(PULSE_context
);
334 pa_stream_state_t sstate
= pa_stream_get_state(wwi
->stream
);
336 if (cstate
== PA_CONTEXT_FAILED
|| cstate
== PA_CONTEXT_TERMINATED
||
337 sstate
== PA_STREAM_FAILED
|| sstate
== PA_STREAM_TERMINATED
) {
338 ERR("Failed to connect context object: %s\n", pa_strerror(pa_context_errno(PULSE_context
)));
339 ret
= MMSYSERR_NODRIVER
;
340 pa_threaded_mainloop_unlock(PULSE_ml
);
344 if (sstate
== PA_STREAM_READY
)
347 pa_threaded_mainloop_wait(PULSE_ml
);
349 TRACE("(%p)->stream connected for recording.\n", wwi
);
351 PULSE_WaitForOperation(pa_stream_update_timing_info(wwi
->stream
, PULSE_StreamSuccessCallback
, wwi
));
353 wwi
->timing_info
= pa_stream_get_timing_info(wwi
->stream
);
354 assert(wwi
->timing_info
);
355 pa_threaded_mainloop_unlock(PULSE_ml
);
357 wwi
->hStartUpEvent
= CreateEventW(NULL
, FALSE
, FALSE
, NULL
);
358 wwi
->hThread
= CreateThread(NULL
, 0, widRecorder
, (LPVOID
)wwi
, 0, &(wwi
->dwThreadID
));
360 SetThreadPriority(wwi
->hThread
, THREAD_PRIORITY_TIME_CRITICAL
);
362 ERR("Thread creation for the widRecorder failed!\n");
363 ret
= MMSYSERR_NOMEM
;
366 WaitForSingleObject(wwi
->hStartUpEvent
, INFINITE
);
367 CloseHandle(wwi
->hStartUpEvent
);
368 wwi
->hStartUpEvent
= INVALID_HANDLE_VALUE
;
370 return widNotifyClient(wwi
, WIM_OPEN
, 0L, 0L);
376 if (wwi
->hStartUpEvent
!= INVALID_HANDLE_VALUE
)
377 CloseHandle(wwi
->hStartUpEvent
);
379 if (wwi
->msgRing
.ring_buffer_size
> 0)
380 PULSE_DestroyRingMessage(&wwi
->msgRing
);
383 if (pa_stream_get_state(wwi
->stream
) == PA_STREAM_READY
)
384 pa_stream_disconnect(wwi
->stream
);
385 pa_stream_unref(wwi
->stream
);
387 HeapFree(GetProcessHeap(), 0, wwi
);
391 /**************************************************************************
392 * widClose [internal]
394 static DWORD
widClose(WORD wDevID
, WINE_WAVEINST
*wwi
) {
397 TRACE("(%u, %p);\n", wDevID
, wwi
);
398 if (wDevID
>= PULSE_WidNumDevs
) {
399 WARN("Asked for device %d, but only %d known!\n", wDevID
, PULSE_WodNumDevs
);
400 return MMSYSERR_INVALHANDLE
;
402 WARN("Stream instance invalid.\n");
403 return MMSYSERR_INVALHANDLE
;
406 if (wwi
->state
!= WINE_WS_FAILED
) {
407 if (wwi
->lpQueuePtr
) {
408 WARN("buffers recording recording !\n");
409 return WAVERR_STILLPLAYING
;
412 pa_threaded_mainloop_lock(PULSE_ml
);
413 if (pa_stream_get_state(wwi
->stream
) == PA_STREAM_READY
)
414 pa_stream_drop(wwi
->stream
);
415 pa_stream_disconnect(wwi
->stream
);
416 pa_threaded_mainloop_unlock(PULSE_ml
);
418 if (wwi
->hThread
!= INVALID_HANDLE_VALUE
)
419 PULSE_AddRingMessage(&wwi
->msgRing
, WINE_WM_CLOSING
, 0, TRUE
);
421 PULSE_DestroyRingMessage(&wwi
->msgRing
);
423 ret
= widNotifyClient(wwi
, WIM_CLOSE
, 0L, 0L);
425 pa_stream_unref(wwi
->stream
);
426 TRACE("Deallocating record instance.\n");
427 HeapFree(GetProcessHeap(), 0, wwi
);
431 /**************************************************************************
432 * widAddBuffer [internal]
435 static DWORD
widAddBuffer(WINE_WAVEINST
* wwi
, LPWAVEHDR lpWaveHdr
, DWORD dwSize
) {
436 TRACE("(%p, %p, %08X);\n", wwi
, lpWaveHdr
, dwSize
);
438 if (!wwi
|| wwi
->state
== WINE_WS_FAILED
) {
439 WARN("Stream instance invalid.\n");
440 return MMSYSERR_INVALHANDLE
;
443 if (lpWaveHdr
->lpData
== NULL
|| !(lpWaveHdr
->dwFlags
& WHDR_PREPARED
))
444 return WAVERR_UNPREPARED
;
446 if (lpWaveHdr
->dwFlags
& WHDR_INQUEUE
)
447 return WAVERR_STILLPLAYING
;
449 lpWaveHdr
->dwFlags
&= ~WHDR_DONE
;
450 lpWaveHdr
->dwFlags
|= WHDR_INQUEUE
;
451 lpWaveHdr
->dwBytesRecorded
= 0;
452 lpWaveHdr
->lpNext
= 0;
454 PULSE_AddRingMessage(&wwi
->msgRing
, WINE_WM_HEADER
, (DWORD
)lpWaveHdr
, FALSE
);
456 return MMSYSERR_NOERROR
;
459 /**************************************************************************
460 * widRecorderMessage [internal]
462 static DWORD
widRecorderMessage(WINE_WAVEINST
*wwi
, enum win_wm_message message
) {
463 if (!wwi
|| wwi
->state
== WINE_WS_FAILED
) {
464 WARN("Stream instance invalid.\n");
465 return MMSYSERR_INVALHANDLE
;
468 PULSE_AddRingMessage(&wwi
->msgRing
, message
, 0, TRUE
);
469 return MMSYSERR_NOERROR
;
472 /**************************************************************************
473 * widGetPosition [internal]
475 static DWORD
widGetPosition(WINE_WAVEINST
*wwi
, LPMMTIME lpTime
, DWORD uSize
) {
477 if (!wwi
|| wwi
->state
== WINE_WS_FAILED
) {
478 WARN("Stream instance invalid.\n");
479 return MMSYSERR_INVALHANDLE
;
482 if (lpTime
== NULL
) return MMSYSERR_INVALPARAM
;
484 return PULSE_UsecToMMTime(pa_bytes_to_usec(wwi
->timing_info
->read_index
- wwi
->dwLastReset
, &wwi
->sample_spec
), lpTime
, &wwi
->sample_spec
);
487 /**************************************************************************
488 * widGetDevCaps [internal]
490 static DWORD
widGetDevCaps(DWORD wDevID
, LPWAVEINCAPSW lpCaps
, DWORD dwSize
) {
491 TRACE("(%u, %p, %u);\n", wDevID
, lpCaps
, dwSize
);
493 if (lpCaps
== NULL
) return MMSYSERR_NOTENABLED
;
495 if (wDevID
>= PULSE_WidNumDevs
) {
496 TRACE("Asked for device %d, but only %d known!\n", wDevID
, PULSE_WidNumDevs
);
497 return MMSYSERR_INVALHANDLE
;
500 memcpy(lpCaps
, &(WInDev
[wDevID
].caps
.in
), min(dwSize
, sizeof(*lpCaps
)));
501 return MMSYSERR_NOERROR
;
504 /**************************************************************************
505 * widGetNumDevs [internal]
506 * Context-sanity check here, as if we respond with 0, WINE will move on
507 * to the next wavein driver.
509 static DWORD
widGetNumDevs() {
510 if (pa_context_get_state(PULSE_context
) != PA_CONTEXT_READY
)
513 return PULSE_WidNumDevs
;
516 /**************************************************************************
517 * widDevInterfaceSize [internal]
519 static DWORD
widDevInterfaceSize(UINT wDevID
, LPDWORD dwParam1
) {
520 TRACE("(%u, %p)\n", wDevID
, dwParam1
);
522 *dwParam1
= MultiByteToWideChar(CP_UNIXCP
, 0, WInDev
[wDevID
].interface_name
, -1,
523 NULL
, 0 ) * sizeof(WCHAR
);
524 return MMSYSERR_NOERROR
;
527 /**************************************************************************
528 * widDevInterface [internal]
530 static DWORD
widDevInterface(UINT wDevID
, PWCHAR dwParam1
, DWORD dwParam2
) {
531 if (dwParam2
>= MultiByteToWideChar(CP_UNIXCP
, 0, WInDev
[wDevID
].interface_name
, -1,
532 NULL
, 0 ) * sizeof(WCHAR
))
534 MultiByteToWideChar(CP_UNIXCP
, 0, WInDev
[wDevID
].interface_name
, -1,
535 dwParam1
, dwParam2
/ sizeof(WCHAR
));
536 return MMSYSERR_NOERROR
;
538 return MMSYSERR_INVALPARAM
;
541 /**************************************************************************
542 * widDsDesc [internal]
544 DWORD
widDsDesc(UINT wDevID
, PDSDRIVERDESC desc
)
546 *desc
= WInDev
[wDevID
].ds_desc
;
547 return MMSYSERR_NOERROR
;
550 /**************************************************************************
551 * widMessage (WINEPULSE.@)
553 DWORD WINAPI
PULSE_widMessage(UINT wDevID
, UINT wMsg
, DWORD_PTR dwUser
,
554 DWORD_PTR dwParam1
, DWORD_PTR dwParam2
) {
561 /* FIXME: Pretend this is supported */
563 case WIDM_OPEN
: return widOpen (wDevID
, (DWORD_PTR
*)dwUser
, (LPWAVEOPENDESC
)dwParam1
, dwParam2
);
564 case WIDM_CLOSE
: return widClose (wDevID
, (WINE_WAVEINST
*)dwUser
);
565 case WIDM_ADDBUFFER
: return widAddBuffer ((WINE_WAVEINST
*)dwUser
, (LPWAVEHDR
)dwParam1
, dwParam2
);
566 case WIDM_PREPARE
: return MMSYSERR_NOTSUPPORTED
;
567 case WIDM_UNPREPARE
: return MMSYSERR_NOTSUPPORTED
;
568 case WIDM_GETDEVCAPS
: return widGetDevCaps(wDevID
, (LPWAVEINCAPSW
)dwParam1
, dwParam2
);
569 case WIDM_GETNUMDEVS
: return widGetNumDevs();
570 case WIDM_GETPOS
: return widGetPosition ((WINE_WAVEINST
*)dwUser
, (LPMMTIME
)dwParam1
, dwParam2
);
571 case WIDM_RESET
: return widRecorderMessage((WINE_WAVEINST
*)dwUser
, WINE_WM_RESETTING
);
572 case WIDM_START
: return widRecorderMessage((WINE_WAVEINST
*)dwUser
, WINE_WM_STARTING
);
573 case WIDM_STOP
: return widRecorderMessage((WINE_WAVEINST
*)dwUser
, WINE_WM_STOPPING
);
574 case DRV_QUERYDEVICEINTERFACESIZE
: return widDevInterfaceSize(wDevID
, (LPDWORD
)dwParam1
);
575 case DRV_QUERYDEVICEINTERFACE
: return widDevInterface(wDevID
, (PWCHAR
)dwParam1
, dwParam2
);
576 case DRV_QUERYDSOUNDIFACE
: return MMSYSERR_NOTSUPPORTED
; /* Use emulation, as there is no advantage */
577 case DRV_QUERYDSOUNDDESC
: return widDsDesc(wDevID
, (PDSDRIVERDESC
)dwParam1
);
579 FIXME("unknown message %d!\n", wMsg
);
581 return MMSYSERR_NOTSUPPORTED
;
584 #else /* HAVE_PULSEAUDIO */
586 /**************************************************************************
587 * widMessage (WINEPULSE.@)
589 DWORD WINAPI
PULSE_widMessage(WORD wDevID
, WORD wMsg
, DWORD dwUser
,
590 DWORD dwParam1
, DWORD dwParam2
) {
591 FIXME("(%u, %04X, %08X, %08X, %08X):stub\n", wDevID
, wMsg
, dwUser
, dwParam1
, dwParam2
);
592 return MMSYSERR_NOTENABLED
;
595 #endif /* HAVE_PULSEAUDIO */