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_CopyData [internal]
74 * Copys data from the fragments pulse returns to queued buffers.
76 static void widRecorder_CopyData(WINE_WAVEINST
*wwi
) {
77 LPWAVEHDR lpWaveHdr
= wwi
->lpQueuePtr
;
80 /* Get this value once and trust it. Note that the total available is made
81 * of one _or more_ fragments. These fragments will probably not align with
82 * the wavehdr buffer sizes. */
83 pa_threaded_mainloop_lock(PULSE_ml
);
84 bytes_avail
= pa_stream_readable_size(wwi
->stream
);
85 pa_threaded_mainloop_unlock(PULSE_ml
);
87 if (bytes_avail
== -1) {
88 ERR("pa_stream_readable_size() returned -1, record stream has failed.\n");
92 /* If there is an already peeked buffer, add it to the total */
94 bytes_avail
+= wwi
->buffer_length
- wwi
->buffer_read_offset
;
96 for (;bytes_avail
&& lpWaveHdr
; lpWaveHdr
= wwi
->lpQueuePtr
) {
100 pa_threaded_mainloop_lock(PULSE_ml
);
101 pa_stream_peek(wwi
->stream
, &wwi
->buffer
, &wwi
->buffer_length
);
102 pa_threaded_mainloop_unlock(PULSE_ml
);
103 wwi
->buffer_read_offset
= 0;
105 if (!wwi
->buffer
|| !wwi
->buffer_length
) {
106 WARN("pa_stream_peek failed\n");
111 peek_avail
= min(wwi
->buffer_length
- wwi
->buffer_read_offset
,
112 lpWaveHdr
->dwBufferLength
- lpWaveHdr
->dwBytesRecorded
);
114 memcpy(lpWaveHdr
->lpData
+ lpWaveHdr
->dwBytesRecorded
,
115 (PBYTE
)wwi
->buffer
+ wwi
->buffer_read_offset
,
118 wwi
->buffer_read_offset
+= peek_avail
;
119 lpWaveHdr
->dwBytesRecorded
+= peek_avail
;
120 bytes_avail
-= peek_avail
;
122 if (lpWaveHdr
->dwBytesRecorded
== lpWaveHdr
->dwBufferLength
) {
123 lpWaveHdr
->dwFlags
&= ~WHDR_INQUEUE
;
124 lpWaveHdr
->dwFlags
|= WHDR_DONE
;
125 wwi
->lpQueuePtr
= lpWaveHdr
->lpNext
;
126 widNotifyClient(wwi
, WIM_DATA
, (DWORD
)lpWaveHdr
, 0);
129 if (wwi
->buffer_read_offset
== wwi
->buffer_length
) {
130 pa_threaded_mainloop_lock(PULSE_ml
);
131 pa_stream_drop(wwi
->stream
);
133 pa_threaded_mainloop_unlock(PULSE_ml
);
135 } /* for(bytes_avail && lpWaveHdr) */
140 static void widRecorder_ProcessMessages(WINE_WAVEINST
* wwi
) {
142 enum win_wm_message msg
;
147 while (PULSE_RetrieveRingMessage(&wwi
->msgRing
, &msg
, ¶m
, &ev
)) {
148 TRACE("Received %s %x\n", PULSE_getCmdString(msg
), param
);
152 /* Spin the loop in widRecorder */
156 case WINE_WM_STARTING
:
157 wwi
->dwLastReset
= wwi
->timing_info
->read_index
;
158 pa_threaded_mainloop_lock(PULSE_ml
);
159 PULSE_WaitForOperation(pa_stream_cork(wwi
->stream
, 0, PULSE_StreamSuccessCallback
, NULL
));
160 pa_threaded_mainloop_unlock(PULSE_ml
);
161 wwi
->state
= WINE_WS_PLAYING
;
166 lpWaveHdr
= (LPWAVEHDR
)param
;
167 lpWaveHdr
->lpNext
= 0;
168 /* insert buffer at the end of queue */
171 for (wh
= &(wwi
->lpQueuePtr
); *wh
; wh
= &((*wh
)->lpNext
));
176 case WINE_WM_STOPPING
:
177 if (wwi
->state
!= WINE_WS_STOPPED
) {
178 wwi
->state
= WINE_WS_STOPPED
;
179 pa_threaded_mainloop_lock(PULSE_ml
);
180 PULSE_WaitForOperation(pa_stream_cork(wwi
->stream
, 1, PULSE_StreamSuccessCallback
, NULL
));
182 pa_stream_drop(wwi
->stream
);
185 pa_threaded_mainloop_unlock(PULSE_ml
);
187 /* return only the current buffer to app */
188 if ((lpWaveHdr
= wwi
->lpQueuePtr
)) {
189 LPWAVEHDR lpNext
= lpWaveHdr
->lpNext
;
190 TRACE("stop %p %p\n", lpWaveHdr
, lpWaveHdr
->lpNext
);
191 lpWaveHdr
->dwFlags
&= ~WHDR_INQUEUE
;
192 lpWaveHdr
->dwFlags
|= WHDR_DONE
;
193 wwi
->lpQueuePtr
= lpNext
;
194 widNotifyClient(wwi
, WIM_DATA
, (DWORD
)lpWaveHdr
, 0);
200 case WINE_WM_RESETTING
:
201 if (wwi
->state
!= WINE_WS_STOPPED
) {
202 wwi
->state
= WINE_WS_STOPPED
;
203 pa_threaded_mainloop_lock(PULSE_ml
);
204 PULSE_WaitForOperation(pa_stream_cork(wwi
->stream
, 1, PULSE_StreamSuccessCallback
, NULL
));
206 pa_stream_drop(wwi
->stream
);
209 pa_threaded_mainloop_unlock(PULSE_ml
);
212 /* return all the buffers to the app */
213 lpWaveHdr
= wwi
->lpPlayPtr
? wwi
->lpPlayPtr
: wwi
->lpQueuePtr
;
214 for (; lpWaveHdr
; lpWaveHdr
= wwi
->lpQueuePtr
) {
215 lpWaveHdr
->dwFlags
&= ~WHDR_INQUEUE
;
216 lpWaveHdr
->dwFlags
|= WHDR_DONE
;
217 wwi
->lpQueuePtr
= lpWaveHdr
->lpNext
;
218 widNotifyClient(wwi
, WIM_DATA
, (DWORD
)lpWaveHdr
, 0);
223 case WINE_WM_CLOSING
:
225 wwi
->state
= WINE_WS_CLOSED
;
228 /* shouldn't go here */
231 FIXME("unknown message %d\n", msg
);
237 /**************************************************************************
238 * widRecorder [internal]
240 static DWORD CALLBACK
widRecorder(LPVOID lpParam
) {
241 WINE_WAVEINST
*wwi
= (WINE_WAVEINST
*)lpParam
;
243 wwi
->state
= WINE_WS_STOPPED
;
244 SetEvent(wwi
->hStartUpEvent
);
247 PULSE_WaitRingMessage(&wwi
->msgRing
, INFINITE
);
248 widRecorder_ProcessMessages(wwi
);
249 if (wwi
->state
== WINE_WS_PLAYING
&& wwi
->lpQueuePtr
)
250 widRecorder_CopyData(wwi
);
256 /**************************************************************************
259 static DWORD
widOpen(WORD wDevID
, DWORD_PTR
*lpdwUser
, LPWAVEOPENDESC lpDesc
, DWORD dwFlags
) {
261 WINE_WAVEINST
*wwi
= NULL
;
262 DWORD ret
= MMSYSERR_NOERROR
;
264 TRACE("(%u, %p, %08X);\n", wDevID
, lpDesc
, dwFlags
);
265 if (lpDesc
== NULL
) {
266 WARN("Invalid Parameter !\n");
267 return MMSYSERR_INVALPARAM
;
270 if (wDevID
>= PULSE_WidNumDevs
) {
271 TRACE("Asked for device %d, but only %d known!\n", wDevID
, PULSE_WidNumDevs
);
272 return MMSYSERR_BADDEVICEID
;
274 wdi
= &WInDev
[wDevID
];
276 wwi
= HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, sizeof(WINE_WAVEINST
));
277 if (!wwi
) return MMSYSERR_NOMEM
;
278 *lpdwUser
= (DWORD_PTR
)wwi
;
280 /* check to see if format is supported and make pa_sample_spec struct */
281 if (!PULSE_SetupFormat(lpDesc
->lpFormat
, &wwi
->sample_spec
)) {
282 WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
283 lpDesc
->lpFormat
->wFormatTag
, lpDesc
->lpFormat
->nChannels
,
284 lpDesc
->lpFormat
->nSamplesPerSec
);
285 ret
= WAVERR_BADFORMAT
;
289 if (TRACE_ON(wave
)) {
290 char t
[PA_SAMPLE_SPEC_SNPRINT_MAX
];
291 pa_sample_spec_snprint(t
, sizeof(t
), &wwi
->sample_spec
);
292 TRACE("Sample spec '%s'\n", t
);
295 if (dwFlags
& WAVE_FORMAT_QUERY
) {
296 TRACE("Query format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
297 lpDesc
->lpFormat
->wFormatTag
, lpDesc
->lpFormat
->nChannels
,
298 lpDesc
->lpFormat
->nSamplesPerSec
);
299 ret
= MMSYSERR_NOERROR
;
303 wwi
->wFlags
= HIWORD(dwFlags
& CALLBACK_TYPEMASK
);
304 wwi
->waveDesc
= *lpDesc
;
305 PULSE_InitRingMessage(&wwi
->msgRing
);
307 wwi
->stream
= pa_stream_new(PULSE_context
, "WaveIn", &wwi
->sample_spec
, NULL
);
309 ret
= WAVERR_BADFORMAT
;
313 pa_stream_set_state_callback(wwi
->stream
, PULSE_StreamStateCallback
, wwi
);
314 pa_stream_set_read_callback (wwi
->stream
, PULSE_StreamRequestCallback
, wwi
);
316 wwi
->buffer_attr
.maxlength
= (uint32_t)-1;
317 wwi
->buffer_attr
.fragsize
= pa_bytes_per_second(&wwi
->sample_spec
) / 100;
319 pa_threaded_mainloop_lock(PULSE_ml
);
320 TRACE("Asking to open %s for recording.\n", wdi
->device_name
);
321 pa_stream_connect_record(wwi
->stream
, wdi
->device_name
, &wwi
->buffer_attr
,
322 PA_STREAM_START_CORKED
|
323 PA_STREAM_AUTO_TIMING_UPDATE
);
326 pa_context_state_t cstate
= pa_context_get_state(PULSE_context
);
327 pa_stream_state_t sstate
= pa_stream_get_state(wwi
->stream
);
329 if (cstate
== PA_CONTEXT_FAILED
|| cstate
== PA_CONTEXT_TERMINATED
||
330 sstate
== PA_STREAM_FAILED
|| sstate
== PA_STREAM_TERMINATED
) {
331 ERR("Failed to connect context object: %s\n", pa_strerror(pa_context_errno(PULSE_context
)));
332 ret
= MMSYSERR_NODRIVER
;
333 pa_threaded_mainloop_unlock(PULSE_ml
);
337 if (sstate
== PA_STREAM_READY
)
340 pa_threaded_mainloop_wait(PULSE_ml
);
342 TRACE("(%p)->stream connected for recording.\n", wwi
);
344 PULSE_WaitForOperation(pa_stream_update_timing_info(wwi
->stream
, PULSE_StreamSuccessCallback
, wwi
));
346 wwi
->timing_info
= pa_stream_get_timing_info(wwi
->stream
);
347 assert(wwi
->timing_info
);
348 pa_threaded_mainloop_unlock(PULSE_ml
);
350 wwi
->hStartUpEvent
= CreateEventW(NULL
, FALSE
, FALSE
, NULL
);
351 wwi
->hThread
= CreateThread(NULL
, 0, widRecorder
, (LPVOID
)wwi
, 0, &(wwi
->dwThreadID
));
353 SetThreadPriority(wwi
->hThread
, THREAD_PRIORITY_TIME_CRITICAL
);
355 ERR("Thread creation for the widRecorder failed!\n");
356 ret
= MMSYSERR_NOMEM
;
359 WaitForSingleObject(wwi
->hStartUpEvent
, INFINITE
);
360 CloseHandle(wwi
->hStartUpEvent
);
361 wwi
->hStartUpEvent
= INVALID_HANDLE_VALUE
;
363 return widNotifyClient(wwi
, WIM_OPEN
, 0L, 0L);
369 if (wwi
->hStartUpEvent
!= INVALID_HANDLE_VALUE
)
370 CloseHandle(wwi
->hStartUpEvent
);
372 if (wwi
->msgRing
.ring_buffer_size
> 0)
373 PULSE_DestroyRingMessage(&wwi
->msgRing
);
376 if (pa_stream_get_state(wwi
->stream
) == PA_STREAM_READY
)
377 pa_stream_disconnect(wwi
->stream
);
378 pa_stream_unref(wwi
->stream
);
380 HeapFree(GetProcessHeap(), 0, wwi
);
384 /**************************************************************************
385 * widClose [internal]
387 static DWORD
widClose(WORD wDevID
, WINE_WAVEINST
*wwi
) {
390 TRACE("(%u, %p);\n", wDevID
, wwi
);
391 if (wDevID
>= PULSE_WidNumDevs
) {
392 WARN("Asked for device %d, but only %d known!\n", wDevID
, PULSE_WodNumDevs
);
393 return MMSYSERR_INVALHANDLE
;
395 WARN("Stream instance invalid.\n");
396 return MMSYSERR_INVALHANDLE
;
399 if (wwi
->state
!= WINE_WS_FAILED
) {
400 if (wwi
->lpQueuePtr
) {
401 WARN("buffers recording recording !\n");
402 return WAVERR_STILLPLAYING
;
405 pa_threaded_mainloop_lock(PULSE_ml
);
406 if (pa_stream_get_state(wwi
->stream
) == PA_STREAM_READY
)
407 pa_stream_drop(wwi
->stream
);
408 pa_stream_disconnect(wwi
->stream
);
409 pa_threaded_mainloop_unlock(PULSE_ml
);
411 if (wwi
->hThread
!= INVALID_HANDLE_VALUE
)
412 PULSE_AddRingMessage(&wwi
->msgRing
, WINE_WM_CLOSING
, 0, TRUE
);
414 PULSE_DestroyRingMessage(&wwi
->msgRing
);
416 ret
= widNotifyClient(wwi
, WIM_CLOSE
, 0L, 0L);
418 pa_stream_unref(wwi
->stream
);
419 TRACE("Deallocating record instance.\n");
420 HeapFree(GetProcessHeap(), 0, wwi
);
424 /**************************************************************************
425 * widAddBuffer [internal]
428 static DWORD
widAddBuffer(WINE_WAVEINST
* wwi
, LPWAVEHDR lpWaveHdr
, DWORD dwSize
) {
429 TRACE("(%p, %p, %08X);\n", wwi
, lpWaveHdr
, dwSize
);
431 if (!wwi
|| wwi
->state
== WINE_WS_FAILED
) {
432 WARN("Stream instance invalid.\n");
433 return MMSYSERR_INVALHANDLE
;
436 if (lpWaveHdr
->lpData
== NULL
|| !(lpWaveHdr
->dwFlags
& WHDR_PREPARED
))
437 return WAVERR_UNPREPARED
;
439 if (lpWaveHdr
->dwFlags
& WHDR_INQUEUE
)
440 return WAVERR_STILLPLAYING
;
442 lpWaveHdr
->dwFlags
&= ~WHDR_DONE
;
443 lpWaveHdr
->dwFlags
|= WHDR_INQUEUE
;
444 lpWaveHdr
->dwBytesRecorded
= 0;
445 lpWaveHdr
->lpNext
= 0;
447 PULSE_AddRingMessage(&wwi
->msgRing
, WINE_WM_HEADER
, (DWORD
)lpWaveHdr
, FALSE
);
449 return MMSYSERR_NOERROR
;
452 /**************************************************************************
453 * widRecorderMessage [internal]
455 static DWORD
widRecorderMessage(WINE_WAVEINST
*wwi
, enum win_wm_message message
) {
456 if (!wwi
|| wwi
->state
== WINE_WS_FAILED
) {
457 WARN("Stream instance invalid.\n");
458 return MMSYSERR_INVALHANDLE
;
461 PULSE_AddRingMessage(&wwi
->msgRing
, message
, 0, TRUE
);
462 return MMSYSERR_NOERROR
;
465 /**************************************************************************
466 * widGetPosition [internal]
468 static DWORD
widGetPosition(WINE_WAVEINST
*wwi
, LPMMTIME lpTime
, DWORD uSize
) {
470 if (!wwi
|| wwi
->state
== WINE_WS_FAILED
) {
471 WARN("Stream instance invalid.\n");
472 return MMSYSERR_INVALHANDLE
;
475 if (lpTime
== NULL
) return MMSYSERR_INVALPARAM
;
477 return PULSE_UsecToMMTime(pa_bytes_to_usec(wwi
->timing_info
->read_index
- wwi
->dwLastReset
, &wwi
->sample_spec
), lpTime
, &wwi
->sample_spec
);
480 /**************************************************************************
481 * widGetDevCaps [internal]
483 static DWORD
widGetDevCaps(DWORD wDevID
, LPWAVEINCAPSW lpCaps
, DWORD dwSize
) {
484 TRACE("(%u, %p, %u);\n", wDevID
, lpCaps
, dwSize
);
486 if (lpCaps
== NULL
) return MMSYSERR_NOTENABLED
;
488 if (wDevID
>= PULSE_WidNumDevs
) {
489 TRACE("Asked for device %d, but only %d known!\n", wDevID
, PULSE_WidNumDevs
);
490 return MMSYSERR_INVALHANDLE
;
493 memcpy(lpCaps
, &(WInDev
[wDevID
].caps
.in
), min(dwSize
, sizeof(*lpCaps
)));
494 return MMSYSERR_NOERROR
;
497 /**************************************************************************
498 * widGetNumDevs [internal]
499 * Context-sanity check here, as if we respond with 0, WINE will move on
500 * to the next wavein driver.
502 static DWORD
widGetNumDevs(void) {
503 if (pa_context_get_state(PULSE_context
) != PA_CONTEXT_READY
)
506 return PULSE_WidNumDevs
;
509 /**************************************************************************
510 * widDevInterfaceSize [internal]
512 static DWORD
widDevInterfaceSize(UINT wDevID
, LPDWORD dwParam1
) {
513 TRACE("(%u, %p)\n", wDevID
, dwParam1
);
515 *dwParam1
= MultiByteToWideChar(CP_UTF8
, 0, WInDev
[wDevID
].interface_name
, -1,
516 NULL
, 0 ) * sizeof(WCHAR
);
517 return MMSYSERR_NOERROR
;
520 /**************************************************************************
521 * widDevInterface [internal]
523 static DWORD
widDevInterface(UINT wDevID
, PWCHAR dwParam1
, DWORD dwParam2
) {
524 if (dwParam2
>= MultiByteToWideChar(CP_UTF8
, 0, WInDev
[wDevID
].interface_name
, -1,
525 NULL
, 0 ) * sizeof(WCHAR
))
527 MultiByteToWideChar(CP_UTF8
, 0, WInDev
[wDevID
].interface_name
, -1,
528 dwParam1
, dwParam2
/ sizeof(WCHAR
));
529 return MMSYSERR_NOERROR
;
531 return MMSYSERR_INVALPARAM
;
534 /**************************************************************************
535 * widDsDesc [internal]
537 DWORD
widDsDesc(UINT wDevID
, PDSDRIVERDESC desc
)
539 *desc
= WInDev
[wDevID
].ds_desc
;
540 return MMSYSERR_NOERROR
;
543 /**************************************************************************
544 * widMessage (WINEPULSE.@)
546 DWORD WINAPI
PULSE_widMessage(UINT wDevID
, UINT wMsg
, DWORD_PTR dwUser
,
547 DWORD_PTR dwParam1
, DWORD_PTR dwParam2
) {
554 /* FIXME: Pretend this is supported */
556 case WIDM_OPEN
: return widOpen (wDevID
, (DWORD_PTR
*)dwUser
, (LPWAVEOPENDESC
)dwParam1
, dwParam2
);
557 case WIDM_CLOSE
: return widClose (wDevID
, (WINE_WAVEINST
*)dwUser
);
558 case WIDM_ADDBUFFER
: return widAddBuffer ((WINE_WAVEINST
*)dwUser
, (LPWAVEHDR
)dwParam1
, dwParam2
);
559 case WIDM_PREPARE
: return MMSYSERR_NOTSUPPORTED
;
560 case WIDM_UNPREPARE
: return MMSYSERR_NOTSUPPORTED
;
561 case WIDM_GETDEVCAPS
: return widGetDevCaps(wDevID
, (LPWAVEINCAPSW
)dwParam1
, dwParam2
);
562 case WIDM_GETNUMDEVS
: return widGetNumDevs();
563 case WIDM_GETPOS
: return widGetPosition ((WINE_WAVEINST
*)dwUser
, (LPMMTIME
)dwParam1
, dwParam2
);
564 case WIDM_RESET
: return widRecorderMessage((WINE_WAVEINST
*)dwUser
, WINE_WM_RESETTING
);
565 case WIDM_START
: return widRecorderMessage((WINE_WAVEINST
*)dwUser
, WINE_WM_STARTING
);
566 case WIDM_STOP
: return widRecorderMessage((WINE_WAVEINST
*)dwUser
, WINE_WM_STOPPING
);
567 case DRV_QUERYDEVICEINTERFACESIZE
: return widDevInterfaceSize(wDevID
, (LPDWORD
)dwParam1
);
568 case DRV_QUERYDEVICEINTERFACE
: return widDevInterface(wDevID
, (PWCHAR
)dwParam1
, dwParam2
);
569 case DRV_QUERYDSOUNDIFACE
: return MMSYSERR_NOTSUPPORTED
; /* Use emulation, as there is no advantage */
570 case DRV_QUERYDSOUNDDESC
: return widDsDesc(wDevID
, (PDSDRIVERDESC
)dwParam1
);
572 FIXME("unknown message %d!\n", wMsg
);
574 return MMSYSERR_NOTSUPPORTED
;
577 #else /* HAVE_PULSEAUDIO */
579 /**************************************************************************
580 * widMessage (WINEPULSE.@)
582 DWORD WINAPI
PULSE_widMessage(WORD wDevID
, WORD wMsg
, DWORD dwUser
,
583 DWORD dwParam1
, DWORD dwParam2
) {
584 FIXME("(%u, %04X, %08X, %08X, %08X):stub\n", wDevID
, wMsg
, dwUser
, dwParam1
, dwParam2
);
585 return MMSYSERR_NOTENABLED
;
588 #endif /* HAVE_PULSEAUDIO */