Further unified HAL and HEL mixing, and added some more intelligence
to prebuffering, with some basic prebuffer canceling support, to get rid of mixing delays in Half-Life. Used a very small waveout buffer queue in HEL mode, using a callback to queue additional buffers, to get rid of playback delays in Half-Life. Fixed a couple of bugs.
This commit is contained in:
parent
a90875686f
commit
f250b6b5fa
|
@ -48,15 +48,18 @@
|
|||
DEFAULT_DEBUG_CHANNEL(dsound);
|
||||
|
||||
/* these are eligible for tuning... they must be high on slow machines... */
|
||||
/* especially since the WINMM overhead is pretty high, and could be improved quite a bit;
|
||||
* the high DS_HEL_MARGIN reflects the currently high wineoss/HEL latency
|
||||
* some settings here should probably get ported to wine.conf */
|
||||
/* some stuff may get more responsive with lower values though... */
|
||||
#define DS_EMULDRIVER 1 /* some games (Quake 2, UT) refuse to accept
|
||||
emulated dsound devices. set to 0 ! */
|
||||
#define DS_HEL_FRAGS 48 /* HEL only: number of waveOut fragments in primary buffer */
|
||||
#define DS_HEL_MARGIN 4 /* HEL only: number of waveOut fragments ahead to mix in new buffers */
|
||||
#define DS_HEL_FRAGS 48 /* HEL only: number of waveOut fragments in primary buffer
|
||||
* (changing this won't help you) */
|
||||
#define DS_HEL_MARGIN 5 /* HEL only: number of waveOut fragments ahead to mix in new buffers
|
||||
* (keep this close or equal to DS_HEL_QUEUE for best results) */
|
||||
#define DS_HEL_QUEUE 5 /* HEL only: number of waveOut fragments ahead to queue to driver
|
||||
* (this will affect HEL sound reliability and latency) */
|
||||
|
||||
#define DS_SND_QUEUE 28 /* max number of fragments to prebuffer */
|
||||
#define DS_SND_QUEUE_MAX 28 /* max number of fragments to prebuffer */
|
||||
#define DS_SND_QUEUE_MIN 12 /* min number of fragments to prebuffer */
|
||||
|
||||
/* Linux does not support better timing than 10ms */
|
||||
#define DS_TIME_RES 10 /* Resolution of multimedia timer */
|
||||
|
@ -88,7 +91,7 @@ struct IDirectSoundImpl
|
|||
DSDRIVERCAPS drvcaps;
|
||||
HWAVEOUT hwo;
|
||||
LPWAVEHDR pwave[DS_HEL_FRAGS];
|
||||
UINT timerID, pwplay, pwwrite, pwqueue;
|
||||
UINT timerID, pwplay, pwwrite, pwqueue, prebuf;
|
||||
DWORD fraglen;
|
||||
DWORD priolevel;
|
||||
int nrofbuffers;
|
||||
|
@ -129,6 +132,7 @@ struct IDirectSoundBufferImpl
|
|||
/* used for intelligent (well, sort of) prebuffering */
|
||||
DWORD probably_valid_to;
|
||||
DWORD primary_mixpos, buf_mixpos;
|
||||
BOOL need_remix;
|
||||
};
|
||||
|
||||
#define STATE_STOPPED 0
|
||||
|
@ -225,6 +229,11 @@ static IDirectSoundImpl* dsound = NULL;
|
|||
static IDirectSoundBufferImpl* primarybuf = NULL;
|
||||
|
||||
static void DSOUND_CheckEvent(IDirectSoundBufferImpl *dsb, int len);
|
||||
static void DSOUND_MixCancelAt(IDirectSoundBufferImpl *dsb, DWORD buf_writepos);
|
||||
|
||||
static void DSOUND_WaveQueue(IDirectSoundImpl *dsound, DWORD mixq);
|
||||
static void DSOUND_PerformMix(void);
|
||||
static void CALLBACK DSOUND_callback(HWAVEOUT hwo, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2);
|
||||
|
||||
static HRESULT DSOUND_CreateDirectSoundCapture( LPVOID* ppobj );
|
||||
static HRESULT DSOUND_CreateDirectSoundCaptureBuffer( LPCDSCBUFFERDESC lpcDSCBufferDesc, LPVOID* ppobj );
|
||||
|
@ -1115,6 +1124,7 @@ static HRESULT DSOUND_PrimaryOpen(IDirectSoundBufferImpl *dsb)
|
|||
ds->pwqueue = 0;
|
||||
memset(dsb->buffer, (dsb->wfx.wBitsPerSample == 16) ? 0 : 128, dsb->buflen);
|
||||
TRACE("fraglen=%ld\n", ds->fraglen);
|
||||
DSOUND_WaveQueue(dsb->dsound, (DWORD)-1);
|
||||
}
|
||||
if ((err == DS_OK) && (merr != DS_OK))
|
||||
err = merr;
|
||||
|
@ -1130,9 +1140,11 @@ static void DSOUND_PrimaryClose(IDirectSoundBufferImpl *dsb)
|
|||
unsigned c;
|
||||
IDirectSoundImpl *ds = dsb->dsound;
|
||||
|
||||
ds->pwqueue = (DWORD)-1; /* resetting queues */
|
||||
waveOutReset(ds->hwo);
|
||||
for (c=0; c<DS_HEL_FRAGS; c++)
|
||||
waveOutUnprepareHeader(ds->hwo, ds->pwave[c], sizeof(WAVEHDR));
|
||||
ds->pwqueue = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1141,6 +1153,8 @@ static HRESULT DSOUND_PrimaryPlay(IDirectSoundBufferImpl *dsb)
|
|||
HRESULT err = DS_OK;
|
||||
if (dsb->hwbuf)
|
||||
err = IDsDriverBuffer_Play(dsb->hwbuf, 0, 0, DSBPLAY_LOOPING);
|
||||
else
|
||||
err = mmErr(waveOutRestart(dsb->dsound->hwo));
|
||||
return err;
|
||||
}
|
||||
|
||||
|
@ -1156,12 +1170,15 @@ static HRESULT DSOUND_PrimaryStop(IDirectSoundBufferImpl *dsb)
|
|||
waveOutClose(dsb->dsound->hwo);
|
||||
dsb->dsound->hwo = 0;
|
||||
waveOutOpen(&(dsb->dsound->hwo), dsb->dsound->drvdesc.dnDevNode,
|
||||
&(primarybuf->wfx), 0, 0, CALLBACK_NULL | WAVE_DIRECTSOUND);
|
||||
&(primarybuf->wfx), (DWORD)DSOUND_callback, (DWORD)dsb->dsound,
|
||||
CALLBACK_FUNCTION | WAVE_DIRECTSOUND);
|
||||
err = IDsDriver_CreateSoundBuffer(dsb->dsound->driver,&(dsb->wfx),dsb->dsbd.dwFlags,0,
|
||||
&(dsb->buflen),&(dsb->buffer),
|
||||
(LPVOID)&(dsb->hwbuf));
|
||||
}
|
||||
}
|
||||
else
|
||||
err = mmErr(waveOutPause(dsb->dsound->hwo));
|
||||
return err;
|
||||
}
|
||||
|
||||
|
@ -1213,13 +1230,15 @@ static HRESULT WINAPI IDirectSoundBufferImpl_SetFormat(
|
|||
|
||||
primarybuf->wfx.nAvgBytesPerSec =
|
||||
This->wfx.nSamplesPerSec * This->wfx.nBlockAlign;
|
||||
|
||||
if (primarybuf->dsound->drvdesc.dwFlags & DSDDESC_DOMMSYSTEMSETFORMAT) {
|
||||
/* FIXME: check for errors */
|
||||
DSOUND_PrimaryClose(primarybuf);
|
||||
waveOutClose(This->dsound->hwo);
|
||||
This->dsound->hwo = 0;
|
||||
waveOutOpen(&(This->dsound->hwo), This->dsound->drvdesc.dnDevNode,
|
||||
&(primarybuf->wfx), 0, 0, CALLBACK_NULL | WAVE_DIRECTSOUND);
|
||||
&(primarybuf->wfx), (DWORD)DSOUND_callback, (DWORD)This->dsound,
|
||||
CALLBACK_FUNCTION | WAVE_DIRECTSOUND);
|
||||
DSOUND_PrimaryOpen(primarybuf);
|
||||
}
|
||||
if (primarybuf->hwbuf) {
|
||||
|
@ -1444,11 +1463,12 @@ static DWORD DSOUND_CalcPlayPosition(IDirectSoundBufferImpl *This,
|
|||
DWORD bplay;
|
||||
|
||||
TRACE("primary playpos=%ld, mixpos=%ld\n", pplay, pmix);
|
||||
TRACE("this mixpos=%ld\n", bmix);
|
||||
TRACE("this mixpos=%ld, time=%ld\n", bmix, GetTickCount());
|
||||
|
||||
/* the actual primary play position (pplay) is always behind last mixed (pmix),
|
||||
* unless the computer is too slow or something */
|
||||
/* we need to know how far away we are from there */
|
||||
#if 0 /* we'll never fill the primary entirely */
|
||||
if (pmix == pplay) {
|
||||
if ((state == STATE_PLAYING) || (state == STATE_STOPPING)) {
|
||||
/* wow, the software mixer is really doing well,
|
||||
|
@ -1457,12 +1477,13 @@ static DWORD DSOUND_CalcPlayPosition(IDirectSoundBufferImpl *This,
|
|||
}
|
||||
/* else: the primary buffer is not playing, so probably empty */
|
||||
}
|
||||
#endif
|
||||
if (pmix < pplay) pmix += primarybuf->buflen; /* wraparound */
|
||||
pmix -= pplay;
|
||||
/* detect buffer underrun */
|
||||
if (pwrite < pplay) pwrite += primarybuf->buflen; /* wraparound */
|
||||
pwrite -= pplay;
|
||||
if (pmix > (DS_SND_QUEUE * primarybuf->dsound->fraglen + pwrite + primarybuf->writelead)) {
|
||||
if (pmix > (DS_SND_QUEUE_MAX * primarybuf->dsound->fraglen + pwrite + primarybuf->writelead)) {
|
||||
WARN("detected an underrun: primary queue was %ld\n",pmix);
|
||||
pmix = 0;
|
||||
}
|
||||
|
@ -1497,7 +1518,6 @@ static HRESULT WINAPI IDirectSoundBufferImpl_GetCurrentPosition(
|
|||
hres=IDsDriverBuffer_GetPosition(This->hwbuf,playpos,writepos);
|
||||
if (hres)
|
||||
return hres;
|
||||
|
||||
}
|
||||
else if (This->dsbd.dwFlags & DSBCAPS_PRIMARYBUFFER) {
|
||||
if (playpos) {
|
||||
|
@ -1510,7 +1530,7 @@ static HRESULT WINAPI IDirectSoundBufferImpl_GetCurrentPosition(
|
|||
if (writepos) {
|
||||
/* the writepos should only be used by apps with WRITEPRIMARY priority,
|
||||
* in which case our software mixer is disabled anyway */
|
||||
*writepos = This->playpos + DS_HEL_MARGIN * This->dsound->fraglen;
|
||||
*writepos = (This->dsound->pwplay + DS_HEL_MARGIN) * This->dsound->fraglen;
|
||||
while (*writepos >= This->buflen)
|
||||
*writepos -= This->buflen;
|
||||
}
|
||||
|
@ -1523,7 +1543,6 @@ static HRESULT WINAPI IDirectSoundBufferImpl_GetCurrentPosition(
|
|||
DWORD pplay, pwrite, lplay, splay, pstate;
|
||||
/* let's get this exact; first, recursively call GetPosition on the primary */
|
||||
EnterCriticalSection(&(primarybuf->lock));
|
||||
if ((This->dsbd.dwFlags & DSBCAPS_GETCURRENTPOSITION2) || primarybuf->hwbuf || !DS_EMULDRIVER) {
|
||||
IDirectSoundBufferImpl_GetCurrentPosition((LPDIRECTSOUNDBUFFER)primarybuf, &pplay, &pwrite);
|
||||
/* detect HEL mode underrun */
|
||||
pstate = primarybuf->state;
|
||||
|
@ -1539,6 +1558,7 @@ static HRESULT WINAPI IDirectSoundBufferImpl_GetCurrentPosition(
|
|||
pstate &= This->state;
|
||||
lplay = This->primary_mixpos;
|
||||
splay = This->buf_mixpos;
|
||||
if ((This->dsbd.dwFlags & DSBCAPS_GETCURRENTPOSITION2) || primarybuf->hwbuf) {
|
||||
/* calculate play position using this */
|
||||
*playpos = DSOUND_CalcPlayPosition(This, pstate, pplay, pwrite, lplay, splay);
|
||||
} else {
|
||||
|
@ -1546,7 +1566,12 @@ static HRESULT WINAPI IDirectSoundBufferImpl_GetCurrentPosition(
|
|||
/* don't know exactly how this should be handled...
|
||||
* the docs says that play cursor is reported as directly
|
||||
* behind write cursor, hmm... */
|
||||
*playpos = This->playpos;
|
||||
/* let's just do what might work for Half-Life */
|
||||
DWORD wp;
|
||||
wp = (This->dsound->pwplay + DS_HEL_MARGIN) * This->dsound->fraglen;
|
||||
while (wp >= primarybuf->buflen)
|
||||
wp -= primarybuf->buflen;
|
||||
*playpos = DSOUND_CalcPlayPosition(This, pstate, wp, pwrite, lplay, splay);
|
||||
}
|
||||
LeaveCriticalSection(&(primarybuf->lock));
|
||||
}
|
||||
|
@ -1572,10 +1597,11 @@ static HRESULT WINAPI IDirectSoundBufferImpl_GetStatus(
|
|||
return DSERR_INVALIDPARAM;
|
||||
|
||||
*status = 0;
|
||||
if ((This->state == STATE_STARTING) || (This->state == STATE_PLAYING))
|
||||
if ((This->state == STATE_STARTING) || (This->state == STATE_PLAYING)) {
|
||||
*status |= DSBSTATUS_PLAYING;
|
||||
if (This->playflags & DSBPLAY_LOOPING)
|
||||
*status |= DSBSTATUS_LOOPING;
|
||||
}
|
||||
|
||||
TRACE("status=%lx\n", *status);
|
||||
return DS_OK;
|
||||
|
@ -1609,7 +1635,7 @@ static HRESULT WINAPI IDirectSoundBufferImpl_Lock(
|
|||
ICOM_THIS(IDirectSoundBufferImpl,iface);
|
||||
DWORD capf;
|
||||
|
||||
TRACE("(%p,%ld,%ld,%p,%p,%p,%p,0x%08lx)\n",
|
||||
TRACE("(%p,%ld,%ld,%p,%p,%p,%p,0x%08lx) at %ld\n",
|
||||
This,
|
||||
writecursor,
|
||||
writebytes,
|
||||
|
@ -1617,7 +1643,8 @@ static HRESULT WINAPI IDirectSoundBufferImpl_Lock(
|
|||
audiobytes1,
|
||||
lplpaudioptr2,
|
||||
audiobytes2,
|
||||
flags
|
||||
flags,
|
||||
GetTickCount()
|
||||
);
|
||||
|
||||
if (flags & DSBLOCK_FROMWRITECURSOR) {
|
||||
|
@ -1655,7 +1682,9 @@ static HRESULT WINAPI IDirectSoundBufferImpl_Lock(
|
|||
lplpaudioptr2, audiobytes2,
|
||||
writecursor, writebytes,
|
||||
0);
|
||||
} else
|
||||
}
|
||||
else {
|
||||
BOOL remix = FALSE;
|
||||
if (writecursor+writebytes <= This->buflen) {
|
||||
*(LPBYTE*)lplpaudioptr1 = This->buffer+writecursor;
|
||||
*audiobytes1 = writebytes;
|
||||
|
@ -1673,6 +1702,23 @@ static HRESULT WINAPI IDirectSoundBufferImpl_Lock(
|
|||
*audiobytes2 = writebytes-(This->buflen-writecursor);
|
||||
TRACE("->%ld.%ld\n",*audiobytes1,audiobytes2?*audiobytes2:0);
|
||||
}
|
||||
/* if the segment between playpos and buf_mixpos is touched,
|
||||
* we need to cancel some mixing */
|
||||
if (This->buf_mixpos >= This->playpos) {
|
||||
if (This->buf_mixpos > writecursor &&
|
||||
This->playpos <= writecursor+writebytes)
|
||||
remix = TRUE;
|
||||
}
|
||||
else {
|
||||
if (This->buf_mixpos > writecursor ||
|
||||
This->playpos <= writecursor+writebytes)
|
||||
remix = TRUE;
|
||||
}
|
||||
if (remix) {
|
||||
TRACE("locking prebuffered region, ouch\n");
|
||||
DSOUND_MixCancelAt(This, writecursor);
|
||||
}
|
||||
}
|
||||
return DS_OK;
|
||||
}
|
||||
|
||||
|
@ -1830,7 +1876,7 @@ static HRESULT WINAPI IDirectSoundBufferImpl_GetCaps(
|
|||
if (This->hwbuf) caps->dwFlags |= DSBCAPS_LOCHARDWARE;
|
||||
else caps->dwFlags |= DSBCAPS_LOCSOFTWARE;
|
||||
|
||||
caps->dwBufferBytes = This->dsbd.dwBufferBytes;
|
||||
caps->dwBufferBytes = This->buflen;
|
||||
|
||||
/* This value represents the speed of the "unlock" command.
|
||||
As unlock is quite fast (it does not do anything), I put
|
||||
|
@ -1919,6 +1965,7 @@ static HRESULT WINAPI IDirectSoundBufferImpl_QueryInterface(
|
|||
}
|
||||
#endif
|
||||
|
||||
#if USE_DSOUND3D
|
||||
if ( IsEqualGUID( &IID_IDirectSound3DListener, riid ) ) {
|
||||
IDirectSound3DListenerImpl* dsl;
|
||||
|
||||
|
@ -1959,6 +2006,14 @@ static HRESULT WINAPI IDirectSoundBufferImpl_QueryInterface(
|
|||
|
||||
return S_OK;
|
||||
}
|
||||
#else
|
||||
if ( IsEqualGUID( &IID_IDirectSound3DListener, riid ) ) {
|
||||
FIXME("%s: I know about this GUID, but don't support it yet\n",
|
||||
debugstr_guid( riid ));
|
||||
*ppobj = NULL;
|
||||
return E_FAIL;
|
||||
}
|
||||
#endif
|
||||
|
||||
FIXME( "Unknown GUID %s\n", debugstr_guid( riid ) );
|
||||
|
||||
|
@ -2639,7 +2694,7 @@ static INT DSOUND_MixerNorm(IDirectSoundBufferImpl *dsb, BYTE *buf, INT len)
|
|||
/* Patent enhancements (c) 2000 Ove Kåven,
|
||||
* TransGaming Technologies Inc. */
|
||||
|
||||
TRACE("(%p) Adjusting frequency: %ld -> %ld\n",
|
||||
FIXME("(%p) Adjusting frequency: %ld -> %ld (need optimization)\n",
|
||||
dsb, dsb->freq, primarybuf->wfx.nSamplesPerSec);
|
||||
|
||||
size = len / oAdvance;
|
||||
|
@ -2852,9 +2907,118 @@ static DWORD DSOUND_MixInBuffer(IDirectSoundBufferImpl *dsb, DWORD writepos, DWO
|
|||
return len;
|
||||
}
|
||||
|
||||
static void DSOUND_MixCancel(IDirectSoundBufferImpl *dsb, DWORD writepos)
|
||||
static void DSOUND_PhaseCancel(IDirectSoundBufferImpl *dsb, DWORD writepos, DWORD len)
|
||||
{
|
||||
FIXME("prebuffer cancel not implemented yet\n");
|
||||
INT i, ilen, field;
|
||||
INT advance = primarybuf->wfx.wBitsPerSample >> 3;
|
||||
BYTE *buf, *ibuf, *obuf;
|
||||
INT16 *ibufs, *obufs;
|
||||
|
||||
len &= ~3; /* 4 byte alignment */
|
||||
|
||||
TRACE("allocating buffer (size = %ld)\n", len);
|
||||
if ((buf = ibuf = (BYTE *) DSOUND_tmpbuffer(len)) == NULL)
|
||||
return;
|
||||
|
||||
TRACE("PhaseCancel (%p) len = %ld, dest = %ld\n", dsb, len, writepos);
|
||||
|
||||
ilen = DSOUND_MixerNorm(dsb, ibuf, len);
|
||||
if ((dsb->dsbd.dwFlags & DSBCAPS_CTRLPAN) ||
|
||||
(dsb->dsbd.dwFlags & DSBCAPS_CTRLVOLUME))
|
||||
DSOUND_MixerVol(dsb, ibuf, len);
|
||||
|
||||
/* subtract instead of add, to phase out premixed data */
|
||||
obuf = primarybuf->buffer + writepos;
|
||||
for (i = 0; i < len; i += advance) {
|
||||
obufs = (INT16 *) obuf;
|
||||
ibufs = (INT16 *) ibuf;
|
||||
if (primarybuf->wfx.wBitsPerSample == 8) {
|
||||
/* 8-bit WAV is unsigned */
|
||||
field = (*ibuf - 128);
|
||||
field -= (*obuf - 128);
|
||||
field = field > 127 ? 127 : field;
|
||||
field = field < -128 ? -128 : field;
|
||||
*obuf = field + 128;
|
||||
} else {
|
||||
/* 16-bit WAV is signed */
|
||||
field = *ibufs;
|
||||
field -= *obufs;
|
||||
field = field > 32767 ? 32767 : field;
|
||||
field = field < -32768 ? -32768 : field;
|
||||
*obufs = field;
|
||||
}
|
||||
ibuf += advance;
|
||||
obuf += advance;
|
||||
if (obuf >= (BYTE *)(primarybuf->buffer + primarybuf->buflen))
|
||||
obuf = primarybuf->buffer;
|
||||
}
|
||||
/* free(buf); */
|
||||
}
|
||||
|
||||
static void DSOUND_MixCancel(IDirectSoundBufferImpl *dsb, DWORD writepos, BOOL cancel)
|
||||
{
|
||||
DWORD size, flen, len, npos, nlen;
|
||||
INT iAdvance = dsb->wfx.nBlockAlign;
|
||||
INT oAdvance = primarybuf->wfx.nBlockAlign;
|
||||
/* determine amount of premixed data to cancel */
|
||||
DWORD primary_done =
|
||||
((dsb->primary_mixpos < writepos) ? primarybuf->buflen : 0) +
|
||||
dsb->primary_mixpos - writepos;
|
||||
|
||||
TRACE("(%p, %ld), buf_mixpos=%ld\n", dsb, writepos, dsb->buf_mixpos);
|
||||
|
||||
/* backtrack the mix position */
|
||||
size = primary_done / oAdvance;
|
||||
flen = size * dsb->freqAdjust;
|
||||
len = (flen >> DSOUND_FREQSHIFT) * iAdvance;
|
||||
flen &= (1<<DSOUND_FREQSHIFT)-1;
|
||||
while (dsb->freqAcc < flen) {
|
||||
len += iAdvance;
|
||||
dsb->freqAcc += 1<<DSOUND_FREQSHIFT;
|
||||
}
|
||||
len %= dsb->buflen;
|
||||
npos = ((dsb->buf_mixpos < len) ? dsb->buflen : 0) +
|
||||
dsb->buf_mixpos - len;
|
||||
if (dsb->leadin && (dsb->startpos > npos) && (dsb->startpos <= npos + len)) {
|
||||
/* stop backtracking at startpos */
|
||||
npos = dsb->startpos;
|
||||
len = ((dsb->buf_mixpos < npos) ? dsb->buflen : 0) +
|
||||
dsb->buf_mixpos - npos;
|
||||
flen = dsb->freqAcc;
|
||||
nlen = len / dsb->wfx.nBlockAlign;
|
||||
nlen = ((nlen << DSOUND_FREQSHIFT) + flen) / dsb->freqAdjust;
|
||||
nlen *= primarybuf->wfx.nBlockAlign;
|
||||
writepos =
|
||||
((dsb->primary_mixpos < nlen) ? primarybuf->buflen : 0) +
|
||||
dsb->primary_mixpos - nlen;
|
||||
}
|
||||
|
||||
dsb->freqAcc -= flen;
|
||||
dsb->buf_mixpos = npos;
|
||||
dsb->primary_mixpos = writepos;
|
||||
|
||||
TRACE("new buf_mixpos=%ld, primary_mixpos=%ld (len=%ld)\n",
|
||||
dsb->buf_mixpos, dsb->primary_mixpos, len);
|
||||
|
||||
if (cancel) DSOUND_PhaseCancel(dsb, writepos, len);
|
||||
}
|
||||
|
||||
static void DSOUND_MixCancelAt(IDirectSoundBufferImpl *dsb, DWORD buf_writepos)
|
||||
{
|
||||
#if 0
|
||||
DWORD i, size, flen, len, npos, nlen;
|
||||
INT iAdvance = dsb->wfx.nBlockAlign;
|
||||
INT oAdvance = primarybuf->wfx.nBlockAlign;
|
||||
/* determine amount of premixed data to cancel */
|
||||
DWORD buf_done =
|
||||
((dsb->buf_mixpos < buf_writepos) ? dsb->buflen : 0) +
|
||||
dsb->buf_mixpos - buf_writepos;
|
||||
#endif
|
||||
|
||||
WARN("(%p, %ld), buf_mixpos=%ld\n", dsb, buf_writepos, dsb->buf_mixpos);
|
||||
/* since this is not implemented yet, just cancel *ALL* prebuffering for now
|
||||
* (which is faster anyway when there's one a single secondary buffer) */
|
||||
primarybuf->need_remix = TRUE;
|
||||
}
|
||||
|
||||
static DWORD DSOUND_MixOne(IDirectSoundBufferImpl *dsb, DWORD playpos, DWORD writepos, DWORD mixlen)
|
||||
|
@ -2926,12 +3090,11 @@ static DWORD DSOUND_MixOne(IDirectSoundBufferImpl *dsb, DWORD playpos, DWORD wri
|
|||
/* we just have to go ahead and mix what we have,
|
||||
* there's no telling what the app is thinking anyway */
|
||||
} else {
|
||||
/* divide valid length by our sample size */
|
||||
probably_valid_left /= dsb->wfx.nBlockAlign;
|
||||
/* adjust for our frequency */
|
||||
probably_valid_left = (probably_valid_left << DSOUND_FREQSHIFT) / dsb->freqAdjust;
|
||||
/* multiply by primary sample size */
|
||||
probably_valid_left *= primarybuf->wfx.nBlockAlign;
|
||||
/* adjust for our frequency and our sample size */
|
||||
probably_valid_left = MulDiv(probably_valid_left,
|
||||
1 << DSOUND_FREQSHIFT,
|
||||
dsb->wfx.nBlockAlign * dsb->freqAdjust) *
|
||||
primarybuf->wfx.nBlockAlign;
|
||||
/* check whether to clip mix_len */
|
||||
if (probably_valid_left < mixlen) {
|
||||
TRACE("clipping to probably_valid_left=%ld\n", probably_valid_left);
|
||||
|
@ -3005,7 +3168,7 @@ static DWORD DSOUND_MixToPrimary(DWORD playpos, DWORD writepos, DWORD mixlen, BO
|
|||
TRACE("Checking %p, mixlen=%ld\n", dsb, mixlen);
|
||||
EnterCriticalSection(&(dsb->lock));
|
||||
if (dsb->state == STATE_STOPPING) {
|
||||
DSOUND_MixCancel(dsb, writepos);
|
||||
DSOUND_MixCancel(dsb, writepos, TRUE);
|
||||
dsb->state = STATE_STOPPED;
|
||||
} else {
|
||||
if ((dsb->state == STATE_STARTING) || recover)
|
||||
|
@ -3022,19 +3185,83 @@ static DWORD DSOUND_MixToPrimary(DWORD playpos, DWORD writepos, DWORD mixlen, BO
|
|||
return maxlen;
|
||||
}
|
||||
|
||||
static void CALLBACK DSOUND_timer(UINT timerID, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2)
|
||||
static void DSOUND_MixReset(DWORD writepos)
|
||||
{
|
||||
INT i;
|
||||
IDirectSoundBufferImpl *dsb;
|
||||
int nfiller;
|
||||
|
||||
TRACE("(%ld)\n", writepos);
|
||||
|
||||
/* the sound of silence */
|
||||
nfiller = primarybuf->wfx.wBitsPerSample == 8 ? 128 : 0;
|
||||
|
||||
/* reset all buffer mix positions */
|
||||
for (i = dsound->nrofbuffers - 1; i >= 0; i--) {
|
||||
dsb = dsound->buffers[i];
|
||||
|
||||
if (!dsb || !(ICOM_VTBL(dsb)))
|
||||
continue;
|
||||
if (dsb->buflen && dsb->state && !dsb->hwbuf) {
|
||||
TRACE("Resetting %p\n", dsb);
|
||||
EnterCriticalSection(&(dsb->lock));
|
||||
if (dsb->state == STATE_STOPPING) {
|
||||
dsb->state = STATE_STOPPED;
|
||||
}
|
||||
else if (dsb->state == STATE_STARTING) {
|
||||
/* nothing */
|
||||
} else {
|
||||
DSOUND_MixCancel(dsb, writepos, FALSE);
|
||||
}
|
||||
LeaveCriticalSection(&(dsb->lock));
|
||||
}
|
||||
}
|
||||
|
||||
/* wipe out premixed data */
|
||||
if (primarybuf->buf_mixpos < writepos) {
|
||||
memset(primarybuf->buffer + writepos, nfiller, primarybuf->buflen - writepos);
|
||||
memset(primarybuf->buffer, nfiller, primarybuf->buf_mixpos);
|
||||
} else {
|
||||
memset(primarybuf->buffer + writepos, nfiller, primarybuf->buf_mixpos - writepos);
|
||||
}
|
||||
|
||||
/* reset primary mix position */
|
||||
primarybuf->buf_mixpos = writepos;
|
||||
}
|
||||
|
||||
static void DSOUND_CheckReset(IDirectSoundImpl *dsound, DWORD writepos)
|
||||
{
|
||||
if (primarybuf->need_remix) {
|
||||
DSOUND_MixReset(writepos);
|
||||
primarybuf->need_remix = FALSE;
|
||||
/* maximize Half-Life performance */
|
||||
dsound->prebuf = DS_SND_QUEUE_MIN;
|
||||
} else {
|
||||
/* if (dsound->prebuf < DS_SND_QUEUE_MAX) dsound->prebuf++; */
|
||||
}
|
||||
TRACE("premix adjust: %d\n", dsound->prebuf);
|
||||
}
|
||||
|
||||
static void DSOUND_WaveQueue(IDirectSoundImpl *dsound, DWORD mixq)
|
||||
{
|
||||
if (mixq + dsound->pwqueue > DS_HEL_QUEUE) mixq = DS_HEL_QUEUE - dsound->pwqueue;
|
||||
TRACE("queueing %ld buffers, starting at %d\n", mixq, dsound->pwwrite);
|
||||
for (; mixq; mixq--) {
|
||||
waveOutWrite(dsound->hwo, dsound->pwave[dsound->pwwrite], sizeof(WAVEHDR));
|
||||
dsound->pwwrite++;
|
||||
if (dsound->pwwrite >= DS_HEL_FRAGS) dsound->pwwrite = 0;
|
||||
dsound->pwqueue++;
|
||||
}
|
||||
}
|
||||
|
||||
/* #define SYNC_CALLBACK */
|
||||
|
||||
static void DSOUND_PerformMix(void)
|
||||
{
|
||||
int nfiller;
|
||||
BOOL forced;
|
||||
HRESULT hres;
|
||||
|
||||
if (!dsound || !primarybuf) {
|
||||
ERR("dsound died without killing us?\n");
|
||||
timeKillEvent(timerID);
|
||||
timeEndPeriod(DS_TIME_RES);
|
||||
return;
|
||||
}
|
||||
|
||||
EnterCriticalSection(&(dsound->lock));
|
||||
|
||||
if (!primarybuf || !primarybuf->ref) {
|
||||
|
@ -3049,11 +3276,12 @@ static void CALLBACK DSOUND_timer(UINT timerID, UINT msg, DWORD dwUser, DWORD dw
|
|||
/* whether the primary is forced to play even without secondary buffers */
|
||||
forced = ((primarybuf->state == STATE_PLAYING) || (primarybuf->state == STATE_STARTING));
|
||||
|
||||
if (primarybuf->hwbuf) {
|
||||
TRACE("entering at %ld\n", GetTickCount());
|
||||
if (dsound->priolevel != DSSCL_WRITEPRIMARY) {
|
||||
BOOL paused = ((primarybuf->state == STATE_STOPPED) || (primarybuf->state == STATE_STARTING));
|
||||
/* FIXME: document variables */
|
||||
DWORD playpos, writepos, inq, maxq, frag;
|
||||
if (primarybuf->hwbuf) {
|
||||
hres = IDsDriverBuffer_GetPosition(primarybuf->hwbuf, &playpos, &writepos);
|
||||
if (hres) {
|
||||
LeaveCriticalSection(&(dsound->lock));
|
||||
|
@ -3067,6 +3295,16 @@ static void CALLBACK DSOUND_timer(UINT timerID, UINT msg, DWORD dwUser, DWORD dw
|
|||
while (writepos >= primarybuf->buflen)
|
||||
writepos -= primarybuf->buflen;
|
||||
} else writepos = playpos;
|
||||
}
|
||||
else {
|
||||
playpos = dsound->pwplay * dsound->fraglen;
|
||||
writepos = playpos;
|
||||
if (!paused) {
|
||||
writepos += DS_HEL_MARGIN * dsound->fraglen;
|
||||
while (writepos >= primarybuf->buflen)
|
||||
writepos -= primarybuf->buflen;
|
||||
}
|
||||
}
|
||||
TRACE("primary playpos=%ld, writepos=%ld, clrpos=%ld, mixpos=%ld\n",
|
||||
playpos,writepos,primarybuf->playpos,primarybuf->buf_mixpos);
|
||||
/* wipe out just-played sound data */
|
||||
|
@ -3078,6 +3316,11 @@ static void CALLBACK DSOUND_timer(UINT timerID, UINT msg, DWORD dwUser, DWORD dw
|
|||
}
|
||||
primarybuf->playpos = playpos;
|
||||
|
||||
EnterCriticalSection(&(primarybuf->lock));
|
||||
|
||||
/* reset mixing if necessary */
|
||||
DSOUND_CheckReset(dsound, writepos);
|
||||
|
||||
/* check how much prebuffering is left */
|
||||
inq = primarybuf->buf_mixpos;
|
||||
if (inq < writepos)
|
||||
|
@ -3092,12 +3335,10 @@ static void CALLBACK DSOUND_timer(UINT timerID, UINT msg, DWORD dwUser, DWORD dw
|
|||
maxq -= writepos;
|
||||
} else maxq = primarybuf->buflen;
|
||||
|
||||
/* clip maxq to DS_SND_QUEUE */
|
||||
frag = DS_SND_QUEUE * dsound->fraglen;
|
||||
/* clip maxq to dsound->prebuf */
|
||||
frag = dsound->prebuf * dsound->fraglen;
|
||||
if (maxq > frag) maxq = frag;
|
||||
|
||||
EnterCriticalSection(&(primarybuf->lock));
|
||||
|
||||
/* check for consistency */
|
||||
if (inq > maxq) {
|
||||
/* the playback position must have passed our last
|
||||
|
@ -3106,7 +3347,6 @@ static void CALLBACK DSOUND_timer(UINT timerID, UINT msg, DWORD dwUser, DWORD dw
|
|||
TRACE("reached end of mixed data (inq=%ld, maxq=%ld)\n", inq, maxq);
|
||||
inq = 0;
|
||||
/* stop the playback now, to allow buffers to refill */
|
||||
DSOUND_PrimaryStop(primarybuf);
|
||||
if (primarybuf->state == STATE_PLAYING) {
|
||||
primarybuf->state = STATE_STARTING;
|
||||
}
|
||||
|
@ -3117,6 +3357,15 @@ static void CALLBACK DSOUND_timer(UINT timerID, UINT msg, DWORD dwUser, DWORD dw
|
|||
/* how can we have an underrun if we aren't playing? */
|
||||
WARN("unexpected primary state (%ld)\n", primarybuf->state);
|
||||
}
|
||||
#ifdef SYNC_CALLBACK
|
||||
/* DSOUND_callback may need this lock */
|
||||
LeaveCriticalSection(&(primarybuf->lock));
|
||||
#endif
|
||||
DSOUND_PrimaryStop(primarybuf);
|
||||
#ifdef SYNC_CALLBACK
|
||||
EnterCriticalSection(&(primarybuf->lock));
|
||||
#endif
|
||||
if (primarybuf->hwbuf) {
|
||||
/* the Stop is supposed to reset play position to beginning of buffer */
|
||||
/* unfortunately, OSS is not able to do so, so get current pointer */
|
||||
hres = IDsDriverBuffer_GetPosition(primarybuf->hwbuf, &playpos, NULL);
|
||||
|
@ -3125,6 +3374,9 @@ static void CALLBACK DSOUND_timer(UINT timerID, UINT msg, DWORD dwUser, DWORD dw
|
|||
LeaveCriticalSection(&(primarybuf->lock));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
playpos = dsound->pwplay * dsound->fraglen;
|
||||
}
|
||||
writepos = playpos;
|
||||
primarybuf->playpos = playpos;
|
||||
primarybuf->buf_mixpos = writepos;
|
||||
|
@ -3145,18 +3397,20 @@ static void CALLBACK DSOUND_timer(UINT timerID, UINT msg, DWORD dwUser, DWORD dw
|
|||
if (frag) {
|
||||
/* buffers have been filled, restart playback */
|
||||
if (primarybuf->state == STATE_STARTING) {
|
||||
DSOUND_PrimaryPlay(primarybuf);
|
||||
primarybuf->state = STATE_PLAYING;
|
||||
TRACE("starting playback\n");
|
||||
}
|
||||
else if (primarybuf->state == STATE_STOPPED) {
|
||||
/* the primarybuf is supposed to play if there's something to play
|
||||
* even if it is reported as stopped, so don't let this confuse you */
|
||||
DSOUND_PrimaryPlay(primarybuf);
|
||||
primarybuf->state = STATE_STOPPING;
|
||||
}
|
||||
LeaveCriticalSection(&(primarybuf->lock));
|
||||
if (paused) {
|
||||
DSOUND_PrimaryPlay(primarybuf);
|
||||
TRACE("starting playback\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
LeaveCriticalSection(&(primarybuf->lock));
|
||||
} else {
|
||||
/* in the DSSCL_WRITEPRIMARY mode, the app is totally in charge... */
|
||||
|
@ -3169,91 +3423,64 @@ static void CALLBACK DSOUND_timer(UINT timerID, UINT msg, DWORD dwUser, DWORD dw
|
|||
primarybuf->state = STATE_STOPPED;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* using waveOut stuff */
|
||||
/* if no buffers are playing, we should be in pause mode now */
|
||||
DWORD writepos, mixq;
|
||||
/* clean out completed fragments */
|
||||
while (dsound->pwqueue && (dsound->pwave[dsound->pwplay]->dwFlags & WHDR_DONE)) {
|
||||
if (dsound->priolevel != DSSCL_WRITEPRIMARY) {
|
||||
DWORD pos = dsound->pwplay * dsound->fraglen;
|
||||
TRACE("done playing primary pos=%ld\n",pos);
|
||||
memset(primarybuf->buffer + pos, nfiller, dsound->fraglen);
|
||||
}
|
||||
dsound->pwplay++;
|
||||
if (dsound->pwplay >= DS_HEL_FRAGS) dsound->pwplay = 0;
|
||||
dsound->pwqueue--;
|
||||
}
|
||||
primarybuf->playpos = dsound->pwplay * dsound->fraglen;
|
||||
TRACE("primary playpos=%ld, mixpos=%ld\n",primarybuf->playpos,primarybuf->buf_mixpos);
|
||||
EnterCriticalSection(&(primarybuf->lock));
|
||||
|
||||
if (!dsound->pwqueue) {
|
||||
/* this is either an underrun or we have nothing more to play...
|
||||
* since playback has already stopped now, we can enter pause mode,
|
||||
* in order to allow buffers to refill */
|
||||
if (primarybuf->state == STATE_PLAYING) {
|
||||
TRACE("no more fragments ready to play\n");
|
||||
waveOutPause(dsound->hwo);
|
||||
primarybuf->state = STATE_STARTING;
|
||||
}
|
||||
else if (primarybuf->state == STATE_STOPPING) {
|
||||
TRACE("no more fragments ready to play\n");
|
||||
waveOutPause(dsound->hwo);
|
||||
primarybuf->state = STATE_STOPPED;
|
||||
}
|
||||
}
|
||||
|
||||
/* find next write position, plus some extra margin, if necessary */
|
||||
mixq = DS_HEL_MARGIN;
|
||||
if (mixq > dsound->pwqueue) mixq = dsound->pwqueue;
|
||||
writepos = primarybuf->playpos + mixq * dsound->fraglen;
|
||||
while (writepos >= primarybuf->buflen) writepos -= primarybuf->buflen;
|
||||
|
||||
/* do the mixing */
|
||||
if (dsound->priolevel != DSSCL_WRITEPRIMARY) {
|
||||
DWORD frag, maxq = DS_SND_QUEUE * dsound->fraglen;
|
||||
frag = DSOUND_MixToPrimary(primarybuf->playpos, writepos, maxq, FALSE);
|
||||
mixq = frag / dsound->fraglen;
|
||||
if (frag - (mixq * dsound->fraglen))
|
||||
mixq++;
|
||||
} else mixq = 0;
|
||||
if (forced) mixq = DS_SND_QUEUE;
|
||||
|
||||
/* Make sure that we don't rewrite any fragments that have already been
|
||||
written and are waiting to be played */
|
||||
mixq = (dsound->pwqueue > mixq) ? 0 : (mixq - dsound->pwqueue);
|
||||
|
||||
/* output it */
|
||||
for (; mixq; mixq--) {
|
||||
waveOutWrite(dsound->hwo, dsound->pwave[dsound->pwwrite], sizeof(WAVEHDR));
|
||||
dsound->pwwrite++;
|
||||
if (dsound->pwwrite >= DS_HEL_FRAGS) dsound->pwwrite = 0;
|
||||
dsound->pwqueue++;
|
||||
}
|
||||
primarybuf->buf_mixpos = dsound->pwwrite * dsound->fraglen;
|
||||
|
||||
if (dsound->pwqueue) {
|
||||
/* buffers have been filled, restart playback */
|
||||
if (primarybuf->state == STATE_STARTING) {
|
||||
waveOutRestart(dsound->hwo);
|
||||
primarybuf->state = STATE_PLAYING;
|
||||
TRACE("starting playback\n");
|
||||
}
|
||||
else if (primarybuf->state == STATE_STOPPED) {
|
||||
/* the primarybuf is supposed to play if there's something to play
|
||||
* even if it is reported as stopped, so don't let this confuse you */
|
||||
waveOutRestart(dsound->hwo);
|
||||
primarybuf->state = STATE_STOPPING;
|
||||
TRACE("starting playback\n");
|
||||
}
|
||||
}
|
||||
LeaveCriticalSection(&(primarybuf->lock));
|
||||
}
|
||||
TRACE("completed processing\n");
|
||||
TRACE("completed processing at %ld\n", GetTickCount());
|
||||
LeaveCriticalSection(&(dsound->lock));
|
||||
}
|
||||
|
||||
static void CALLBACK DSOUND_timer(UINT timerID, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2)
|
||||
{
|
||||
if (!dsound || !primarybuf) {
|
||||
ERR("dsound died without killing us?\n");
|
||||
timeKillEvent(timerID);
|
||||
timeEndPeriod(DS_TIME_RES);
|
||||
return;
|
||||
}
|
||||
|
||||
TRACE("entered\n");
|
||||
DSOUND_PerformMix();
|
||||
}
|
||||
|
||||
static void CALLBACK DSOUND_callback(HWAVEOUT hwo, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2)
|
||||
{
|
||||
IDirectSoundImpl* This = (IDirectSoundImpl*)dwUser;
|
||||
TRACE("entering at %ld, msg=%08x\n", GetTickCount(), msg);
|
||||
if (msg == MM_WOM_DONE) {
|
||||
DWORD inq, mixq, fraglen, buflen, pwplay, playpos, mixpos;
|
||||
if (This->pwqueue == (DWORD)-1) {
|
||||
TRACE("completed due to reset\n");
|
||||
return;
|
||||
}
|
||||
/* it could be a bad idea to enter critical section here... if there's lock contention,
|
||||
* the resulting scheduling delays might obstruct the winmm player thread */
|
||||
#ifdef SYNC_CALLBACK
|
||||
EnterCriticalSection(&(primarybuf->lock));
|
||||
#endif
|
||||
/* retrieve current values */
|
||||
fraglen = dsound->fraglen;
|
||||
buflen = primarybuf->buflen;
|
||||
pwplay = dsound->pwplay;
|
||||
playpos = pwplay * fraglen;
|
||||
mixpos = primarybuf->buf_mixpos;
|
||||
/* check remaining mixed data */
|
||||
inq = ((mixpos < playpos) ? buflen : 0) + mixpos - playpos;
|
||||
mixq = inq / fraglen;
|
||||
if ((inq - (mixq * fraglen)) > 0) mixq++;
|
||||
/* complete the playing buffer */
|
||||
TRACE("done playing primary pos=%ld\n", playpos);
|
||||
pwplay++;
|
||||
if (pwplay >= DS_HEL_FRAGS) pwplay = 0;
|
||||
/* write new values */
|
||||
dsound->pwplay = pwplay;
|
||||
dsound->pwqueue--;
|
||||
/* queue new buffer if we have data for it */
|
||||
if (inq>1) DSOUND_WaveQueue(This, inq-1);
|
||||
#ifdef SYNC_CALLBACK
|
||||
LeaveCriticalSection(&(primarybuf->lock));
|
||||
#endif
|
||||
}
|
||||
TRACE("completed\n");
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* DirectSoundCreate (DSOUND.1)
|
||||
*/
|
||||
|
@ -3307,6 +3534,8 @@ HRESULT WINAPI DirectSoundCreate(REFGUID lpGUID,LPDIRECTSOUND *ppDS,IUnknown *pU
|
|||
(*ippDS)->primary = NULL;
|
||||
(*ippDS)->listener = NULL;
|
||||
|
||||
(*ippDS)->prebuf = DS_SND_QUEUE_MAX;
|
||||
|
||||
/* Get driver description */
|
||||
if (drv) {
|
||||
IDsDriver_GetDriverDesc(drv,&((*ippDS)->drvdesc));
|
||||
|
@ -3330,7 +3559,8 @@ HRESULT WINAPI DirectSoundCreate(REFGUID lpGUID,LPDIRECTSOUND *ppDS,IUnknown *pU
|
|||
if ((*ippDS)->drvdesc.dwFlags & DSDDESC_DOMMSYSTEMOPEN) {
|
||||
/* FIXME: is this right? */
|
||||
err = mmErr(waveOutOpen(&((*ippDS)->hwo), (*ippDS)->drvdesc.dnDevNode, &((*ippDS)->wfx),
|
||||
0, 0, CALLBACK_NULL | WAVE_DIRECTSOUND));
|
||||
(DWORD)DSOUND_callback, (DWORD)(*ippDS),
|
||||
CALLBACK_FUNCTION | WAVE_DIRECTSOUND));
|
||||
}
|
||||
|
||||
if (drv && (err == DS_OK))
|
||||
|
|
Loading…
Reference in New Issue