2 * Sample Wine Driver for Advanced Linux Sound System (ALSA)
3 * Based on version <final> of the ALSA API
5 * Copyright 2002 Eric Pouech
6 * 2002 Marco Pietrobono
7 * 2003 Christian Costa : WaveIn support
8 * 2006-2007 Maarten Lankhorst
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
25 /*======================================================================*
26 * Low level WAVE OUT implementation *
27 *======================================================================*/
30 #include "wine/port.h"
42 #ifdef HAVE_SYS_IOCTL_H
43 # include <sys/ioctl.h>
45 #ifdef HAVE_SYS_MMAN_H
46 # include <sys/mman.h>
62 #include "wine/library.h"
63 #include "wine/unicode.h"
64 #include "wine/debug.h"
66 WINE_DEFAULT_DEBUG_CHANNEL(wave
);
68 WINE_WAVEDEV
*WOutDev
;
69 DWORD ALSA_WodNumMallocedDevs
;
70 DWORD ALSA_WodNumDevs
;
72 /**************************************************************************
73 * wodNotifyClient [internal]
75 static void wodNotifyClient(WINE_WAVEDEV
* wwo
, WORD wMsg
, DWORD_PTR dwParam1
, DWORD_PTR dwParam2
)
77 TRACE("wMsg = 0x%04x dwParm1 = %lx dwParam2 = %lx\n", wMsg
, dwParam1
, dwParam2
);
83 DriverCallback(wwo
->waveDesc
.dwCallback
, wwo
->wFlags
, (HDRVR
)wwo
->waveDesc
.hWave
,
84 wMsg
, wwo
->waveDesc
.dwInstance
, dwParam1
, dwParam2
);
87 FIXME("Unknown callback message %u\n", wMsg
);
91 /**************************************************************************
92 * wodUpdatePlayedTotal [internal]
95 static BOOL
wodUpdatePlayedTotal(WINE_WAVEDEV
* wwo
, snd_pcm_status_t
* ps
)
97 snd_pcm_sframes_t delay
;
98 snd_pcm_sframes_t avail
;
99 snd_pcm_uframes_t buf_size
= 0;
100 snd_pcm_state_t state
;
102 state
= snd_pcm_state(wwo
->pcm
);
103 avail
= snd_pcm_avail_update(wwo
->pcm
);
104 snd_pcm_hw_params_get_buffer_size(wwo
->hw_params
, &buf_size
);
105 delay
= buf_size
- avail
;
107 if (state
!= SND_PCM_STATE_RUNNING
&& state
!= SND_PCM_STATE_PREPARED
)
109 WARN("Unexpected state (%d) while updating Total Played, resetting\n", state
);
110 wine_snd_pcm_recover(wwo
->pcm
, -EPIPE
, 0);
114 /* A delay < 0 indicates an underrun; for our purposes that's 0. */
117 WARN("Unexpected delay (%ld) while updating Total Played, resetting\n", delay
);
121 InterlockedExchange((LONG
*)&wwo
->dwPlayedTotal
, wwo
->dwWrittenTotal
- snd_pcm_frames_to_bytes(wwo
->pcm
, delay
));
125 /**************************************************************************
126 * wodPlayer_BeginWaveHdr [internal]
128 * Makes the specified lpWaveHdr the currently playing wave header.
129 * If the specified wave header is a begin loop and we're not already in
130 * a loop, setup the loop.
132 static void wodPlayer_BeginWaveHdr(WINE_WAVEDEV
* wwo
, LPWAVEHDR lpWaveHdr
)
134 wwo
->lpPlayPtr
= lpWaveHdr
;
136 if (!lpWaveHdr
) return;
138 if (lpWaveHdr
->dwFlags
& WHDR_BEGINLOOP
) {
139 if (wwo
->lpLoopPtr
) {
140 WARN("Already in a loop. Discarding loop on this header (%p)\n", lpWaveHdr
);
142 TRACE("Starting loop (%dx) with %p\n", lpWaveHdr
->dwLoops
, lpWaveHdr
);
143 wwo
->lpLoopPtr
= lpWaveHdr
;
144 /* Windows does not touch WAVEHDR.dwLoops,
145 * so we need to make an internal copy */
146 wwo
->dwLoops
= lpWaveHdr
->dwLoops
;
149 wwo
->dwPartialOffset
= 0;
152 /**************************************************************************
153 * wodPlayer_PlayPtrNext [internal]
155 * Advance the play pointer to the next waveheader, looping if required.
157 static LPWAVEHDR
wodPlayer_PlayPtrNext(WINE_WAVEDEV
* wwo
)
159 LPWAVEHDR lpWaveHdr
= wwo
->lpPlayPtr
;
161 wwo
->dwPartialOffset
= 0;
162 if ((lpWaveHdr
->dwFlags
& WHDR_ENDLOOP
) && wwo
->lpLoopPtr
) {
163 /* We're at the end of a loop, loop if required */
164 if (--wwo
->dwLoops
> 0) {
165 wwo
->lpPlayPtr
= wwo
->lpLoopPtr
;
167 /* Handle overlapping loops correctly */
168 if (wwo
->lpLoopPtr
!= lpWaveHdr
&& (lpWaveHdr
->dwFlags
& WHDR_BEGINLOOP
)) {
169 FIXME("Correctly handled case ? (ending loop buffer also starts a new loop)\n");
170 /* shall we consider the END flag for the closing loop or for
171 * the opening one or for both ???
172 * code assumes for closing loop only
175 lpWaveHdr
= lpWaveHdr
->lpNext
;
177 wwo
->lpLoopPtr
= NULL
;
178 wodPlayer_BeginWaveHdr(wwo
, lpWaveHdr
);
181 /* We're not in a loop. Advance to the next wave header */
182 wodPlayer_BeginWaveHdr(wwo
, lpWaveHdr
= lpWaveHdr
->lpNext
);
188 /**************************************************************************
189 * wodPlayer_DSPWait [internal]
190 * Returns the number of milliseconds to wait for the DSP buffer to play a
193 static DWORD
wodPlayer_DSPWait(const WINE_WAVEDEV
*wwo
)
195 /* time for one period to be played */
199 err
= snd_pcm_hw_params_get_period_time(wwo
->hw_params
, &val
, &dir
);
203 /**************************************************************************
204 * wodPlayer_NotifyWait [internal]
205 * Returns the number of milliseconds to wait before attempting to notify
206 * completion of the specified wavehdr.
207 * This is based on the number of bytes remaining to be written in the
210 static DWORD
wodPlayer_NotifyWait(const WINE_WAVEDEV
* wwo
, LPWAVEHDR lpWaveHdr
)
214 if (lpWaveHdr
->reserved
< wwo
->dwPlayedTotal
) {
217 dwMillis
= (lpWaveHdr
->reserved
- wwo
->dwPlayedTotal
) * 1000 / wwo
->format
.Format
.nAvgBytesPerSec
;
218 if (!dwMillis
) dwMillis
= 1;
225 /**************************************************************************
226 * wodPlayer_WriteMaxFrags [internal]
227 * Writes the maximum number of frames possible to the DSP and returns
228 * the number of frames written.
230 static int wodPlayer_WriteMaxFrags(WINE_WAVEDEV
* wwo
, DWORD
* frames
)
232 /* Only attempt to write to free frames */
233 LPWAVEHDR lpWaveHdr
= wwo
->lpPlayPtr
;
234 DWORD dwLength
= snd_pcm_bytes_to_frames(wwo
->pcm
, lpWaveHdr
->dwBufferLength
- wwo
->dwPartialOffset
);
235 int toWrite
= min(dwLength
, *frames
);
238 TRACE("Writing wavehdr %p.%u[%u]\n", lpWaveHdr
, wwo
->dwPartialOffset
, lpWaveHdr
->dwBufferLength
);
241 written
= (wwo
->write
)(wwo
->pcm
, lpWaveHdr
->lpData
+ wwo
->dwPartialOffset
, toWrite
);
243 /* XRUN occurred. let's try to recover */
244 wine_snd_pcm_recover(wwo
->pcm
, written
, 0);
245 written
= (wwo
->write
)(wwo
->pcm
, lpWaveHdr
->lpData
+ wwo
->dwPartialOffset
, toWrite
);
249 ERR("Error in writing wavehdr. Reason: %s\n", snd_strerror(written
));
255 wwo
->dwPartialOffset
+= snd_pcm_frames_to_bytes(wwo
->pcm
, written
);
256 if (wwo
->dwPartialOffset
+ wwo
->format
.Format
.nBlockAlign
- 1 >= lpWaveHdr
->dwBufferLength
) {
257 /* this will be used to check if the given wave header has been fully played or not... */
258 wwo
->dwPartialOffset
= lpWaveHdr
->dwBufferLength
;
259 /* If we wrote all current wavehdr, skip to the next one */
260 wodPlayer_PlayPtrNext(wwo
);
263 wwo
->dwWrittenTotal
+= snd_pcm_frames_to_bytes(wwo
->pcm
, written
);
264 TRACE("dwWrittenTotal=%u\n", wwo
->dwWrittenTotal
);
270 /**************************************************************************
271 * wodPlayer_NotifyCompletions [internal]
273 * Notifies and remove from queue all wavehdrs which have been played to
274 * the speaker (ie. they have cleared the ALSA buffer). If force is true,
275 * we notify all wavehdrs and remove them all from the queue even if they
276 * are unplayed or part of a loop.
278 static DWORD
wodPlayer_NotifyCompletions(WINE_WAVEDEV
* wwo
, BOOL force
)
282 /* Start from lpQueuePtr and keep notifying until:
283 * - we hit an unwritten wavehdr
284 * - we hit the beginning of a running loop
285 * - we hit a wavehdr which hasn't finished playing
289 lpWaveHdr
= wwo
->lpQueuePtr
;
290 if (!lpWaveHdr
) {TRACE("Empty queue\n"); break;}
293 snd_pcm_uframes_t frames
;
294 snd_pcm_hw_params_get_period_size(wwo
->hw_params
, &frames
, NULL
);
296 if (lpWaveHdr
== wwo
->lpPlayPtr
) {TRACE("play %p\n", lpWaveHdr
); break;}
297 if (lpWaveHdr
== wwo
->lpLoopPtr
) {TRACE("loop %p\n", lpWaveHdr
); break;}
298 if (lpWaveHdr
->reserved
> wwo
->dwPlayedTotal
+ frames
) {TRACE("still playing %p (%lu/%u)\n", lpWaveHdr
, lpWaveHdr
->reserved
, wwo
->dwPlayedTotal
);break;}
300 wwo
->dwPlayedTotal
+= lpWaveHdr
->reserved
- wwo
->dwPlayedTotal
;
301 wwo
->lpQueuePtr
= lpWaveHdr
->lpNext
;
303 lpWaveHdr
->dwFlags
&= ~WHDR_INQUEUE
;
304 lpWaveHdr
->dwFlags
|= WHDR_DONE
;
306 wodNotifyClient(wwo
, WOM_DONE
, (DWORD_PTR
)lpWaveHdr
, 0);
308 return (lpWaveHdr
&& lpWaveHdr
!= wwo
->lpPlayPtr
&& lpWaveHdr
!= wwo
->lpLoopPtr
) ?
309 wodPlayer_NotifyWait(wwo
, lpWaveHdr
) : INFINITE
;
313 /**************************************************************************
314 * wodPlayer_Reset [internal]
316 * wodPlayer helper. Resets current output stream.
318 static void wodPlayer_Reset(WINE_WAVEDEV
* wwo
, BOOL reset
)
321 TRACE("(%p)\n", wwo
);
323 wodUpdatePlayedTotal(wwo
, NULL
);
324 /* updates current notify list */
325 wodPlayer_NotifyCompletions(wwo
, FALSE
);
327 if ( (err
= snd_pcm_drop(wwo
->pcm
)) < 0) {
328 FIXME("flush: %s\n", snd_strerror(err
));
330 wwo
->state
= WINE_WS_STOPPED
;
333 if ( (err
= snd_pcm_prepare(wwo
->pcm
)) < 0 )
334 ERR("pcm prepare failed: %s\n", snd_strerror(err
));
337 enum win_wm_message msg
;
341 /* remove any buffer */
342 wodPlayer_NotifyCompletions(wwo
, TRUE
);
344 wwo
->lpPlayPtr
= wwo
->lpQueuePtr
= wwo
->lpLoopPtr
= NULL
;
345 wwo
->state
= WINE_WS_STOPPED
;
346 wwo
->dwPlayedTotal
= wwo
->dwWrittenTotal
= 0;
347 /* Clear partial wavehdr */
348 wwo
->dwPartialOffset
= 0;
350 /* remove any existing message in the ring */
351 EnterCriticalSection(&wwo
->msgRing
.msg_crst
);
352 /* return all pending headers in queue */
353 while (ALSA_RetrieveRingMessage(&wwo
->msgRing
, &msg
, ¶m
, &ev
))
355 if (msg
!= WINE_WM_HEADER
)
357 FIXME("shouldn't have headers left\n");
361 ((LPWAVEHDR
)param
)->dwFlags
&= ~WHDR_INQUEUE
;
362 ((LPWAVEHDR
)param
)->dwFlags
|= WHDR_DONE
;
364 wodNotifyClient(wwo
, WOM_DONE
, param
, 0);
366 ALSA_ResetRingMessage(&wwo
->msgRing
);
367 LeaveCriticalSection(&wwo
->msgRing
.msg_crst
);
369 if (wwo
->lpLoopPtr
) {
370 /* complicated case, not handled yet (could imply modifying the loop counter */
371 FIXME("Pausing while in loop isn't correctly handled yet, expect strange results\n");
372 wwo
->lpPlayPtr
= wwo
->lpLoopPtr
;
373 wwo
->dwPartialOffset
= 0;
374 wwo
->dwWrittenTotal
= wwo
->dwPlayedTotal
; /* this is wrong !!! */
377 DWORD sz
= wwo
->dwPartialOffset
;
379 /* reset all the data as if we had written only up to lpPlayedTotal bytes */
380 /* compute the max size playable from lpQueuePtr */
381 for (ptr
= wwo
->lpQueuePtr
; ptr
!= wwo
->lpPlayPtr
; ptr
= ptr
->lpNext
) {
382 sz
+= ptr
->dwBufferLength
;
384 /* because the reset lpPlayPtr will be lpQueuePtr */
385 if (wwo
->dwWrittenTotal
> wwo
->dwPlayedTotal
+ sz
) ERR("grin\n");
386 wwo
->dwPartialOffset
= sz
- (wwo
->dwWrittenTotal
- wwo
->dwPlayedTotal
);
387 wwo
->dwWrittenTotal
= wwo
->dwPlayedTotal
;
388 wwo
->lpPlayPtr
= wwo
->lpQueuePtr
;
390 wwo
->state
= WINE_WS_PAUSED
;
394 /**************************************************************************
395 * wodPlayer_ProcessMessages [internal]
397 static void wodPlayer_ProcessMessages(WINE_WAVEDEV
* wwo
)
400 enum win_wm_message msg
;
405 while (ALSA_RetrieveRingMessage(&wwo
->msgRing
, &msg
, ¶m
, &ev
)) {
406 TRACE("Received %s %lx\n", ALSA_getCmdString(msg
), param
);
409 case WINE_WM_PAUSING
:
410 if ( snd_pcm_state(wwo
->pcm
) == SND_PCM_STATE_RUNNING
)
412 if ( snd_pcm_hw_params_can_pause(wwo
->hw_params
) )
414 err
= snd_pcm_pause(wwo
->pcm
, 1);
416 ERR("pcm_pause failed: %s\n", snd_strerror(err
));
417 wwo
->state
= WINE_WS_PAUSED
;
421 wodPlayer_Reset(wwo
,FALSE
);
426 case WINE_WM_RESTARTING
:
427 if (wwo
->state
== WINE_WS_PAUSED
)
429 if ( snd_pcm_state(wwo
->pcm
) == SND_PCM_STATE_PAUSED
)
431 err
= snd_pcm_pause(wwo
->pcm
, 0);
433 ERR("pcm_pause failed: %s\n", snd_strerror(err
));
435 wwo
->state
= WINE_WS_PLAYING
;
440 lpWaveHdr
= (LPWAVEHDR
)param
;
442 /* insert buffer at the end of queue */
445 for (wh
= &(wwo
->lpQueuePtr
); *wh
; wh
= &((*wh
)->lpNext
));
449 wodPlayer_BeginWaveHdr(wwo
,lpWaveHdr
);
450 if (wwo
->state
== WINE_WS_STOPPED
)
451 wwo
->state
= WINE_WS_PLAYING
;
453 case WINE_WM_RESETTING
:
454 wodPlayer_Reset(wwo
,TRUE
);
457 case WINE_WM_BREAKLOOP
:
458 if (wwo
->state
== WINE_WS_PLAYING
&& wwo
->lpLoopPtr
!= NULL
) {
459 /* ensure exit at end of current loop */
464 case WINE_WM_CLOSING
:
465 /* sanity check: this should not happen since the device must have been reset before */
466 if (wwo
->lpQueuePtr
|| wwo
->lpPlayPtr
) ERR("out of sync\n");
468 wwo
->state
= WINE_WS_CLOSED
;
471 /* shouldn't go here */
473 FIXME("unknown message %d\n", msg
);
479 /**************************************************************************
480 * wodPlayer_FeedDSP [internal]
481 * Feed as much sound data as we can into the DSP and return the number of
482 * milliseconds before it will be necessary to feed the DSP again.
484 static DWORD
wodPlayer_FeedDSP(WINE_WAVEDEV
* wwo
)
488 wodUpdatePlayedTotal(wwo
, NULL
);
489 availInQ
= snd_pcm_avail_update(wwo
->pcm
);
491 /* no more room... no need to try to feed */
493 /* Feed from partial wavehdr */
494 if (wwo
->lpPlayPtr
&& wwo
->dwPartialOffset
!= 0) {
495 wodPlayer_WriteMaxFrags(wwo
, &availInQ
);
498 /* Feed wavehdrs until we run out of wavehdrs or DSP space */
499 if (wwo
->dwPartialOffset
== 0 && wwo
->lpPlayPtr
) {
501 TRACE("Setting time to elapse for %p to %u\n",
502 wwo
->lpPlayPtr
, wwo
->dwWrittenTotal
+ wwo
->lpPlayPtr
->dwBufferLength
);
503 /* note the value that dwPlayedTotal will return when this wave finishes playing */
504 wwo
->lpPlayPtr
->reserved
= wwo
->dwWrittenTotal
+ wwo
->lpPlayPtr
->dwBufferLength
;
505 } while (wodPlayer_WriteMaxFrags(wwo
, &availInQ
) && wwo
->lpPlayPtr
&& availInQ
> 0);
509 return wodPlayer_DSPWait(wwo
);
512 /**************************************************************************
513 * wodPlayer [internal]
515 static DWORD CALLBACK
wodPlayer(LPVOID pmt
)
517 WORD uDevID
= (DWORD_PTR
)pmt
;
518 WINE_WAVEDEV
* wwo
= &WOutDev
[uDevID
];
519 DWORD dwNextFeedTime
= INFINITE
; /* Time before DSP needs feeding */
520 DWORD dwNextNotifyTime
= INFINITE
; /* Time before next wave completion */
523 wwo
->state
= WINE_WS_STOPPED
;
524 SetEvent(wwo
->hStartUpEvent
);
527 /** Wait for the shortest time before an action is required. If there
528 * are no pending actions, wait forever for a command.
530 dwSleepTime
= min(dwNextFeedTime
, dwNextNotifyTime
);
531 TRACE("waiting %ums (%u,%u)\n", dwSleepTime
, dwNextFeedTime
, dwNextNotifyTime
);
532 ALSA_WaitRingMessage(&wwo
->msgRing
, dwSleepTime
);
533 wodPlayer_ProcessMessages(wwo
);
534 if (wwo
->state
== WINE_WS_PLAYING
) {
535 dwNextFeedTime
= wodPlayer_FeedDSP(wwo
);
536 dwNextNotifyTime
= wodPlayer_NotifyCompletions(wwo
, FALSE
);
537 if (dwNextFeedTime
== INFINITE
) {
538 /* FeedDSP ran out of data, but before giving up, */
539 /* check that a notification didn't give us more */
540 wodPlayer_ProcessMessages(wwo
);
541 if (wwo
->lpPlayPtr
) {
542 TRACE("recovering\n");
543 dwNextFeedTime
= wodPlayer_FeedDSP(wwo
);
547 dwNextFeedTime
= dwNextNotifyTime
= INFINITE
;
553 /**************************************************************************
554 * wodGetDevCaps [internal]
556 static DWORD
wodGetDevCaps(WORD wDevID
, LPWAVEOUTCAPSW lpCaps
, DWORD dwSize
)
558 TRACE("(%u, %p, %u);\n", wDevID
, lpCaps
, dwSize
);
560 if (lpCaps
== NULL
) return MMSYSERR_NOTENABLED
;
562 if (wDevID
>= ALSA_WodNumDevs
) {
563 TRACE("Asked for device %d, but only %d known!\n", wDevID
, ALSA_WodNumDevs
);
564 return MMSYSERR_BADDEVICEID
;
567 memcpy(lpCaps
, &WOutDev
[wDevID
].outcaps
, min(dwSize
, sizeof(*lpCaps
)));
568 return MMSYSERR_NOERROR
;
571 /**************************************************************************
574 static DWORD
wodOpen(WORD wDevID
, LPWAVEOPENDESC lpDesc
, DWORD dwFlags
)
577 snd_pcm_t
* pcm
= NULL
;
578 snd_hctl_t
* hctl
= NULL
;
579 snd_pcm_hw_params_t
* hw_params
;
580 snd_pcm_sw_params_t
* sw_params
;
581 snd_pcm_access_t access
;
582 snd_pcm_format_t format
= -1;
584 unsigned int buffer_time
= 120000;
585 unsigned int period_time
= 22000;
586 snd_pcm_uframes_t buffer_size
;
587 snd_pcm_uframes_t period_size
;
593 TRACE("(%u, %p, %08X);\n", wDevID
, lpDesc
, dwFlags
);
594 if (lpDesc
== NULL
) {
595 WARN("Invalid Parameter !\n");
596 return MMSYSERR_INVALPARAM
;
598 if (wDevID
>= ALSA_WodNumDevs
) {
599 TRACE("Asked for device %d, but only %d known!\n", wDevID
, ALSA_WodNumDevs
);
600 return MMSYSERR_BADDEVICEID
;
603 /* only PCM format is supported so far... */
604 if (!ALSA_supportedFormat(lpDesc
->lpFormat
)) {
605 WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
606 lpDesc
->lpFormat
->wFormatTag
, lpDesc
->lpFormat
->nChannels
,
607 lpDesc
->lpFormat
->nSamplesPerSec
);
608 return WAVERR_BADFORMAT
;
611 if (dwFlags
& WAVE_FORMAT_QUERY
) {
612 TRACE("Query format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
613 lpDesc
->lpFormat
->wFormatTag
, lpDesc
->lpFormat
->nChannels
,
614 lpDesc
->lpFormat
->nSamplesPerSec
);
615 return MMSYSERR_NOERROR
;
618 wwo
= &WOutDev
[wDevID
];
620 if (wwo
->pcm
!= NULL
) {
621 WARN("%d already allocated\n", wDevID
);
622 return MMSYSERR_ALLOCATED
;
625 if (dwFlags
& WAVE_DIRECTSOUND
)
626 FIXME("Why are we called with DirectSound flag? It doesn't use MMSYSTEM any more\n");
627 /* not supported, ignore it */
628 dwFlags
&= ~WAVE_DIRECTSOUND
;
630 flags
= SND_PCM_NONBLOCK
;
632 if ( (err
= snd_pcm_open(&pcm
, wwo
->pcmname
, SND_PCM_STREAM_PLAYBACK
, flags
)) < 0)
634 ERR("Error open: %s\n", snd_strerror(err
));
635 return MMSYSERR_NOTENABLED
;
640 err
= snd_hctl_open(&hctl
, wwo
->ctlname
, 0);
647 WARN("Could not open hctl for [%s]: %s\n", wwo
->ctlname
, snd_strerror(err
));
652 wwo
->wFlags
= HIWORD(dwFlags
& CALLBACK_TYPEMASK
);
654 wwo
->waveDesc
= *lpDesc
;
655 ALSA_copyFormat(lpDesc
->lpFormat
, &wwo
->format
);
657 TRACE("Requested this format: %dx%dx%d %s\n",
658 wwo
->format
.Format
.nSamplesPerSec
,
659 wwo
->format
.Format
.wBitsPerSample
,
660 wwo
->format
.Format
.nChannels
,
661 ALSA_getFormat(wwo
->format
.Format
.wFormatTag
));
663 if (wwo
->format
.Format
.wBitsPerSample
== 0) {
664 WARN("Resetting zeroed wBitsPerSample\n");
665 wwo
->format
.Format
.wBitsPerSample
= 8 *
666 (wwo
->format
.Format
.nAvgBytesPerSec
/
667 wwo
->format
.Format
.nSamplesPerSec
) /
668 wwo
->format
.Format
.nChannels
;
671 #define EXIT_ON_ERROR(f,e,txt) do \
674 if ( (err = (f) ) < 0) \
676 WARN(txt ": %s\n", snd_strerror(err)); \
682 sw_params
= HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY
, snd_pcm_sw_params_sizeof() );
683 hw_params
= HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY
, snd_pcm_hw_params_sizeof() );
684 if (!hw_params
|| !sw_params
)
686 retcode
= MMSYSERR_NOMEM
;
689 snd_pcm_hw_params_any(pcm
, hw_params
);
691 access
= SND_PCM_ACCESS_MMAP_INTERLEAVED
;
692 if ( ( err
= snd_pcm_hw_params_set_access(pcm
, hw_params
, access
) ) < 0) {
693 WARN("mmap not available. switching to standard write.\n");
694 access
= SND_PCM_ACCESS_RW_INTERLEAVED
;
695 EXIT_ON_ERROR( snd_pcm_hw_params_set_access(pcm
, hw_params
, access
), MMSYSERR_INVALPARAM
, "unable to set access for playback");
696 wwo
->write
= snd_pcm_writei
;
699 wwo
->write
= snd_pcm_mmap_writei
;
701 if ((err
= snd_pcm_hw_params_set_channels(pcm
, hw_params
, wwo
->format
.Format
.nChannels
)) < 0) {
702 WARN("unable to set required channels: %d\n", wwo
->format
.Format
.nChannels
);
703 EXIT_ON_ERROR( snd_pcm_hw_params_set_channels(pcm
, hw_params
, wwo
->format
.Format
.nChannels
), WAVERR_BADFORMAT
, "unable to set required channels" );
706 if ((wwo
->format
.Format
.wFormatTag
== WAVE_FORMAT_PCM
) ||
707 ((wwo
->format
.Format
.wFormatTag
== WAVE_FORMAT_EXTENSIBLE
) &&
708 IsEqualGUID(&wwo
->format
.SubFormat
, &KSDATAFORMAT_SUBTYPE_PCM
))) {
709 format
= (wwo
->format
.Format
.wBitsPerSample
== 8) ? SND_PCM_FORMAT_U8
:
710 (wwo
->format
.Format
.wBitsPerSample
== 16) ? SND_PCM_FORMAT_S16_LE
:
711 (wwo
->format
.Format
.wBitsPerSample
== 24) ? SND_PCM_FORMAT_S24_3LE
:
712 (wwo
->format
.Format
.wBitsPerSample
== 32) ? SND_PCM_FORMAT_S32_LE
: -1;
713 } else if ((wwo
->format
.Format
.wFormatTag
== WAVE_FORMAT_EXTENSIBLE
) &&
714 IsEqualGUID(&wwo
->format
.SubFormat
, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
)){
715 format
= (wwo
->format
.Format
.wBitsPerSample
== 32) ? SND_PCM_FORMAT_FLOAT_LE
: -1;
717 ERR("invalid format: %0x04x\n", wwo
->format
.Format
.wFormatTag
);
718 retcode
= WAVERR_BADFORMAT
;
722 if ((err
= snd_pcm_hw_params_set_format(pcm
, hw_params
, format
)) < 0) {
723 WARN("unable to set required format: %s\n", snd_pcm_format_name(format
));
724 EXIT_ON_ERROR( snd_pcm_hw_params_set_format(pcm
, hw_params
, format
), WAVERR_BADFORMAT
, "unable to set required format" );
727 rate
= wwo
->format
.Format
.nSamplesPerSec
;
729 err
= snd_pcm_hw_params_set_rate_near(pcm
, hw_params
, &rate
, &dir
);
731 WARN("Rate %d Hz not available for playback: %s\n", wwo
->format
.Format
.nSamplesPerSec
, snd_strerror(rate
));
732 retcode
= WAVERR_BADFORMAT
;
735 if (!ALSA_NearMatch(rate
, wwo
->format
.Format
.nSamplesPerSec
)) {
736 WARN("Rate doesn't match (requested %d Hz, got %d Hz)\n", wwo
->format
.Format
.nSamplesPerSec
, rate
);
737 retcode
= WAVERR_BADFORMAT
;
741 TRACE("Got this format: %dx%dx%d %s\n",
742 wwo
->format
.Format
.nSamplesPerSec
,
743 wwo
->format
.Format
.wBitsPerSample
,
744 wwo
->format
.Format
.nChannels
,
745 ALSA_getFormat(wwo
->format
.Format
.wFormatTag
));
748 EXIT_ON_ERROR( snd_pcm_hw_params_set_buffer_time_near(pcm
, hw_params
, &buffer_time
, &dir
), MMSYSERR_INVALPARAM
, "unable to set buffer time");
750 EXIT_ON_ERROR( snd_pcm_hw_params_set_period_time_near(pcm
, hw_params
, &period_time
, &dir
), MMSYSERR_INVALPARAM
, "unable to set period time");
752 EXIT_ON_ERROR( snd_pcm_hw_params(pcm
, hw_params
), MMSYSERR_INVALPARAM
, "unable to set hw params for playback");
754 err
= snd_pcm_hw_params_get_period_size(hw_params
, &period_size
, &dir
);
755 err
= snd_pcm_hw_params_get_buffer_size(hw_params
, &buffer_size
);
757 snd_pcm_sw_params_current(pcm
, sw_params
);
759 EXIT_ON_ERROR( snd_pcm_sw_params_set_start_threshold(pcm
, sw_params
, 1), MMSYSERR_ERROR
, "unable to set start threshold");
760 EXIT_ON_ERROR( snd_pcm_sw_params_set_silence_size(pcm
, sw_params
, 0), MMSYSERR_ERROR
, "unable to set silence size");
761 EXIT_ON_ERROR( snd_pcm_sw_params_set_avail_min(pcm
, sw_params
, period_size
), MMSYSERR_ERROR
, "unable to set avail min");
762 EXIT_ON_ERROR( snd_pcm_sw_params_set_silence_threshold(pcm
, sw_params
, 0), MMSYSERR_ERROR
, "unable to set silence threshold");
763 EXIT_ON_ERROR( snd_pcm_sw_params(pcm
, sw_params
), MMSYSERR_ERROR
, "unable to set sw params for playback");
766 snd_pcm_prepare(pcm
);
769 ALSA_TraceParameters(hw_params
, sw_params
, FALSE
);
771 /* now, we can save all required data for later use... */
773 wwo
->dwBufferSize
= snd_pcm_frames_to_bytes(pcm
, buffer_size
);
774 wwo
->lpQueuePtr
= wwo
->lpPlayPtr
= wwo
->lpLoopPtr
= NULL
;
775 wwo
->dwPlayedTotal
= wwo
->dwWrittenTotal
= 0;
776 wwo
->dwPartialOffset
= 0;
778 ALSA_InitRingMessage(&wwo
->msgRing
);
780 wwo
->hStartUpEvent
= CreateEventW(NULL
, FALSE
, FALSE
, NULL
);
781 wwo
->hThread
= CreateThread(NULL
, 0, wodPlayer
, (LPVOID
)(DWORD_PTR
)wDevID
, 0, &(wwo
->dwThreadID
));
783 ERR("Thread creation for the wodPlayer failed!\n");
784 CloseHandle(wwo
->hStartUpEvent
);
785 retcode
= MMSYSERR_NOMEM
;
788 SetThreadPriority(wwo
->hThread
, THREAD_PRIORITY_TIME_CRITICAL
);
789 WaitForSingleObject(wwo
->hStartUpEvent
, INFINITE
);
790 CloseHandle(wwo
->hStartUpEvent
);
791 wwo
->hStartUpEvent
= NULL
;
793 TRACE("handle=%p\n", pcm
);
794 TRACE("wBitsPerSample=%u, nAvgBytesPerSec=%u, nSamplesPerSec=%u, nChannels=%u nBlockAlign=%u!\n",
795 wwo
->format
.Format
.wBitsPerSample
, wwo
->format
.Format
.nAvgBytesPerSec
,
796 wwo
->format
.Format
.nSamplesPerSec
, wwo
->format
.Format
.nChannels
,
797 wwo
->format
.Format
.nBlockAlign
);
799 HeapFree( GetProcessHeap(), 0, sw_params
);
800 wwo
->hw_params
= hw_params
;
804 wodNotifyClient(wwo
, WOM_OPEN
, 0L, 0L);
805 return MMSYSERR_NOERROR
;
814 snd_hctl_close(hctl
);
817 HeapFree( GetProcessHeap(), 0, hw_params
);
818 HeapFree( GetProcessHeap(), 0, sw_params
);
819 if (wwo
->msgRing
.ring_buffer_size
> 0)
820 ALSA_DestroyRingMessage(&wwo
->msgRing
);
826 /**************************************************************************
827 * wodClose [internal]
829 static DWORD
wodClose(WORD wDevID
)
833 TRACE("(%u);\n", wDevID
);
835 if (wDevID
>= ALSA_WodNumDevs
) {
836 TRACE("Asked for device %d, but only %d known!\n", wDevID
, ALSA_WodNumDevs
);
837 return MMSYSERR_BADDEVICEID
;
840 wwo
= &WOutDev
[wDevID
];
841 if (wwo
->pcm
== NULL
) {
842 WARN("Requested to close already closed device %d!\n", wDevID
);
843 return MMSYSERR_BADDEVICEID
;
846 if (wwo
->lpQueuePtr
) {
847 WARN("buffers still playing !\n");
848 return WAVERR_STILLPLAYING
;
851 ALSA_AddRingMessage(&wwo
->msgRing
, WINE_WM_CLOSING
, 0, TRUE
);
853 ALSA_DestroyRingMessage(&wwo
->msgRing
);
855 HeapFree( GetProcessHeap(), 0, wwo
->hw_params
);
856 wwo
->hw_params
= NULL
;
860 snd_hctl_free(wwo
->hctl
);
861 snd_hctl_close(wwo
->hctl
);
865 snd_pcm_close(wwo
->pcm
);
868 wodNotifyClient(wwo
, WOM_CLOSE
, 0L, 0L);
871 return MMSYSERR_NOERROR
;
875 /**************************************************************************
876 * wodWrite [internal]
879 static DWORD
wodWrite(WORD wDevID
, LPWAVEHDR lpWaveHdr
, DWORD dwSize
)
881 TRACE("(%u, %p, %08X);\n", wDevID
, lpWaveHdr
, dwSize
);
883 if (wDevID
>= ALSA_WodNumDevs
) {
884 TRACE("Asked for device %d, but only %d known!\n", wDevID
, ALSA_WodNumDevs
);
885 return MMSYSERR_BADDEVICEID
;
888 if (WOutDev
[wDevID
].pcm
== NULL
) {
889 WARN("Requested to write to closed device %d!\n", wDevID
);
890 return MMSYSERR_BADDEVICEID
;
893 if (lpWaveHdr
->lpData
== NULL
|| !(lpWaveHdr
->dwFlags
& WHDR_PREPARED
))
894 return WAVERR_UNPREPARED
;
896 if (lpWaveHdr
->dwFlags
& WHDR_INQUEUE
)
897 return WAVERR_STILLPLAYING
;
899 lpWaveHdr
->dwFlags
&= ~WHDR_DONE
;
900 lpWaveHdr
->dwFlags
|= WHDR_INQUEUE
;
901 lpWaveHdr
->lpNext
= 0;
903 ALSA_AddRingMessage(&WOutDev
[wDevID
].msgRing
, WINE_WM_HEADER
, (DWORD_PTR
)lpWaveHdr
, FALSE
);
905 return MMSYSERR_NOERROR
;
908 /**************************************************************************
909 * wodPause [internal]
911 static DWORD
wodPause(WORD wDevID
)
913 TRACE("(%u);!\n", wDevID
);
915 if (wDevID
>= ALSA_WodNumDevs
) {
916 TRACE("Asked for device %d, but only %d known!\n", wDevID
, ALSA_WodNumDevs
);
917 return MMSYSERR_BADDEVICEID
;
920 if (WOutDev
[wDevID
].pcm
== NULL
) {
921 WARN("Requested to pause closed device %d!\n", wDevID
);
922 return MMSYSERR_BADDEVICEID
;
925 ALSA_AddRingMessage(&WOutDev
[wDevID
].msgRing
, WINE_WM_PAUSING
, 0, TRUE
);
927 return MMSYSERR_NOERROR
;
930 /**************************************************************************
931 * wodRestart [internal]
933 static DWORD
wodRestart(WORD wDevID
)
935 TRACE("(%u);\n", wDevID
);
937 if (wDevID
>= ALSA_WodNumDevs
) {
938 TRACE("Asked for device %d, but only %d known!\n", wDevID
, ALSA_WodNumDevs
);
939 return MMSYSERR_BADDEVICEID
;
942 if (WOutDev
[wDevID
].pcm
== NULL
) {
943 WARN("Requested to restart closed device %d!\n", wDevID
);
944 return MMSYSERR_BADDEVICEID
;
947 if (WOutDev
[wDevID
].state
== WINE_WS_PAUSED
) {
948 ALSA_AddRingMessage(&WOutDev
[wDevID
].msgRing
, WINE_WM_RESTARTING
, 0, TRUE
);
951 /* FIXME: is NotifyClient with WOM_DONE right ? (Comet Busters 1.3.3 needs this notification) */
952 /* FIXME: Myst crashes with this ... hmm -MM
953 return wodNotifyClient(wwo, WOM_DONE, 0L, 0L);
956 return MMSYSERR_NOERROR
;
959 /**************************************************************************
960 * wodReset [internal]
962 static DWORD
wodReset(WORD wDevID
)
964 TRACE("(%u);\n", wDevID
);
966 if (wDevID
>= ALSA_WodNumDevs
) {
967 TRACE("Asked for device %d, but only %d known!\n", wDevID
, ALSA_WodNumDevs
);
968 return MMSYSERR_BADDEVICEID
;
971 if (WOutDev
[wDevID
].pcm
== NULL
) {
972 WARN("Requested to reset closed device %d!\n", wDevID
);
973 return MMSYSERR_BADDEVICEID
;
976 ALSA_AddRingMessage(&WOutDev
[wDevID
].msgRing
, WINE_WM_RESETTING
, 0, TRUE
);
978 return MMSYSERR_NOERROR
;
981 /**************************************************************************
982 * wodGetPosition [internal]
984 static DWORD
wodGetPosition(WORD wDevID
, LPMMTIME lpTime
, DWORD uSize
)
988 TRACE("(%u, %p, %u);\n", wDevID
, lpTime
, uSize
);
990 if (wDevID
>= ALSA_WodNumDevs
) {
991 TRACE("Asked for device %d, but only %d known!\n", wDevID
, ALSA_WodNumDevs
);
992 return MMSYSERR_BADDEVICEID
;
995 if (WOutDev
[wDevID
].pcm
== NULL
) {
996 WARN("Requested to get position of closed device %d!\n", wDevID
);
997 return MMSYSERR_BADDEVICEID
;
1000 if (lpTime
== NULL
) return MMSYSERR_INVALPARAM
;
1002 wwo
= &WOutDev
[wDevID
];
1003 return ALSA_bytes_to_mmtime(lpTime
, wwo
->dwPlayedTotal
, &wwo
->format
);
1006 /**************************************************************************
1007 * wodBreakLoop [internal]
1009 static DWORD
wodBreakLoop(WORD wDevID
)
1011 TRACE("(%u);\n", wDevID
);
1013 if (wDevID
>= ALSA_WodNumDevs
) {
1014 TRACE("Asked for device %d, but only %d known!\n", wDevID
, ALSA_WodNumDevs
);
1015 return MMSYSERR_BADDEVICEID
;
1018 if (WOutDev
[wDevID
].pcm
== NULL
) {
1019 WARN("Requested to breakloop of closed device %d!\n", wDevID
);
1020 return MMSYSERR_BADDEVICEID
;
1023 ALSA_AddRingMessage(&WOutDev
[wDevID
].msgRing
, WINE_WM_BREAKLOOP
, 0, TRUE
);
1024 return MMSYSERR_NOERROR
;
1027 /**************************************************************************
1028 * wodGetVolume [internal]
1030 static DWORD
wodGetVolume(WORD wDevID
, LPDWORD lpdwVol
)
1038 TRACE("(%u, %p);\n", wDevID
, lpdwVol
);
1039 if (wDevID
>= ALSA_WodNumDevs
) {
1040 TRACE("Asked for device %d, but only %d known!\n", wDevID
, ALSA_WodNumDevs
);
1041 return MMSYSERR_BADDEVICEID
;
1044 if (lpdwVol
== NULL
)
1045 return MMSYSERR_NOTENABLED
;
1047 wwo
= &WOutDev
[wDevID
];
1049 rc
= ALSA_CheckSetVolume(wwo
->hctl
, &left
, &right
, &min
, &max
, NULL
, NULL
, NULL
);
1050 if (rc
== MMSYSERR_NOERROR
)
1052 #define VOLUME_ALSA_TO_WIN(x) ( ( (((x)-min) * 65535) + (max-min)/2 ) /(max-min))
1053 wleft
= VOLUME_ALSA_TO_WIN(left
);
1054 wright
= VOLUME_ALSA_TO_WIN(right
);
1055 #undef VOLUME_ALSA_TO_WIN
1056 TRACE("left=%d,right=%d,converted to windows left %d, right %d\n", left
, right
, wleft
, wright
);
1057 *lpdwVol
= MAKELONG( wleft
, wright
);
1060 TRACE("CheckSetVolume failed; rc %d\n", rc
);
1065 /**************************************************************************
1066 * wodSetVolume [internal]
1068 static DWORD
wodSetVolume(WORD wDevID
, DWORD dwParam
)
1076 TRACE("(%u, %08X);\n", wDevID
, dwParam
);
1077 if (wDevID
>= ALSA_WodNumDevs
) {
1078 TRACE("Asked for device %d, but only %d known!\n", wDevID
, ALSA_WodNumDevs
);
1079 return MMSYSERR_BADDEVICEID
;
1082 wwo
= &WOutDev
[wDevID
];
1084 rc
= ALSA_CheckSetVolume(wwo
->hctl
, NULL
, NULL
, &min
, &max
, NULL
, NULL
, NULL
);
1085 if (rc
== MMSYSERR_NOERROR
)
1087 wleft
= LOWORD(dwParam
);
1088 wright
= HIWORD(dwParam
);
1089 #define VOLUME_WIN_TO_ALSA(x) ( ( ( ((x) * (max-min)) + 32767) / 65535) + min )
1090 left
= VOLUME_WIN_TO_ALSA(wleft
);
1091 right
= VOLUME_WIN_TO_ALSA(wright
);
1092 #undef VOLUME_WIN_TO_ALSA
1093 rc
= ALSA_CheckSetVolume(wwo
->hctl
, NULL
, NULL
, NULL
, NULL
, NULL
, &left
, &right
);
1094 if (rc
== MMSYSERR_NOERROR
)
1095 TRACE("set volume: wleft=%d, wright=%d, converted to alsa left %d, right %d\n", wleft
, wright
, left
, right
);
1097 TRACE("SetVolume failed; rc %d\n", rc
);
1103 /**************************************************************************
1104 * wodGetNumDevs [internal]
1106 static DWORD
wodGetNumDevs(void)
1108 return ALSA_WodNumDevs
;
1111 /**************************************************************************
1112 * wodDevInterfaceSize [internal]
1114 static DWORD
wodDevInterfaceSize(UINT wDevID
, LPDWORD dwParam1
)
1116 TRACE("(%u, %p)\n", wDevID
, dwParam1
);
1118 *dwParam1
= MultiByteToWideChar(CP_UNIXCP
, 0, WOutDev
[wDevID
].interface_name
, -1,
1119 NULL
, 0 ) * sizeof(WCHAR
);
1120 return MMSYSERR_NOERROR
;
1123 /**************************************************************************
1124 * wodDevInterface [internal]
1126 static DWORD
wodDevInterface(UINT wDevID
, PWCHAR dwParam1
, DWORD dwParam2
)
1128 if (dwParam2
>= MultiByteToWideChar(CP_UNIXCP
, 0, WOutDev
[wDevID
].interface_name
, -1,
1129 NULL
, 0 ) * sizeof(WCHAR
))
1131 MultiByteToWideChar(CP_UNIXCP
, 0, WOutDev
[wDevID
].interface_name
, -1,
1132 dwParam1
, dwParam2
/ sizeof(WCHAR
));
1133 return MMSYSERR_NOERROR
;
1135 return MMSYSERR_INVALPARAM
;
1138 /**************************************************************************
1139 * wodMessage (WINEALSA.@)
1141 DWORD WINAPI
ALSA_wodMessage(UINT wDevID
, UINT wMsg
, DWORD_PTR dwUser
,
1142 DWORD_PTR dwParam1
, DWORD_PTR dwParam2
)
1144 TRACE("(%u, %s, %08lX, %08lX, %08lX);\n",
1145 wDevID
, ALSA_getMessage(wMsg
), dwUser
, dwParam1
, dwParam2
);
1153 /* FIXME: Pretend this is supported */
1155 case WODM_OPEN
: return wodOpen (wDevID
, (LPWAVEOPENDESC
)dwParam1
, dwParam2
);
1156 case WODM_CLOSE
: return wodClose (wDevID
);
1157 case WODM_GETDEVCAPS
: return wodGetDevCaps (wDevID
, (LPWAVEOUTCAPSW
)dwParam1
, dwParam2
);
1158 case WODM_GETNUMDEVS
: return wodGetNumDevs ();
1159 case WODM_GETPITCH
: return MMSYSERR_NOTSUPPORTED
;
1160 case WODM_SETPITCH
: return MMSYSERR_NOTSUPPORTED
;
1161 case WODM_GETPLAYBACKRATE
: return MMSYSERR_NOTSUPPORTED
;
1162 case WODM_SETPLAYBACKRATE
: return MMSYSERR_NOTSUPPORTED
;
1163 case WODM_WRITE
: return wodWrite (wDevID
, (LPWAVEHDR
)dwParam1
, dwParam2
);
1164 case WODM_PAUSE
: return wodPause (wDevID
);
1165 case WODM_GETPOS
: return wodGetPosition (wDevID
, (LPMMTIME
)dwParam1
, dwParam2
);
1166 case WODM_BREAKLOOP
: return wodBreakLoop (wDevID
);
1167 case WODM_PREPARE
: return MMSYSERR_NOTSUPPORTED
;
1168 case WODM_UNPREPARE
: return MMSYSERR_NOTSUPPORTED
;
1169 case WODM_GETVOLUME
: return wodGetVolume (wDevID
, (LPDWORD
)dwParam1
);
1170 case WODM_SETVOLUME
: return wodSetVolume (wDevID
, dwParam1
);
1171 case WODM_RESTART
: return wodRestart (wDevID
);
1172 case WODM_RESET
: return wodReset (wDevID
);
1173 case DRV_QUERYDEVICEINTERFACESIZE
: return wodDevInterfaceSize (wDevID
, (LPDWORD
)dwParam1
);
1174 case DRV_QUERYDEVICEINTERFACE
: return wodDevInterface (wDevID
, (PWCHAR
)dwParam1
, dwParam2
);
1175 case DRV_QUERYDSOUNDIFACE
: return wodDsCreate (wDevID
, (PIDSDRIVER
*)dwParam1
);
1176 case DRV_QUERYDSOUNDDESC
: return wodDsDesc (wDevID
, (PDSDRIVERDESC
)dwParam1
);
1179 FIXME("unknown message %d!\n", wMsg
);
1181 return MMSYSERR_NOTSUPPORTED
;