dsound patch

Ove Kaaven ovehk at ping.uio.no
Wed Jan 2 11:00:26 CST 2002


Log:
Ove Kaaven <ovek at transgaming.com>
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.

Index: dlls/dsound/dsound_main.c
===================================================================
RCS file: /cvsroot/winex/wine/dlls/dsound/dsound_main.c,v
retrieving revision 1.1.1.14
retrieving revision 1.35
diff -u -r1.1.1.14 -r1.35
--- dlls/dsound/dsound_main.c	2001/10/04 13:43:11	1.1.1.14
+++ dlls/dsound/dsound_main.c	2001/11/23 16:02:19	1.35
@@ -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 @@
     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 @@
     /* used for intelligent (well, sort of) prebuffering */
     DWORD                     probably_valid_to;
     DWORD                     primary_mixpos, buf_mixpos;
+    BOOL                      need_remix;
 };
 
 #define STATE_STOPPED  0
@@ -225,7 +229,12 @@
 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 @@
 			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 @@
 		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 @@
 	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 @@
 			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 @@
 
 	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 @@
 	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 @@
 		}
 		/* 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 @@
 		hres=IDsDriverBuffer_GetPosition(This->hwbuf,playpos,writepos);
 		if (hres)
 		    return hres;
-
 	}
 	else if (This->dsbd.dwFlags & DSBCAPS_PRIMARYBUFFER) {
 		if (playpos) {
@@ -1510,7 +1530,7 @@
 		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,22 +1543,22 @@
 			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;
-				if (!(primarybuf->hwbuf || primarybuf->dsound->pwqueue)) {
-					TRACE("detected an underrun\n");
-					/* pplay = ? */
-					if (pstate == STATE_PLAYING)
-						pstate = STATE_STARTING;
-					else if (pstate == STATE_STOPPING)
-						pstate = STATE_STOPPED;
-				}
-				/* get data for ourselves while we still have the lock */
-				pstate &= This->state;
-				lplay = This->primary_mixpos;
-				splay = This->buf_mixpos;
+			IDirectSoundBufferImpl_GetCurrentPosition((LPDIRECTSOUNDBUFFER)primarybuf, &pplay, &pwrite);
+			/* detect HEL mode underrun */
+			pstate = primarybuf->state;
+			if (!(primarybuf->hwbuf || primarybuf->dsound->pwqueue)) {
+				TRACE("detected an underrun\n");
+				/* pplay = ? */
+				if (pstate == STATE_PLAYING)
+					pstate = STATE_STARTING;
+				else if (pstate == STATE_STOPPING)
+					pstate = STATE_STOPPED;
+			}
+			/* get data for ourselves while we still have the lock */
+			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 @@
 				/* 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 @@
 		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;
+		if (This->playflags & DSBPLAY_LOOPING)
+			*status |= DSBSTATUS_LOOPING;
+	}
 
 	TRACE("status=%lx\n", *status);
 	return DS_OK;
@@ -1609,7 +1635,7 @@
 	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 @@
 		audiobytes1,
 		lplpaudioptr2,
 		audiobytes2,
-		flags
+		flags,
+		GetTickCount()
 	);
 
 	if (flags & DSBLOCK_FROMWRITECURSOR) {
@@ -1655,23 +1682,42 @@
 				     lplpaudioptr2, audiobytes2,
 				     writecursor, writebytes,
 				     0);
-	} else
-	if (writecursor+writebytes <= This->buflen) {
-		*(LPBYTE*)lplpaudioptr1 = This->buffer+writecursor;
-		*audiobytes1 = writebytes;
-		if (lplpaudioptr2)
-			*(LPBYTE*)lplpaudioptr2 = NULL;
-		if (audiobytes2)
-			*audiobytes2 = 0;
-		TRACE("->%ld.0\n",writebytes);
-	} else {
-		*(LPBYTE*)lplpaudioptr1 = This->buffer+writecursor;
-		*audiobytes1 = This->buflen-writecursor;
-		if (lplpaudioptr2)
-			*(LPBYTE*)lplpaudioptr2 = This->buffer;
-		if (audiobytes2)
-			*audiobytes2 = writebytes-(This->buflen-writecursor);
-		TRACE("->%ld.%ld\n",*audiobytes1,audiobytes2?*audiobytes2:0);
+	}
+	else {
+		BOOL remix = FALSE;
+		if (writecursor+writebytes <= This->buflen) {
+			*(LPBYTE*)lplpaudioptr1 = This->buffer+writecursor;
+			*audiobytes1 = writebytes;
+			if (lplpaudioptr2)
+				*(LPBYTE*)lplpaudioptr2 = NULL;
+			if (audiobytes2)
+				*audiobytes2 = 0;
+			TRACE("->%ld.0\n",writebytes);
+		} else {
+			*(LPBYTE*)lplpaudioptr1 = This->buffer+writecursor;
+			*audiobytes1 = This->buflen-writecursor;
+			if (lplpaudioptr2)
+				*(LPBYTE*)lplpaudioptr2 = This->buffer;
+			if (audiobytes2)
+				*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 @@
 	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 @@
 	}
 #endif
 
+#if USE_DSOUND3D
         if ( IsEqualGUID( &IID_IDirectSound3DListener, riid ) ) {
 		IDirectSound3DListenerImpl* dsl;
 
@@ -1959,6 +2006,14 @@
 
 		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 @@
 	/* 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;
@@ -2851,12 +2906,121 @@
 
 	return len;
 }
+
+static void DSOUND_PhaseCancel(IDirectSoundBufferImpl *dsb, DWORD writepos, DWORD len)
+{
+	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)
+static void DSOUND_MixCancel(IDirectSoundBufferImpl *dsb, DWORD writepos, BOOL cancel)
 {
-	FIXME("prebuffer cancel not implemented yet\n");
+	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)
 {
 	DWORD len, slen;
@@ -2926,12 +3090,11 @@
 			/* 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);
@@ -3001,11 +3164,11 @@
 
 		if (!dsb || !(ICOM_VTBL(dsb)))
 			continue;
- 		if (dsb->buflen && dsb->state && !dsb->hwbuf) {
+		if (dsb->buflen && dsb->state && !dsb->hwbuf) {
 			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 @@
 	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;
-	BOOL forced;
-	HRESULT hres;
 
-	if (!dsound || !primarybuf) {
-		ERR("dsound died without killing us?\n");
-		timeKillEvent(timerID);
-		timeEndPeriod(DS_TIME_RES);
-		return;
+	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;
+
 	EnterCriticalSection(&(dsound->lock));
 
 	if (!primarybuf || !primarybuf->ref) {
@@ -3049,11 +3276,12 @@
 	/* whether the primary is forced to play even without secondary buffers */
 	forced = ((primarybuf->state == STATE_PLAYING) || (primarybuf->state == STATE_STARTING));
 
-	if (primarybuf->hwbuf) {
-		if (dsound->priolevel != DSSCL_WRITEPRIMARY) {
-			BOOL paused = ((primarybuf->state == STATE_STOPPED) || (primarybuf->state == STATE_STARTING));
-			/* FIXME: document variables */
- 			DWORD playpos, writepos, inq, maxq, frag;
+        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,56 +3295,77 @@
 				while (writepos >= primarybuf->buflen)
 					writepos -= primarybuf->buflen;
 			} else writepos = playpos;
-			TRACE("primary playpos=%ld, writepos=%ld, clrpos=%ld, mixpos=%ld\n",
-			      playpos,writepos,primarybuf->playpos,primarybuf->buf_mixpos);
-			/* wipe out just-played sound data */
-			if (playpos < primarybuf->playpos) {
-				memset(primarybuf->buffer + primarybuf->playpos, nfiller, primarybuf->buflen - primarybuf->playpos);
-				memset(primarybuf->buffer, nfiller, playpos);
-			} else {
-				memset(primarybuf->buffer + primarybuf->playpos, nfiller, playpos - primarybuf->playpos);
-			}
-			primarybuf->playpos = playpos;
-
-			/* check how much prebuffering is left */
-			inq = primarybuf->buf_mixpos;
-			if (inq < writepos)
-				inq += primarybuf->buflen;
-			inq -= writepos;
+		}
+		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 */
+		if (playpos < primarybuf->playpos) {
+			memset(primarybuf->buffer + primarybuf->playpos, nfiller, primarybuf->buflen - primarybuf->playpos);
+			memset(primarybuf->buffer, nfiller, playpos);
+		} else {
+			memset(primarybuf->buffer + primarybuf->playpos, nfiller, playpos - primarybuf->playpos);
+		}
+		primarybuf->playpos = playpos;
 
-			/* find the maximum we can prebuffer */
-			if (!paused) {
-				maxq = playpos;
-				if (maxq < writepos)
-					maxq += primarybuf->buflen;
-				maxq -= writepos;
-			} else maxq = primarybuf->buflen;
+		EnterCriticalSection(&(primarybuf->lock));
 
-			/* clip maxq to DS_SND_QUEUE */
-			frag = DS_SND_QUEUE * dsound->fraglen;
-			if (maxq > frag) maxq = frag;
+		/* reset mixing if necessary */
+		DSOUND_CheckReset(dsound, writepos);
 
+		/* check how much prebuffering is left */
+		inq = primarybuf->buf_mixpos;
+		if (inq < writepos)
+			inq += primarybuf->buflen;
+		inq -= writepos;
+
+		/* find the maximum we can prebuffer */
+		if (!paused) {
+			maxq = playpos;
+			if (maxq < writepos)
+				maxq += primarybuf->buflen;
+			maxq -= writepos;
+		} else maxq = primarybuf->buflen;
+
+		/* clip maxq to dsound->prebuf */
+		frag = dsound->prebuf * dsound->fraglen;
+		if (maxq > frag) maxq = frag;
+
+		/* check for consistency */
+		if (inq > maxq) {
+			/* the playback position must have passed our last
+			 * mixed position, i.e. it's an underrun, or we have
+			 * nothing more to play */
+			TRACE("reached end of mixed data (inq=%ld, maxq=%ld)\n", inq, maxq);
+			inq = 0;
+			/* stop the playback now, to allow buffers to refill */
+			if (primarybuf->state == STATE_PLAYING) {
+				primarybuf->state = STATE_STARTING;
+			}
+			else if (primarybuf->state == STATE_STOPPING) {
+				primarybuf->state = STATE_STOPPED;
+			}
+			else {
+				/* 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));
-
-			/* check for consistency */
-			if (inq > maxq) {
-				/* the playback position must have passed our last
-				 * mixed position, i.e. it's an underrun, or we have
-				 * nothing more to play */
-				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;
-				}
-				else if (primarybuf->state == STATE_STOPPING) {
-					primarybuf->state = STATE_STOPPED;
-				}
-				else {
-					/* how can we have an underrun if we aren't playing? */
-					WARN("unexpected primary state (%ld)\n", primarybuf->state);
-				}
+#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,135 +3374,113 @@
 					LeaveCriticalSection(&(primarybuf->lock));
 					return;
 				}
-				writepos = playpos;
-				primarybuf->playpos = playpos;
-				primarybuf->buf_mixpos = writepos;
-				inq = 0;
-				maxq = primarybuf->buflen;
-				if (maxq > frag) maxq = frag;
-				memset(primarybuf->buffer, nfiller, primarybuf->buflen);
-				paused = TRUE;
-			}
-
-			/* do the mixing */
-			frag = DSOUND_MixToPrimary(playpos, writepos, maxq, paused);
-			if (forced) frag = maxq - inq;
-			primarybuf->buf_mixpos += frag;
-			while (primarybuf->buf_mixpos >= primarybuf->buflen)
-				primarybuf->buf_mixpos -= primarybuf->buflen;
-
-			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;
-					TRACE("starting playback\n");
-				}
-			}
-			LeaveCriticalSection(&(primarybuf->lock));
-		} else {
-			/* in the DSSCL_WRITEPRIMARY mode, the app is totally in charge... */
-			if (primarybuf->state == STATE_STARTING) {
-				DSOUND_PrimaryPlay(primarybuf);
-				primarybuf->state = STATE_PLAYING;
-			} 
-			else if (primarybuf->state == STATE_STOPPING) {
-				DSOUND_PrimaryStop(primarybuf);
-				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;
+			} else {
+	 			playpos = dsound->pwplay * dsound->fraglen;
 			}
+			writepos = playpos;
+			primarybuf->playpos = playpos;
+			primarybuf->buf_mixpos = writepos;
+			inq = 0;
+			maxq = primarybuf->buflen;
+			if (maxq > frag) maxq = frag;
+			memset(primarybuf->buffer, nfiller, primarybuf->buflen);
+			paused = TRUE;
 		}
 
-		/* 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;
+		frag = DSOUND_MixToPrimary(playpos, writepos, maxq, paused);
+		if (forced) frag = maxq - inq;
+		primarybuf->buf_mixpos += frag;
+		while (primarybuf->buf_mixpos >= primarybuf->buflen)
+			primarybuf->buf_mixpos -= primarybuf->buflen;
 
-		if (dsound->pwqueue) {
+		if (frag) {
 			/* 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;
+			}
+			LeaveCriticalSection(&(primarybuf->lock));
+			if (paused) {
+				DSOUND_PrimaryPlay(primarybuf);
 				TRACE("starting playback\n");
 			}
 		}
-		LeaveCriticalSection(&(primarybuf->lock));
+		else
+			LeaveCriticalSection(&(primarybuf->lock));
+	} else {
+		/* in the DSSCL_WRITEPRIMARY mode, the app is totally in charge... */
+		if (primarybuf->state == STATE_STARTING) {
+			DSOUND_PrimaryPlay(primarybuf);
+			primarybuf->state = STATE_PLAYING;
+		} 
+		else if (primarybuf->state == STATE_STOPPING) {
+			DSOUND_PrimaryStop(primarybuf);
+			primarybuf->state = STATE_STOPPED;
+		}
 	}
-	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 @@
 	(*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 @@
 	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))





More information about the wine-patches mailing list