Commit 78bae032 authored by Simon Britnell's avatar Simon Britnell Committed by Alexandre Julliard

Cleaned up the wodPlayer functionality to make it easier to follow.

Changed notification to be calculated from bytes played rather than time elapsed.
parent 1160c58c
......@@ -43,6 +43,8 @@ DEFAULT_DEBUG_CHANNEL(wave);
/* Allow 1% deviation for sample rates (some ES137x cards) */
#define NEAR_MATCH(rate1,rate2) (((100*((int)(rate1)-(int)(rate2)))/(rate1))==0)
#define REFILL_BUFFER_WHEN 3/4 /* fill the buffer when it's 3/4 empty */
#ifdef HAVE_OSS
#define SOUND_DEV "/dev/dsp"
......@@ -93,20 +95,31 @@ typedef struct {
int unixdev;
volatile int state; /* one of the WINE_WS_ manifest constants */
DWORD dwFragmentSize; /* size of OSS buffer fragment */
DWORD dwBufferSize; /* size of whole OSS buffer in bytes
* used to compute dwPlayedTotal from dwWrittenTotal and
* ioctl GETOSPACE info
*/
WORD uWaitForFragments; /* The number of OSS buffer fragments we would like to be free
* before trying to write to the DSP
*/
DWORD dwMillisPerFragment; /* The number of milliseconds of sound in each OSS buffer
* fragment
*/
WAVEOPENDESC waveDesc;
WORD wFlags;
PCMWAVEFORMAT format;
LPWAVEHDR lpQueuePtr; /* start of queued WAVEHDRs (waiting to be notified) */
LPWAVEHDR lpPlayPtr; /* start of not yet fully played buffers */
/* info on current lpPlayPtr->lpWaveHdr */
LPSTR lpPartialData; /* Data still to write on current wavehdr */
DWORD dwPartialBytes; /* number of bytes to write to end current wavehdr */
LPWAVEHDR lpLoopPtr; /* pointer of first buffer in loop, if any */
DWORD dwLoops; /* private copy of loop counter */
DWORD dwLastFragDone; /* time in ms, when last played fragment will be actually played */
DWORD dwPlayedTotal; /* number of bytes played since opening */
/* info on current lpQueueHdr->lpWaveHdr */
DWORD dwOffCurrHdr; /* offset in lpPlayPtr->lpData for fragments */
DWORD dwRemain; /* number of bytes to write to end the current fragment */
DWORD dwWrittenTotal; /* number of bytes written since opening */
/* synchronization stuff */
HANDLE hThread;
......@@ -147,6 +160,14 @@ static WINE_WAVEOUT WOutDev [MAX_WAVEOUTDRV];
static WINE_WAVEIN WInDev [MAX_WAVEINDRV ];
static DWORD wodDsCreate(UINT wDevID, PIDSDRIVER* drv);
/* These strings used only for tracing */
static const char *wodPlayerCmdString[] = {
"WINE_WM_PAUSING",
"WINE_WM_RESTARTING",
"WINE_WM_RESETTING",
"WINE_WM_CLOSING",
"WINE_WM_HEADER" };
/*======================================================================*
* Low level WAVE implementation *
......@@ -405,120 +426,151 @@ static DWORD OSS_NotifyClient(UINT wDevID, WORD wMsg, DWORD dwParam1, DWORD dwPa
*======================================================================*/
/**************************************************************************
* wodPlayer_WriteFragments [internal]
* updatePlayedTotal [internal]
*
* wodPlayer helper. Writes as many fragments as it can to unixdev.
* Returns TRUE in case of buffer underrun.
* Calculates wwo->dwPlayed total from wwo->dwWrittenTotal and the amount
* still remaining in the OSS buffer.
*/
static BOOL wodPlayer_WriteFragments(WINE_WAVEOUT* wwo)
static DWORD updatePlayedTotal( WINE_WAVEOUT* wwo )
{
LPWAVEHDR lpWaveHdr;
LPBYTE lpData;
int count;
audio_buf_info info;
for (;;) {
if (ioctl(wwo->unixdev, SNDCTL_DSP_GETOSPACE, &info) < 0) {
ERR("ioctl failed (%s)\n", strerror(errno));
return FALSE;
}
TRACE("info={frag=%d fsize=%d ftotal=%d bytes=%d}\n", info.fragments, info.fragsize, info.fragstotal, info.bytes);
if (!info.fragments) /* output queue is full, wait a bit */
return FALSE;
lpWaveHdr = wwo->lpPlayPtr;
if (!lpWaveHdr) {
if (wwo->dwRemain > 0 && /* still data to send to complete current fragment */
wwo->dwLastFragDone && /* first fragment has been played */
info.fragments + 2 > info.fragstotal) { /* done with all waveOutWrite()' fragments */
/* FIXME: should do better handling here */
WARN("Oooch, buffer underrun !\n");
return TRUE; /* force resetting of waveOut device */
}
return FALSE; /* wait a bit */
if (ioctl(wwo->unixdev, SNDCTL_DSP_GETOSPACE, &info) < 0) {
ERR("ioctl failed (%s)\n", strerror(errno));
return wwo->dwPlayedTotal;
}
wwo->dwPlayedTotal=wwo->dwWrittenTotal-( wwo->dwBufferSize - info.bytes );
return wwo->dwPlayedTotal;
}
/**************************************************************************
* wodPlayer_BeginWaveHdr [internal]
*
* Makes the specified lpWaveHdr the currently playing wave header.
* If the specified wave header is a begin loop and we're not already in
* a loop, setup the loop.
*/
static void wodPlayer_BeginWaveHdr( WINE_WAVEOUT* wwo,
LPWAVEHDR lpWaveHdr )
{
wwo->lpPlayPtr=lpWaveHdr;
if( !lpWaveHdr ) return;
if (lpWaveHdr->dwFlags & WHDR_BEGINLOOP) {
if (wwo->lpLoopPtr) {
WARN("Already in a loop. Discarding loop on this header (%p)\n", lpWaveHdr);
} else {
TRACE("Starting loop (%ldx) with %p\n", lpWaveHdr->dwLoops, lpWaveHdr);
wwo->lpLoopPtr = lpWaveHdr;
/* Windows does not touch WAVEHDR.dwLoops,
* so we need to make an internal copy */
wwo->dwLoops = lpWaveHdr->dwLoops;
}
if (wwo->dwOffCurrHdr == 0) {
TRACE("Starting a new wavehdr %p of %ld bytes\n", lpWaveHdr, lpWaveHdr->dwBufferLength);
if (lpWaveHdr->dwFlags & WHDR_BEGINLOOP) {
if (wwo->lpLoopPtr) {
WARN("Already in a loop. Discarding loop on this header (%p)\n", lpWaveHdr);
} else {
TRACE("Starting loop (%ldx) with %p\n", lpWaveHdr->dwLoops, lpWaveHdr);
wwo->lpLoopPtr = lpWaveHdr;
/* Windows does not touch WAVEHDR.dwLoops,
* so we need to make an internal copy */
wwo->dwLoops = lpWaveHdr->dwLoops;
}
}
}
/**************************************************************************
* wodPlayer_PlayPtrNext [internal]
*
* Advance the play pointer to the next waveheader, looping if required.
*/
static LPWAVEHDR wodPlayer_PlayPtrNext(WINE_WAVEOUT* wwo)
{
LPWAVEHDR lpWaveHdr=wwo->lpPlayPtr;
if ((lpWaveHdr->dwFlags & WHDR_ENDLOOP) && wwo->lpLoopPtr) {
/* We're at the end of a loop, loop if required */
if (--wwo->dwLoops > 0) {
wwo->lpPlayPtr = wwo->lpLoopPtr;
} else {
/* Handle overlapping loops correctly */
if (wwo->lpLoopPtr != lpWaveHdr && (lpWaveHdr->dwFlags & WHDR_BEGINLOOP)) {
FIXME("Correctly handled case ? (ending loop buffer also starts a new loop)\n");
/* shall we consider the END flag for the closing loop or for
* the opening one or for both ???
* code assumes for closing loop only
*/
wwo->lpLoopPtr = NULL;
wodPlayer_BeginWaveHdr( wwo, lpWaveHdr );
} else {
wwo->lpLoopPtr = NULL;
wodPlayer_BeginWaveHdr( wwo, lpWaveHdr=lpWaveHdr->lpNext );
}
}
lpData = lpWaveHdr->lpData;
} else {
/* We're not in a loop. Advance to the next wave header */
wodPlayer_BeginWaveHdr( wwo, lpWaveHdr=lpWaveHdr->lpNext );
}
return lpWaveHdr;
}
/* finish current wave hdr ? */
if (wwo->dwOffCurrHdr + wwo->dwRemain >= lpWaveHdr->dwBufferLength) {
DWORD toWrite = lpWaveHdr->dwBufferLength - wwo->dwOffCurrHdr;
/* write end of current wave hdr */
count = write(wwo->unixdev, lpData + wwo->dwOffCurrHdr, toWrite);
TRACE("write(%p[%5lu], %5lu) => %d\n", lpData, wwo->dwOffCurrHdr, toWrite, count);
if (count > 0 || toWrite == 0) {
DWORD tc = GetTickCount();
if (wwo->dwLastFragDone /* + guard time ?? */ < tc)
wwo->dwLastFragDone = tc;
wwo->dwLastFragDone += (toWrite * 1000) / wwo->format.wf.nAvgBytesPerSec;
lpWaveHdr->reserved = wwo->dwLastFragDone;
TRACE("Tagging hdr %p with %08lx\n", lpWaveHdr, wwo->dwLastFragDone);
/* WAVEHDR written, go to next one */
if ((lpWaveHdr->dwFlags & WHDR_ENDLOOP) && wwo->lpLoopPtr) {
if (--wwo->dwLoops > 0) {
wwo->lpPlayPtr = wwo->lpLoopPtr;
} else {
/* last one played */
if (wwo->lpLoopPtr != lpWaveHdr && (lpWaveHdr->dwFlags & WHDR_BEGINLOOP)) {
FIXME("Correctly handled case ? (ending loop buffer also starts a new loop)\n");
/* shall we consider the END flag for the closing loop or for
* the opening one or for both ???
* code assumes for closing loop only
*/
wwo->lpLoopPtr = lpWaveHdr;
} else {
wwo->lpLoopPtr = NULL;
}
wwo->lpPlayPtr = lpWaveHdr->lpNext;
}
} else {
wwo->lpPlayPtr = lpWaveHdr->lpNext;
}
wwo->dwOffCurrHdr = 0;
if ((wwo->dwRemain -= count) == 0) {
wwo->dwRemain = wwo->dwFragmentSize;
}
}
continue; /* try to go to use next wavehdr */
} else {
count = write(wwo->unixdev, lpData + wwo->dwOffCurrHdr, wwo->dwRemain);
TRACE("write(%p[%5lu], %5lu) => %d\n", lpData, wwo->dwOffCurrHdr, wwo->dwRemain, count);
if (count > 0) {
DWORD tc = GetTickCount();
/**************************************************************************
* wodPlayer_DSPWait [internal]
* Returns the number of milliseconds to wait for the DSP buffer to clear.
* This is based on the number of fragments we want to be clear before
* writing and the number of free fragments we already have.
*/
static DWORD wodPlayer_DSPWait( WINE_WAVEOUT *wwo, WORD uFreeFragments )
{
return (uFreeFragments>wwo->uWaitForFragments)?1:
wwo->dwMillisPerFragment*
(wwo->uWaitForFragments-uFreeFragments);
}
/**************************************************************************
* wodPlayer_NotifyWait [internal]
* Returns the number of milliseconds to wait before attempting to notify
* completion of the specified wavehdr.
* This is based on the number of bytes remaining to be written in the
* wave.
*/
static DWORD wodPlayer_NotifyWait( WINE_WAVEOUT *wwo, LPWAVEHDR lpWaveHdr )
{
DWORD dwMillis;
if (wwo->dwLastFragDone /* + guard time ?? */ < tc)
wwo->dwLastFragDone = tc;
wwo->dwLastFragDone += (wwo->dwRemain * 1000) / wwo->format.wf.nAvgBytesPerSec;
if( lpWaveHdr->reserved<wwo->dwPlayedTotal ) {
dwMillis=1;
} else {
dwMillis=(lpWaveHdr->reserved-wwo->dwPlayedTotal) *
wwo->dwMillisPerFragment / wwo->dwFragmentSize;
TRACE( "wait for %lu bytes = %lu\n",
(lpWaveHdr->reserved-wwo->dwPlayedTotal),
dwMillis );
if( dwMillis < 1 )
dwMillis=1;
}
TRACE("Tagging frag with %08lx\n", wwo->dwLastFragDone);
return dwMillis;
}
wwo->dwOffCurrHdr += count;
wwo->dwRemain = wwo->dwFragmentSize;
}
}
/**************************************************************************
* wodPlayer_WriteMaxFrags [internal]
* Writes the maximum number of bytes possible to the DSP and returns
* the number of bytes written. Also updates fragments in dspspace.
*/
static DWORD wodPlayer_WriteMaxFrags( WINE_WAVEOUT *wwo, LPSTR lpData,
DWORD dwLength,
audio_buf_info* dspspace )
{
/* Only attempt to write to free fragments */
int maxWrite=dspspace->fragments*dspspace->fragsize;
int toWrite=min(dwLength,maxWrite);
int written=write(wwo->unixdev, lpData, toWrite);
TRACE("wrote %d of %lu bytes\n",
written, dwLength );
if( written > 0 ) {
/* Keep a count of the total bytes written to the DSP */
wwo->dwWrittenTotal+=written;
/* reduce the number of free fragments */
dspspace->fragments -= (written/dspspace->fragsize)+(written%dspspace->fragsize>0);
}
return written;
}
......@@ -565,24 +617,29 @@ int wodPlayer_RetrieveMessage(WINE_WAVEOUT *wwo, int *msg, DWORD *param)
}
/**************************************************************************
* wodPlayer_Notify [internal]
* wodPlayer_NotifyCompletions [internal]
*
* wodPlayer helper. Notifies (and remove from queue) all the wavehdr which content
* have been played (actually to speaker, not to unixdev fd).
* Notifies and remove from queue all wavehdrs which have been played to
* the speaker (ie. they have cleared the OSS buffer). If force is true,
* we notify all wavehdrs and remove them all from the queue even if they
* are unplayed or part of a loop.
*/
static void wodPlayer_Notify(WINE_WAVEOUT* wwo, WORD uDevID, BOOL force)
static DWORD wodPlayer_NotifyCompletions( WINE_WAVEOUT* wwo, WORD uDevID, BOOL force)
{
LPWAVEHDR lpWaveHdr;
DWORD tc = GetTickCount();
while (wwo->lpQueuePtr &&
updatePlayedTotal(wwo);
/* Start from lpQueuePtr and keep notifying until:
* - we hit an unwritten wavehdr
* - we hit the beginning of a running loop
* - we hit a wavehdr which hasn't finished playing
*/
while( (lpWaveHdr=wwo->lpQueuePtr) &&
(force ||
(wwo->lpQueuePtr != wwo->lpPlayPtr && wwo->lpQueuePtr != wwo->lpLoopPtr))) {
lpWaveHdr = wwo->lpQueuePtr;
if (lpWaveHdr->reserved > tc && !force) break;
(lpWaveHdr != wwo->lpPlayPtr &&
lpWaveHdr != wwo->lpLoopPtr &&
lpWaveHdr->reserved <= wwo->dwPlayedTotal ))) {
wwo->dwPlayedTotal += lpWaveHdr->dwBufferLength;
wwo->lpQueuePtr = lpWaveHdr->lpNext;
lpWaveHdr->dwFlags &= ~WHDR_INQUEUE;
......@@ -593,6 +650,11 @@ static void wodPlayer_Notify(WINE_WAVEOUT* wwo, WORD uDevID, BOOL force)
WARN("can't notify client !\n");
}
}
return ( lpWaveHdr &&
lpWaveHdr != wwo->lpPlayPtr &&
lpWaveHdr != wwo->lpLoopPtr ) ?
wodPlayer_NotifyWait( wwo, lpWaveHdr ) :
0 ;
}
/**************************************************************************
......@@ -603,7 +665,7 @@ static void wodPlayer_Notify(WINE_WAVEOUT* wwo, WORD uDevID, BOOL force)
static void wodPlayer_Reset(WINE_WAVEOUT* wwo, WORD uDevID, BOOL reset)
{
/* updates current notify list */
wodPlayer_Notify(wwo, uDevID, FALSE);
wodPlayer_NotifyCompletions(wwo, uDevID, FALSE);
/* flush all possible output */
if (ioctl(wwo->unixdev, SNDCTL_DSP_RESET, 0) == -1) {
......@@ -613,15 +675,18 @@ static void wodPlayer_Reset(WINE_WAVEOUT* wwo, WORD uDevID, BOOL reset)
ExitThread(-1);
}
wwo->dwOffCurrHdr = 0;
wwo->dwRemain = wwo->dwFragmentSize;
/* Clear partial wavehdr */
wwo->dwPartialBytes=0;
wwo->lpPartialData=NULL;
if (reset) {
/* empty notify list */
wodPlayer_Notify(wwo, uDevID, TRUE);
wodPlayer_NotifyCompletions(wwo, uDevID, TRUE);
wwo->lpPlayPtr = wwo->lpQueuePtr = wwo->lpLoopPtr = NULL;
wwo->state = WINE_WS_STOPPED;
wwo->dwPlayedTotal = 0;
wwo->dwWrittenTotal = 0;
} else {
/* FIXME: this is not accurate when looping, but can be do better ? */
wwo->lpPlayPtr = (wwo->lpLoopPtr) ? wwo->lpLoopPtr : wwo->lpQueuePtr;
......@@ -629,119 +694,199 @@ static void wodPlayer_Reset(WINE_WAVEOUT* wwo, WORD uDevID, BOOL reset)
}
}
#define MIN_SLEEP_TIME 100 /* millis. NB: <100 millis appears instant to a
* human
*/
/**************************************************************************
* wodPlayer [internal]
* wodPlayer_AwaitEvent [internal]
* Wait for a command to be sent to the wodPlayer or for time to pass
* until wodPlayer needs to do something again.
*/
static DWORD CALLBACK wodPlayer(LPVOID pmt)
static void wodPlayer_AwaitEvent( WINE_WAVEOUT* wwo,
DWORD dwNextFeedTime,
DWORD dwNextNotifyTime )
{
WORD uDevID = (DWORD)pmt;
WINE_WAVEOUT* wwo = (WINE_WAVEOUT*)&WOutDev[uDevID];
WAVEHDR* lpWaveHdr;
DWORD dwSleepTime;
DWORD dwSleepTime;
/** Wait for the shortest time before an action is required. If there
* are no pending actions, wait forever for a command.
*/
if( dwNextFeedTime == 0 ) {
if( dwNextNotifyTime == 0 )
dwSleepTime=INFINITE;
else
dwSleepTime=dwNextNotifyTime;
} else {
if( dwNextNotifyTime == 0 )
dwSleepTime=dwNextFeedTime;
else
dwSleepTime=min( dwNextFeedTime, dwNextNotifyTime );
}
if( dwSleepTime != INFINITE && dwSleepTime < MIN_SLEEP_TIME )
dwSleepTime=MIN_SLEEP_TIME;
TRACE( "waiting %lu millis (%lu,%lu)\n", dwSleepTime,
dwNextFeedTime,dwNextNotifyTime );
WaitForSingleObject(wwo->msg_event, dwSleepTime);
TRACE( "wait returned\n");
}
/**************************************************************************
* wodPlayer_ProcessMessages [internal]
*/
static void wodPlayer_ProcessMessages( WINE_WAVEOUT* wwo, WORD uDevID )
{
LPWAVEHDR lpWaveHdr;
int msg;
DWORD param;
DWORD tc;
BOOL had_msg;
wwo->state = WINE_WS_STOPPED;
wwo->dwLastFragDone = 0;
wwo->dwOffCurrHdr = 0;
wwo->dwRemain = wwo->dwFragmentSize;
wwo->lpQueuePtr = wwo->lpPlayPtr = wwo->lpLoopPtr = NULL;
wwo->dwPlayedTotal = 0;
TRACE("imhere[0]\n");
SetEvent(wwo->hEvent);
for (;;) {
/* wait for dwSleepTime or an event in thread's queue
* FIXME:
* - is wait time calculation optimal ?
* - these 100 ms parts should be changed, but Eric reports
* that the wodPlayer thread might lock up if we use INFINITE
* (strange !), so I better don't change that now... */
if (wwo->state != WINE_WS_PLAYING)
dwSleepTime = 100;
else
{
tc = GetTickCount();
if (tc < wwo->dwLastFragDone)
while (wodPlayer_RetrieveMessage(wwo, &msg, &param)) {
TRACE( "Received %s %lx\n",
wodPlayerCmdString[msg-WM_USER-1], param );
switch (msg) {
case WINE_WM_PAUSING:
wodPlayer_Reset(wwo, uDevID, FALSE);
wwo->state = WINE_WS_PAUSED;
SetEvent(wwo->hEvent);
break;
case WINE_WM_RESTARTING:
wwo->state = WINE_WS_PLAYING;
SetEvent(wwo->hEvent);
break;
case WINE_WM_HEADER:
lpWaveHdr = (LPWAVEHDR)param;
/* insert buffer at the end of queue */
{
/* calculate sleep time depending on when the last fragment
will be played */
dwSleepTime = (wwo->dwLastFragDone - tc)*7/10;
if (dwSleepTime > 100)
dwSleepTime = 100;
LPWAVEHDR* wh;
for (wh = &(wwo->lpQueuePtr); *wh; wh = &((*wh)->lpNext));
*wh = lpWaveHdr;
}
else
dwSleepTime = 0;
if (!wwo->lpPlayPtr) wwo->lpPlayPtr = lpWaveHdr;
if (wwo->state == WINE_WS_STOPPED)
wwo->state = WINE_WS_PLAYING;
break;
case WINE_WM_RESETTING:
wodPlayer_Reset(wwo, uDevID, TRUE);
SetEvent(wwo->hEvent);
break;
case WINE_WM_CLOSING:
/* sanity check: this should not happen since the device must have been reset before */
if (wwo->lpQueuePtr || wwo->lpPlayPtr) ERR("out of sync\n");
wwo->hThread = 0;
wwo->state = WINE_WS_CLOSED;
SetEvent(wwo->hEvent);
ExitThread(0);
/* shouldn't go here */
default:
FIXME("unknown message %d\n", msg);
break;
}
}
}
TRACE("imhere[1] tc = %08lx\n", GetTickCount());
if (dwSleepTime)
WaitForSingleObject(wwo->msg_event, dwSleepTime);
TRACE("imhere[2] (q=%p p=%p) tc = %08lx\n", wwo->lpQueuePtr,
wwo->lpPlayPtr, GetTickCount());
had_msg = FALSE;
while (wodPlayer_RetrieveMessage(wwo, &msg, &param)) {
had_msg = TRUE;
switch (msg) {
case WINE_WM_PAUSING:
wodPlayer_Reset(wwo, uDevID, FALSE);
wwo->state = WINE_WS_PAUSED;
SetEvent(wwo->hEvent);
break;
case WINE_WM_RESTARTING:
wwo->state = WINE_WS_PLAYING;
SetEvent(wwo->hEvent);
break;
case WINE_WM_HEADER:
lpWaveHdr = (LPWAVEHDR)param;
/* insert buffer at the end of queue */
{
LPWAVEHDR* wh;
for (wh = &(wwo->lpQueuePtr); *wh; wh = &((*wh)->lpNext));
*wh = lpWaveHdr;
}
if (!wwo->lpPlayPtr) wwo->lpPlayPtr = lpWaveHdr;
if (wwo->state == WINE_WS_STOPPED)
wwo->state = WINE_WS_PLAYING;
break;
case WINE_WM_RESETTING:
wodPlayer_Reset(wwo, uDevID, TRUE);
SetEvent(wwo->hEvent);
break;
case WINE_WM_CLOSING:
/* sanity check: this should not happen since the device must have been reset before */
if (wwo->lpQueuePtr || wwo->lpPlayPtr) ERR("out of sync\n");
wwo->hThread = 0;
wwo->state = WINE_WS_CLOSED;
SetEvent(wwo->hEvent);
ExitThread(0);
/* shouldn't go here */
default:
FIXME("unknown message %d\n", msg);
break;
}
if (wwo->state == WINE_WS_PLAYING) {
wodPlayer_WriteFragments(wwo);
}
wodPlayer_Notify(wwo, uDevID, FALSE);
/**************************************************************************
* wodPlayer_FeedDSP [internal]
* Feed as much sound data as we can into the DSP and return the number of
* milliseconds before it will be necessary to feed the DSP again.
*/
static DWORD wodPlayer_FeedDSP( WINE_WAVEOUT* wwo )
{
LPWAVEHDR lpWaveHdr=wwo->lpPlayPtr;
DWORD written=0;
DWORD bytesToWrite;
audio_buf_info dspspace;
/* Read output space info so we know how much writing to do */
if (ioctl(wwo->unixdev, SNDCTL_DSP_GETOSPACE, &dspspace) < 0) {
ERR("IOCTL can't 'SNDCTL_DSP_GETOSPACE' !\n");
}
TRACE( "fragments=%d, fragsize=%d, fragstotal=%d, bytes=%d\n",
dspspace.fragments, dspspace.fragsize,
dspspace.fragstotal, dspspace.bytes );
/* Do nothing if the DSP isn't hungry */
if( dspspace.fragments==0 ) {
return wodPlayer_DSPWait(wwo,dspspace.fragments);
}
/* If there's a partially written wavehdr, feed more of it
* -------------------------------------------------------
*/
bytesToWrite=wwo->dwPartialBytes;
if( bytesToWrite > 0 ) {
TRACE("partial write %d bytes at %p\n",
wwo->dwPartialBytes,
wwo->lpPartialData );
written=wodPlayer_WriteMaxFrags( wwo, wwo->lpPartialData,
bytesToWrite,
&dspspace );
if( written >= bytesToWrite ) {
/* If we wrote it all, skip to the next header */
wwo->dwPartialBytes=0;
wwo->lpPartialData=NULL;
lpWaveHdr=wodPlayer_PlayPtrNext( wwo );
} else {
/* Remove the amount written */
wwo->lpPartialData+=written;
wwo->dwPartialBytes-=written;
}
}
if (!had_msg) { /* if we've received a msg we've just done this so we
won't repeat it */
if (wwo->state == WINE_WS_PLAYING) {
wodPlayer_WriteFragments(wwo);
}
wodPlayer_Notify(wwo, uDevID, FALSE);
/* Feed wavehdrs until we run out of wavehdrs or DSP space
* -------------------------------------------------------
*/
while( lpWaveHdr && dspspace.fragments > 0 ) {
TRACE( "Writing wavehdr %p %lu bytes\n", lpWaveHdr,
lpWaveHdr->dwBufferLength );
/* note the value that dwPlayedTotal will be when this
* wave finishes playing
*/
lpWaveHdr->reserved=wwo->dwWrittenTotal+lpWaveHdr->dwBufferLength;
written=wodPlayer_WriteMaxFrags( wwo, lpWaveHdr->lpData,
lpWaveHdr->dwBufferLength,
&dspspace );
/* If it's all written, on to the next one, else remember the
* partial write for next FeedDSP call.
*/
if( written >= lpWaveHdr->dwBufferLength ) {
lpWaveHdr=wodPlayer_PlayPtrNext( wwo );
} else {
wwo->dwPartialBytes=lpWaveHdr->dwBufferLength-written;
wwo->lpPartialData=lpWaveHdr->lpData+written;
}
}
return lpWaveHdr ? wodPlayer_DSPWait(wwo,dspspace.fragments) : 0 ;
}
/**************************************************************************
* wodPlayer [internal]
*/
static DWORD CALLBACK wodPlayer(LPVOID pmt)
{
WORD uDevID = (DWORD)pmt;
WINE_WAVEOUT* wwo = (WINE_WAVEOUT*)&WOutDev[uDevID];
DWORD dwNextFeedTime=0; /* Time before DSP needs feeding */
DWORD dwNextNotifyTime=0; /* Time before next wave completion */
wwo->state=WINE_WS_STOPPED;
SetEvent( wwo->hEvent );
for(;;) {
wodPlayer_AwaitEvent(wwo,dwNextFeedTime,dwNextNotifyTime);
wodPlayer_ProcessMessages(wwo,uDevID);
if( wwo->state== WINE_WS_PLAYING ) {
dwNextFeedTime=wodPlayer_FeedDSP(wwo);
dwNextNotifyTime=wodPlayer_NotifyCompletions(wwo,uDevID,FALSE);
} else {
dwNextFeedTime=dwNextNotifyTime=0;
}
}
ExitThread(0);
/* just for not generating compilation warnings... should never be executed */
return 0;
}
/**************************************************************************
......@@ -771,9 +916,9 @@ static DWORD wodOpen(WORD wDevID, LPWAVEOPENDESC lpDesc, DWORD dwFlags)
int format;
int sample_rate;
int dsp_stereo;
int fragment_size;
int audio_fragment;
WINE_WAVEOUT* wwo;
audio_buf_info info;
TRACE("(%u, %p, %08lX);\n", wDevID, lpDesc, dwFlags);
if (lpDesc == NULL) {
......@@ -872,22 +1017,30 @@ static DWORD wodOpen(WORD wDevID, LPWAVEOPENDESC lpDesc, DWORD dwFlags)
ERR("Can't set sample_rate to %lu (%d)\n",
wwo->format.wf.nSamplesPerSec, sample_rate);
/* even if we set fragment size above, read it again, just in case */
IOCTL(audio, SNDCTL_DSP_GETBLKSIZE, fragment_size);
if (fragment_size == -1) {
ERR("IOCTL can't 'SNDCTL_DSP_GETBLKSIZE' !\n");
/* Read output space info for future reference */
if (ioctl(wwo->unixdev, SNDCTL_DSP_GETOSPACE, &info) < 0) {
ERR("IOCTL can't 'SNDCTL_DSP_GETOSPACE' !\n");
close(audio);
wwo->unixdev = -1;
return MMSYSERR_NOTENABLED;
}
if ((fragment_size > 1024) && (LOWORD(audio_fragment) <= 10)) {
/* Check that fragsize is correct per our settings above */
if ((info.fragsize > 1024) && (LOWORD(audio_fragment) <= 10)) {
/* we've tried to set 1K fragments or less, but it didn't work */
ERR("fragment size set failed, size is now %d\n", fragment_size);
ERR("fragment size set failed, size is now %d\n", info.fragsize);
MESSAGE("Your Open Sound System driver did not let us configure small enough sound fragments.\n");
MESSAGE("This may cause delays and other problems in audio playback with certain applications.\n");
}
wwo->dwFragmentSize = fragment_size;
/* Remember fragsize and total buffer size for future use */
wwo->dwFragmentSize = info.fragsize;
wwo->dwBufferSize = info.fragstotal * info.fragsize;
wwo->dwMillisPerFragment = info.fragsize * 1000 / wwo->format.wf.nAvgBytesPerSec;
wwo->uWaitForFragments = info.fragstotal * REFILL_BUFFER_WHEN;
TRACE( "wait for %d fragments at %lu millis/fragment\n",
wwo->uWaitForFragments,
wwo->dwMillisPerFragment );
wwo->msg_toget = 0;
wwo->msg_tosave = 0;
wwo->msg_event = CreateEventA(NULL, FALSE, FALSE, NULL);
......@@ -1121,13 +1274,13 @@ static DWORD wodGetPosition(WORD wDevID, LPMMTIME lpTime, DWORD uSize)
if (lpTime == NULL) return MMSYSERR_INVALPARAM;
wwo = &WOutDev[wDevID];
val = wwo->dwPlayedTotal;
val = updatePlayedTotal(wwo);
TRACE("wType=%04X wBitsPerSample=%u nSamplesPerSec=%lu nChannels=%u nAvgBytesPerSec=%lu\n",
lpTime->wType, wwo->format.wBitsPerSample,
wwo->format.wf.nSamplesPerSec, wwo->format.wf.nChannels,
wwo->format.wf.nAvgBytesPerSec);
TRACE("dwTotalPlayed=%lu\n", val);
TRACE("dwPlayedTotal=%lu\n", val);
switch (lpTime->wType) {
case TIME_BYTES:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment