From 353c776a66a1b8603c6f0ac502e152da0411d031 Mon Sep 17 00:00:00 2001 From: Ove Kaaven Date: Fri, 23 Jun 2000 15:45:05 +0000 Subject: [PATCH] Restructure DirectSound. Remove dsound thread, use MM timers instead. Implemented the DirectSound HEL, with the ability to prebuffer 300ms of sound, while maintaining play latency of 40ms, and the exact playposition (Starcraft cinematics are lip-synched). Some initial HAL support. --- dlls/dsound/Makefile.in | 1 + dlls/dsound/dsound.spec | 1 + dlls/dsound/dsound_main.c | 1092 ++++++++++++++++++++++--------------- include/dsdriver.h | 2 +- 4 files changed, 664 insertions(+), 432 deletions(-) diff --git a/dlls/dsound/Makefile.in b/dlls/dsound/Makefile.in index e24a9d02acf..ebcd1f370cb 100644 --- a/dlls/dsound/Makefile.in +++ b/dlls/dsound/Makefile.in @@ -4,6 +4,7 @@ SRCDIR = @srcdir@ VPATH = @srcdir@ MODULE = dsound SOVERSION = 1.0 +IMPORTS = winmm C_SRCS = \ dsound_main.c diff --git a/dlls/dsound/dsound.spec b/dlls/dsound/dsound.spec index a00097efbc4..6a278381a10 100644 --- a/dlls/dsound/dsound.spec +++ b/dlls/dsound/dsound.spec @@ -1,5 +1,6 @@ name dsound type win32 +import winmm 0 stub DirectSoundUnknown 1 stdcall DirectSoundCreate(ptr ptr ptr) DirectSoundCreate diff --git a/dlls/dsound/dsound_main.c b/dlls/dsound/dsound_main.c index 19732685da4..9c77b5b1c8a 100644 --- a/dlls/dsound/dsound_main.c +++ b/dlls/dsound/dsound_main.c @@ -2,12 +2,9 @@ * * Copyright 1998 Marcus Meissner * Copyright 1998 Rob Riggs + * Copyright 2000 Ove Kåven, TransGaming Technologies, Inc. */ /* - * Note: This file requires multithread ability. It is not possible to - * implement the stuff in a single thread anyway. And most DirectX apps - * require threading themselves. - * * Most thread locking is complete. There may be a few race * conditions still lurking. * @@ -17,42 +14,13 @@ * TODO: * Implement DirectSoundCapture API * Implement SetCooperativeLevel properly (need to address focus issues) - * Use wavetable synth for static buffers if available * Implement DirectSound3DBuffers (stubs in place) - * Use hardware 3D support if available (OSS support may be needed first) - * Add support for APIs other than OSS: ALSA (http://alsa.jcu.cz/) - * and esound (http://www.gnome.org), for instance + * Use hardware 3D support if available * Add critical section locking inside Release and AddRef methods - * Race conditions exist between DSOUND_WriteAudio and DSOUND_CloseAudio/ - * DSOUND_OpenAudio - * - * FIXME: Status needs updating. - * - * Status: - * - Wing Commander 4/W95: - * The intromovie plays without problems. Nearly lipsynchron. - * - DiscWorld 2 - * The sound works, but noticeable chunks are left out (from the sound and - * the animation). Don't know why yet. - * - Diablo: - * Sound works, but slows down the movieplayer. - * - XvT: - * Doesn't sound yet. - * - Monkey Island 3: - * The background sound of the startscreen works ;) - * - WingCommander Prophecy Demo: - * Sound works for the intromovie. - * - Total Annihilation (1998/12/04): - * Sound plays perfectly in the game, but the Smacker movies - * (http://www.smacker.com/) play silently. - * - A-10 Cuba! Demo (1998/12/04): - * Sound works properly (for some people). - * - dsstream.exe, from DirectX 5.2 SDK (1998/12/04): - * Works properly, but requires "-dll -winmm". - * - dsshow.exe, from DirectX 5.2 SDK (1998/12/04): - * Initializes the DLL properly with CoCreateInstance(), but the - * FileOpen dialog box is broken - could not test properly - * - The Dilbert Desktop Game: works (SB16) + * Handle static buffers - put those in hardware, non-static not in hardware + * Hardware DuplicateSoundBuffer + * Proper volume calculation, and setting volume in HEL primary buffer + * Optimize WINMM and negotiate fragment size, decrease DS_HEL_MARGIN */ #include "config.h" @@ -67,16 +35,29 @@ #include #include /* Insomnia - pow() function */ #include "dsound.h" +#include "dsdriver.h" #include "windef.h" #include "wingdi.h" #include "winuser.h" #include "winerror.h" +#include "mmsystem.h" #include "wine/obj_base.h" #include "thread.h" #include "debugtools.h" 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 */ +#define DS_HEL_FRAGS 48 /* HEL only: number of waveOut fragments in primary buffer */ +#define DS_HEL_QUEUE 28 /* HEL only: number of waveOut fragments to prebuffer */ + /* (Starcraft videos won't work with higher than 32 x10ms) */ +#define DS_HEL_MARGIN 4 /* HEL only: number of waveOut fragments ahead to mix in new buffers */ + +/* Linux does not support better timing than 10ms */ +#define DS_TIME_RES 10 /* Resolution of multimedia timer */ +#define DS_TIME_DEL 10 /* Delay of multimedia timer callback, and duration of HEL fragment */ /***************************************************************************** * Predeclare the interface implementation structures @@ -98,6 +79,13 @@ struct IDirectSoundImpl ICOM_VFIELD(IDirectSound); DWORD ref; /* IDirectSoundImpl fields */ + PIDSDRIVER driver; + DSDRIVERDESC drvdesc; + DSDRIVERCAPS drvcaps; + HWAVEOUT hwo; + LPWAVEHDR pwave[DS_HEL_FRAGS]; + UINT timerID, pwplay, pwwrite, pwqueue; + DWORD fraglen; DWORD priolevel; int nrofbuffers; IDirectSoundBufferImpl** buffers; @@ -116,16 +104,16 @@ struct IDirectSoundBufferImpl ICOM_VFIELD(IDirectSoundBuffer); DWORD ref; /* IDirectSoundBufferImpl fields */ + PIDSDRIVERBUFFER hwbuf; WAVEFORMATEX wfx; LPBYTE buffer; IDirectSound3DBufferImpl* ds3db; - DWORD playflags,playing; - DWORD playpos,writepos,buflen; + DWORD playflags,state,leadin; + DWORD playpos,mixpos,writelead,buflen; DWORD nAvgBytesPerSec; DWORD freq; ULONG freqAdjust; - LONG volume,pan; - LONG lVolAdjust,rVolAdjust; + DSVOLUMEPAN volpan; IDirectSoundBufferImpl* parent; /* for duplicates */ IDirectSoundImpl* dsound; DSBUFFERDESC dsbd; @@ -134,6 +122,11 @@ struct IDirectSoundBufferImpl CRITICAL_SECTION lock; }; +#define STATE_STOPPED 0 +#define STATE_STARTING 1 +#define STATE_PLAYING 2 +#define STATE_STOPPING 3 + /***************************************************************************** * IDirectSoundNotify implementation structure */ @@ -204,35 +197,15 @@ struct IDirectSoundCaptureBufferImpl }; -#ifdef HAVE_OSS -# include -# ifdef HAVE_MACHINE_SOUNDCARD_H -# include -# endif -# ifdef HAVE_SYS_SOUNDCARD_H -# include -# endif -# ifdef HAVE_SOUNDCARD_H -# include -# endif - /* #define USE_DSOUND3D 1 */ -#define DSOUND_FRAGLEN ((primarybuf->wfx.nAvgBytesPerSec >> 4) & ~3) #define DSOUND_FREQSHIFT (14) -static int audiofd = -1; -static int audioOK = 0; - static IDirectSoundImpl* dsound = NULL; static IDirectSoundBufferImpl* primarybuf = NULL; -static int DSOUND_setformat(LPWAVEFORMATEX wfex); static void DSOUND_CheckEvent(IDirectSoundBufferImpl *dsb, int len); -static void DSOUND_CloseAudio(void); - -#endif static HRESULT DSOUND_CreateDirectSoundCapture( LPVOID* ppobj ); static HRESULT DSOUND_CreateDirectSoundCaptureBuffer( LPCDSCBUFFERDESC lpcDSCBufferDesc, LPVOID* ppobj ); @@ -240,6 +213,27 @@ static HRESULT DSOUND_CreateDirectSoundCaptureBuffer( LPCDSCBUFFERDESC lpcDSCBuf static ICOM_VTABLE(IDirectSoundCapture) dscvt; static ICOM_VTABLE(IDirectSoundCaptureBuffer) dscbvt; +static HRESULT mmErr(UINT err) +{ + switch(err) { + case MMSYSERR_NOERROR: + return DS_OK; + case MMSYSERR_ALLOCATED: + return DSERR_ALLOCATED; + case MMSYSERR_INVALHANDLE: + return DSERR_GENERIC; /* FIXME */ + case MMSYSERR_NODRIVER: + return DSERR_NODRIVER; + case MMSYSERR_NOMEM: + return DSERR_OUTOFMEMORY; + case MMSYSERR_INVALPARAM: + return DSERR_INVALIDPARAM; + default: + FIXME("Unknown MMSYS error %d\n",err); + return DSERR_GENERIC; + } +} + /*************************************************************************** * DirectSoundEnumerateA [DSOUND.2] * @@ -285,7 +279,6 @@ HRESULT WINAPI DirectSoundEnumerateW( } -#ifdef HAVE_OSS static void _dump_DSBCAPS(DWORD xmask) { struct { DWORD mask; @@ -880,6 +873,112 @@ static ICOM_VTABLE(IDirectSoundNotify) dsnvt = * IDirectSoundBuffer */ +static void DSOUND_RecalcVolPan(PDSVOLUMEPAN volpan) +{ + double temp; + + /* the AmpFactors are expressed in 16.16 fixed point */ + volpan->dwVolAmpFactor = (ULONG) (pow(2.0, volpan->lVolume / 600.0) * 65536); + /* FIXME: dwPan{Left|Right}AmpFactor */ + + /* FIXME: use calculated vol and pan ampfactors */ + temp = (double) (volpan->lVolume - (volpan->lPan > 0 ? volpan->lPan : 0)); + volpan->dwTotalLeftAmpFactor = (ULONG) (pow(2.0, temp / 600.0) * 65536); + temp = (double) (volpan->lVolume + (volpan->lPan < 0 ? volpan->lPan : 0)); + volpan->dwTotalRightAmpFactor = (ULONG) (pow(2.0, temp / 600.0) * 65536); + + TRACE("left = %lx, right = %lx\n", volpan->dwTotalLeftAmpFactor, volpan->dwTotalRightAmpFactor); +} + +static void DSOUND_RecalcFormat(IDirectSoundBufferImpl *dsb) +{ + DWORD sw; + + sw = dsb->wfx.nChannels * (dsb->wfx.wBitsPerSample / 8); + if ((dsb->dsbd.dwFlags & DSBCAPS_PRIMARYBUFFER) && dsb->hwbuf) { + DWORD fraglen; + /* let fragment size approximate the timer delay */ + fraglen = (dsb->freq * DS_TIME_DEL / 1000) * sw; + /* reduce fragment size until an integer number of them fits in the buffer */ + /* (FIXME: this may or may not be a good idea) */ + while (dsb->buflen % fraglen) fraglen -= sw; + dsb->dsound->fraglen = fraglen; + TRACE("fraglen=%ld\n", dsb->dsound->fraglen); + } + /* calculate the 10ms write lead */ + dsb->writelead = (dsb->freq / 100) * sw; +} + +static HRESULT DSOUND_PrimaryOpen(IDirectSoundBufferImpl *dsb) +{ + HRESULT err = DS_OK; + + /* are we using waveOut stuff? */ + if (!dsb->hwbuf) { + LPBYTE newbuf; + DWORD buflen; + HRESULT merr = DS_OK; + /* use fragments of 10ms (1/100s) each (which should get us within + * the documented write cursor lead of 10-15ms) */ + buflen = ((dsb->wfx.nAvgBytesPerSec / 100) & ~3) * DS_HEL_FRAGS; + TRACE("desired buflen=%ld, old buffer=%p\n", buflen, dsb->buffer); + /* reallocate emulated primary buffer */ + newbuf = (LPBYTE)HeapReAlloc(GetProcessHeap(),0,dsb->buffer,buflen); + if (newbuf == NULL) { + ERR("failed to allocate primary buffer\n"); + merr = DSERR_OUTOFMEMORY; + /* but the old buffer might still exists and must be re-prepared */ + } else { + dsb->buffer = newbuf; + dsb->buflen = buflen; + } + if (dsb->buffer) { + unsigned c; + IDirectSoundImpl *ds = dsb->dsound; + + ds->fraglen = dsb->buflen / DS_HEL_FRAGS; + + /* prepare fragment headers */ + for (c=0; cpwave[c]->lpData = dsb->buffer + c*ds->fraglen; + ds->pwave[c]->dwBufferLength = ds->fraglen; + ds->pwave[c]->dwUser = (DWORD)dsb; + ds->pwave[c]->dwFlags = 0; + ds->pwave[c]->dwLoops = 0; + err = mmErr(waveOutPrepareHeader(ds->hwo,ds->pwave[c],sizeof(WAVEHDR))); + if (err != DS_OK) { + while (c--) + waveOutUnprepareHeader(ds->hwo,ds->pwave[c],sizeof(WAVEHDR)); + break; + } + } + + ds->pwplay = 0; + ds->pwwrite = 0; + ds->pwqueue = 0; + memset(dsb->buffer, (dsb->wfx.wBitsPerSample == 16) ? 0 : 128, dsb->buflen); + TRACE("fraglen=%ld\n", ds->fraglen); + } + if ((err == DS_OK) && (merr != DS_OK)) + err = merr; + } + return err; +} + + +static void DSOUND_PrimaryClose(IDirectSoundBufferImpl *dsb) +{ + /* are we using waveOut stuff? */ + if (!dsb->hwbuf) { + unsigned c; + IDirectSoundImpl *ds = dsb->dsound; + + waveOutReset(ds->hwo); + for (c=0; chwo, ds->pwave[c], sizeof(WAVEHDR)); + } +} + /* This sets this format for the Primary Buffer Only */ /* See file:///cdrom/sdk52/docs/worddoc/dsound.doc page 120 */ static HRESULT WINAPI IDirectSoundBufferImpl_SetFormat( @@ -927,7 +1026,20 @@ static HRESULT WINAPI IDirectSoundBufferImpl_SetFormat( primarybuf->wfx.nAvgBytesPerSec = This->wfx.nSamplesPerSec * This->wfx.nBlockAlign; - DSOUND_CloseAudio(); + 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, 0); + /* Start in pause mode until buffers are filled */ + waveOutPause(This->dsound->hwo); + DSOUND_PrimaryOpen(primarybuf); + } + if (primarybuf->hwbuf) + IDsDriverBuffer_SetFormat(primarybuf->hwbuf, &(primarybuf->wfx)); + DSOUND_RecalcFormat(primarybuf); LeaveCriticalSection(&(This->dsound->lock)); /* **** */ @@ -939,7 +1051,6 @@ static HRESULT WINAPI IDirectSoundBufferImpl_SetVolume( LPDIRECTSOUNDBUFFER iface,LONG vol ) { ICOM_THIS(IDirectSoundBufferImpl,iface); - double temp; TRACE("(%p,%ld)\n",This,vol); @@ -950,29 +1061,29 @@ static HRESULT WINAPI IDirectSoundBufferImpl_SetVolume( if ((vol > DSBVOLUME_MAX) || (vol < DSBVOLUME_MIN)) return DSERR_INVALIDPARAM; - /* This needs to adjust the soundcard volume when */ - /* called for the primary buffer */ - if (This->dsbd.dwFlags & DSBCAPS_PRIMARYBUFFER) { - FIXME("Volume control of primary unimplemented.\n"); - This->volume = vol; - return DS_OK; - } - /* **** */ EnterCriticalSection(&(This->lock)); - This->volume = vol; + This->volpan.lVolume = vol; - temp = (double) (This->volume - (This->pan > 0 ? This->pan : 0)); - This->lVolAdjust = (ULONG) (pow(2.0, temp / 600.0) * 32768.0); - temp = (double) (This->volume + (This->pan < 0 ? This->pan : 0)); - This->rVolAdjust = (ULONG) (pow(2.0, temp / 600.0) * 32768.0); + DSOUND_RecalcVolPan(&(This->volpan)); + + if (This->hwbuf) { + IDsDriverBuffer_SetVolumePan(This->hwbuf, &(This->volpan)); + } + else if (This->dsbd.dwFlags & DSBCAPS_PRIMARYBUFFER) { +#if 0 /* should we really do this? */ + /* the DS volume ranges from 0 (max, 0dB attenuation) to -10000 (min, 100dB attenuation) */ + /* the MM volume ranges from 0 to 0xffff in an unspecified logarithmic scale */ + WORD cvol = 0xffff + vol*6 + vol/2; + DWORD vol = cvol | ((DWORD)cvol << 16) + waveOutSetVolume(This->dsound->hwo, vol); +#endif + } LeaveCriticalSection(&(This->lock)); /* **** */ - TRACE("left = %lx, right = %lx\n", This->lVolAdjust, This->rVolAdjust); - return DS_OK; } @@ -985,7 +1096,7 @@ static HRESULT WINAPI IDirectSoundBufferImpl_GetVolume( if (vol == NULL) return DSERR_INVALIDPARAM; - *vol = This->volume; + *vol = This->volpan.lVolume; return DS_OK; } @@ -1000,6 +1111,8 @@ static HRESULT WINAPI IDirectSoundBufferImpl_SetFrequency( (This->dsbd.dwFlags & DSBCAPS_PRIMARYBUFFER)) return DSERR_CONTROLUNAVAIL; + if (!freq) freq = This->wfx.nSamplesPerSec; + if ((freq < DSBFREQUENCY_MIN) || (freq > DSBFREQUENCY_MAX)) return DSERR_INVALIDPARAM; @@ -1009,6 +1122,7 @@ static HRESULT WINAPI IDirectSoundBufferImpl_SetFrequency( This->freq = freq; This->freqAdjust = (freq << DSOUND_FREQSHIFT) / primarybuf->wfx.nSamplesPerSec; This->nAvgBytesPerSec = freq * This->wfx.nBlockAlign; + DSOUND_RecalcFormat(This); LeaveCriticalSection(&(This->lock)); /* **** */ @@ -1024,7 +1138,15 @@ static HRESULT WINAPI IDirectSoundBufferImpl_Play( This,reserved1,reserved2,flags ); This->playflags = flags; - This->playing = 1; + if (This->state == STATE_STOPPED) { + This->state = STATE_STARTING; + This->leadin = TRUE; + } else if (This->state == STATE_STOPPING) + This->state = STATE_PLAYING; + if (!(This->dsbd.dwFlags & DSBCAPS_PRIMARYBUFFER) && This->hwbuf) { + IDsDriverBuffer_Play(This->hwbuf, 0, 0, This->playflags); + This->state = STATE_PLAYING; + } return DS_OK; } @@ -1036,7 +1158,14 @@ static HRESULT WINAPI IDirectSoundBufferImpl_Stop(LPDIRECTSOUNDBUFFER iface) /* **** */ EnterCriticalSection(&(This->lock)); - This->playing = 0; + if (This->state == STATE_PLAYING) + This->state = STATE_STOPPING; + else if (This->state == STATE_STARTING) + This->state = STATE_STOPPED; + if (!(This->dsbd.dwFlags & DSBCAPS_PRIMARYBUFFER) && This->hwbuf) { + IDsDriverBuffer_Stop(This->hwbuf); + This->state = STATE_STOPPED; + } DSOUND_CheckEvent(This, 0); LeaveCriticalSection(&(This->lock)); @@ -1075,6 +1204,11 @@ static DWORD WINAPI IDirectSoundBufferImpl_Release(LPDIRECTSOUNDBUFFER iface) { LeaveCriticalSection(&(This->dsound->lock)); DeleteCriticalSection(&(This->lock)); + if (This->dsbd.dwFlags & DSBCAPS_PRIMARYBUFFER) + DSOUND_PrimaryClose(This); + if (This->hwbuf) { + IDsDriverBuffer_Release(This->hwbuf); + } if (This->ds3db) IDirectSound3DBuffer_Release((LPDIRECTSOUND3DBUFFER)This->ds3db); if (This->parent) @@ -1097,8 +1231,82 @@ static HRESULT WINAPI IDirectSoundBufferImpl_GetCurrentPosition( ) { ICOM_THIS(IDirectSoundBufferImpl,iface); TRACE("(%p,%p,%p)\n",This,playpos,writepos); - if (playpos) *playpos = This->playpos; - if (writepos) *writepos = This->writepos; + if (This->hwbuf) { + IDsDriverBuffer_GetPosition(This->hwbuf, playpos, writepos); + } + else if (This->dsbd.dwFlags & DSBCAPS_PRIMARYBUFFER) { + if (playpos && (This->dsbd.dwFlags & DSBCAPS_GETCURRENTPOSITION2)) { + MMTIME mtime; + mtime.wType = TIME_BYTES; + waveOutGetPosition(This->dsound->hwo, &mtime, sizeof(mtime)); + mtime.u.cb = mtime.u.cb % This->buflen; + *playpos = mtime.u.cb; + } + /* don't know how exactly non-GETCURRENTPOSITION2 behaves, + * but I think this works for Starcraft */ + else if (playpos) *playpos = This->playpos; + 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; + while (*writepos >= This->buflen) + *writepos -= This->buflen; + } + } else { + if (playpos && (This->state != STATE_PLAYING)) { + /* we haven't been merged into the primary buffer (yet) */ + *playpos = 0; + } + else if (playpos) { + DWORD pplay, lplay, splay; + /* let's get this exact; first, recursively call GetPosition on the primary */ + EnterCriticalSection(&(primarybuf->lock)); + if ((This->dsbd.dwFlags & DSBCAPS_GETCURRENTPOSITION2) || primarybuf->hwbuf) { + IDirectSoundBufferImpl_GetCurrentPosition((LPDIRECTSOUNDBUFFER)primarybuf, &pplay, NULL); + } else { + /* (unless the app isn't using GETCURRENTPOSITION2) */ + /* don't know exactly how this should be handled either */ + pplay = primarybuf->playpos; + } + /* get last mixed primary play position */ + lplay = primarybuf->mixpos; + /* get our own last mixed position while we still have the lock */ + splay = This->mixpos; + LeaveCriticalSection(&(primarybuf->lock)); + TRACE("primary playpos=%ld, mixpos=%ld\n", pplay, lplay); + TRACE("this mixpos=%ld\n", splay); + + /* the actual primary play position (pplay) is always behind last mixed (lplay) + * (unless the computer is too slow, which we can't fix anyway) */ + /* we need to know how far away we are from there */ + if (lplay < pplay) lplay += primarybuf->buflen; /* wraparound */ + lplay -= pplay; + /* divide the offset by its sample size */ + lplay /= primarybuf->wfx.nChannels * (primarybuf->wfx.wBitsPerSample / 8); + TRACE("primary back-samples=%ld\n",lplay); + /* adjust for our frequency */ + lplay = (lplay * This->freqAdjust) >> DSOUND_FREQSHIFT; + /* multiply by our own sample size */ + lplay *= This->wfx.nChannels * (This->wfx.wBitsPerSample / 8); + TRACE("this back-offset=%ld\n", lplay); + /* subtract from our last mixed position */ + if ((splay < lplay) && This->leadin) { + /* seems we haven't started playing yet */ + splay = 0; + } else { + while (splay < lplay) splay += This->buflen; /* wraparound */ + splay -= lplay; + } + /* return the result */ + *playpos = splay; + } + if (writepos) *writepos = This->mixpos; + } + /* apply the documented 10ms lead to writepos */ + if (writepos) { + *writepos += This->writelead; + while (*writepos >= This->buflen) *writepos -= This->buflen; + } TRACE("playpos = %ld, writepos = %ld\n", playpos?*playpos:0, writepos?*writepos:0); return DS_OK; } @@ -1113,7 +1321,7 @@ static HRESULT WINAPI IDirectSoundBufferImpl_GetStatus( return DSERR_INVALIDPARAM; *status = 0; - if (This->playing) + if ((This->state == STATE_STARTING) || (This->state == STATE_PLAYING)) *status |= DSBSTATUS_PLAYING; if (This->playflags & DSBPLAY_LOOPING) *status |= DSBSTATUS_LOOPING; @@ -1147,6 +1355,7 @@ static HRESULT WINAPI IDirectSoundBufferImpl_Lock( LPDIRECTSOUNDBUFFER iface,DWORD writecursor,DWORD writebytes,LPVOID lplpaudioptr1,LPDWORD audiobytes1,LPVOID lplpaudioptr2,LPDWORD audiobytes2,DWORD flags ) { ICOM_THIS(IDirectSoundBufferImpl,iface); + DWORD capf; TRACE("(%p,%ld,%ld,%p,%p,%p,%p,0x%08lx)\n", This, @@ -1158,8 +1367,13 @@ static HRESULT WINAPI IDirectSoundBufferImpl_Lock( audiobytes2, flags ); - if (flags & DSBLOCK_FROMWRITECURSOR) - writecursor += This->writepos; + + if (flags & DSBLOCK_FROMWRITECURSOR) { + DWORD writepos; + /* GetCurrentPosition does too much magic to duplicate here */ + IDirectSoundBufferImpl_GetCurrentPosition(iface, NULL, &writepos); + writecursor += writepos; + } if (flags & DSBLOCK_ENTIREBUFFER) writebytes = This->buflen; if (writebytes > This->buflen) @@ -1167,6 +1381,18 @@ static HRESULT WINAPI IDirectSoundBufferImpl_Lock( assert(audiobytes1!=audiobytes2); assert(lplpaudioptr1!=lplpaudioptr2); + + if (This->dsbd.dwFlags & DSBCAPS_PRIMARYBUFFER) + capf = DSDDESC_DONTNEEDPRIMARYLOCK; + else + capf = DSDDESC_DONTNEEDSECONDARYLOCK; + if (!(This->dsound->drvdesc.dwFlags & capf) && This->hwbuf) { + IDsDriverBuffer_Lock(This->hwbuf, + lplpaudioptr1, audiobytes1, + lplpaudioptr2, audiobytes2, + writecursor, writebytes, + 0); + } else if (writecursor+writebytes <= This->buflen) { *(LPBYTE*)lplpaudioptr1 = This->buffer+writecursor; *audiobytes1 = writebytes; @@ -1184,8 +1410,6 @@ static HRESULT WINAPI IDirectSoundBufferImpl_Lock( *audiobytes2 = writebytes-(This->buflen-writecursor); TRACE("->%ld.%ld\n",*audiobytes1,audiobytes2?*audiobytes2:0); } - /* No. See file:///cdrom/sdk52/docs/worddoc/dsound.doc page 21 */ - /* This->writepos=(writecursor+writebytes)%This->buflen; */ return DS_OK; } @@ -1198,7 +1422,9 @@ static HRESULT WINAPI IDirectSoundBufferImpl_SetCurrentPosition( /* **** */ EnterCriticalSection(&(This->lock)); - This->playpos = newpos; + This->mixpos = newpos; + if (This->hwbuf) + IDsDriverBuffer_SetPosition(This->hwbuf, This->mixpos); LeaveCriticalSection(&(This->lock)); /* **** */ @@ -1210,7 +1436,6 @@ static HRESULT WINAPI IDirectSoundBufferImpl_SetPan( LPDIRECTSOUNDBUFFER iface,LONG pan ) { ICOM_THIS(IDirectSoundBufferImpl,iface); - double temp; TRACE("(%p,%ld)\n",This,pan); @@ -1227,12 +1452,13 @@ static HRESULT WINAPI IDirectSoundBufferImpl_SetPan( /* **** */ EnterCriticalSection(&(This->lock)); - This->pan = pan; - - temp = (double) (This->volume - (This->pan > 0 ? This->pan : 0)); - This->lVolAdjust = (ULONG) (pow(2.0, temp / 600.0) * 32768.0); - temp = (double) (This->volume + (This->pan < 0 ? This->pan : 0)); - This->rVolAdjust = (ULONG) (pow(2.0, temp / 600.0) * 32768.0); + This->volpan.lPan = pan; + + DSOUND_RecalcVolPan(&(This->volpan)); + + if (This->hwbuf) { + IDsDriverBuffer_SetVolumePan(This->hwbuf, &(This->volpan)); + } LeaveCriticalSection(&(This->lock)); /* **** */ @@ -1249,7 +1475,7 @@ static HRESULT WINAPI IDirectSoundBufferImpl_GetPan( if (pan == NULL) return DSERR_INVALIDPARAM; - *pan = This->pan; + *pan = This->volpan.lPan; return DS_OK; } @@ -1258,22 +1484,26 @@ static HRESULT WINAPI IDirectSoundBufferImpl_Unlock( LPDIRECTSOUNDBUFFER iface,LPVOID p1,DWORD x1,LPVOID p2,DWORD x2 ) { ICOM_THIS(IDirectSoundBufferImpl,iface); + DWORD capf; + TRACE("(%p,%p,%ld,%p,%ld):stub\n", This,p1,x1,p2,x2); - /* There is really nothing to do here. Should someone */ - /* choose to implement static buffers in hardware (by */ - /* using a wave table synth, for example) this is where */ - /* you'd want to do the loading. For software buffers, */ - /* which is what we currently use, we need do nothing. */ - #if 0 - /* It's also the place to pre-process 3D buffers... */ - + /* Preprocess 3D buffers... */ + /* This is highly experimental and liable to break things */ if (This->dsbd.dwFlags & DSBCAPS_CTRL3D) DSOUND_Create3DBuffer(This); #endif + if (This->dsbd.dwFlags & DSBCAPS_PRIMARYBUFFER) + capf = DSDDESC_DONTNEEDPRIMARYLOCK; + else + capf = DSDDESC_DONTNEEDSECONDARYLOCK; + if (!(This->dsound->drvdesc.dwFlags & capf) && This->hwbuf) { + IDsDriverBuffer_Unlock(This->hwbuf, p1, x1, p2, x2); + } + return DS_OK; } @@ -1314,14 +1544,19 @@ static HRESULT WINAPI IDirectSoundBufferImpl_GetCaps( /* Inside DirectX, p215. That should apply here, too. */ caps->dwSize = sizeof(*caps); - caps->dwFlags = This->dsbd.dwFlags | DSBCAPS_LOCSOFTWARE; + caps->dwFlags = This->dsbd.dwFlags; + if (This->hwbuf) caps->dwFlags |= DSBCAPS_LOCHARDWARE; + else caps->dwFlags |= DSBCAPS_LOCSOFTWARE; + caps->dwBufferBytes = This->dsbd.dwBufferBytes; + /* This value represents the speed of the "unlock" command. As unlock is quite fast (it does not do anything), I put 4096 ko/s = 4 Mo / s */ + /* FIXME: hwbuf speed */ caps->dwUnlockTransferRate = 4096; caps->dwPlayCpuOverhead = 0; - + return DS_OK; } @@ -1486,6 +1721,7 @@ static HRESULT WINAPI IDirectSoundImpl_CreateSoundBuffer( ICOM_THIS(IDirectSoundImpl,iface); IDirectSoundBufferImpl** ippdsb=(IDirectSoundBufferImpl**)ppdsb; LPWAVEFORMATEX wfex; + HRESULT err = DS_OK; TRACE("(%p,%p,%p,%p)\n",This,dsbd,ippdsb,lpunk); @@ -1516,39 +1752,87 @@ static HRESULT WINAPI IDirectSoundImpl_CreateSoundBuffer( *ippdsb = primarybuf; primarybuf->dsbd.dwFlags = dsbd->dwFlags; return DS_OK; - } /* Else create primarybuf */ + } /* Else create primary buffer */ } *ippdsb = (IDirectSoundBufferImpl*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(IDirectSoundBufferImpl)); if (*ippdsb == NULL) return DSERR_OUTOFMEMORY; + ICOM_VTBL(*ippdsb) = &dsbvt; (*ippdsb)->ref = 1; + (*ippdsb)->dsound = This; + (*ippdsb)->parent = NULL; + (*ippdsb)->buffer = NULL; + + memcpy(&((*ippdsb)->dsbd),dsbd,sizeof(*dsbd)); + if (dsbd->lpwfxFormat) + memcpy(&((*ippdsb)->wfx), dsbd->lpwfxFormat, sizeof((*ippdsb)->wfx)); TRACE("Created buffer at %p\n", *ippdsb); if (dsbd->dwFlags & DSBCAPS_PRIMARYBUFFER) { (*ippdsb)->buflen = dsound->wfx.nAvgBytesPerSec; (*ippdsb)->freq = dsound->wfx.nSamplesPerSec; + + /* FIXME: verify that hardware capabilities (DSCAPS_PRIMARY flags) match */ + + if (This->driver) { + err = IDsDriver_CreateSoundBuffer(This->driver,wfex,dsbd->dwFlags,0, + &((*ippdsb)->buflen),&((*ippdsb)->buffer), + (LPVOID*)&((*ippdsb)->hwbuf)); + } + if (err == DS_OK) + err = DSOUND_PrimaryOpen(*ippdsb); } else { + DWORD capf = 0; + int use_hw; + (*ippdsb)->buflen = dsbd->dwBufferBytes; (*ippdsb)->freq = dsbd->lpwfxFormat->nSamplesPerSec; + + /* Check necessary hardware mixing capabilities */ + if (wfex->nChannels==2) capf |= DSCAPS_SECONDARYSTEREO; + else capf |= DSCAPS_SECONDARYMONO; + if (wfex->wBitsPerSample==16) capf |= DSCAPS_SECONDARY16BIT; + else capf |= DSCAPS_SECONDARY8BIT; + use_hw = (This->drvcaps.dwFlags & capf) == capf; + + /* FIXME: check hardware sample rate mixing capabilities */ + /* FIXME: check app hints for software/hardware buffer (STATIC, LOCHARDWARE, etc) */ + /* FIXME: check whether any hardware buffers are left */ + /* FIXME: handle DSDHEAP_CREATEHEAP for hardware buffers */ + + /* Allocate system memory if applicable */ + if ((This->drvdesc.dwFlags & DSDDESC_USESYSTEMMEMORY) || !use_hw) { + (*ippdsb)->buffer = (LPBYTE)HeapAlloc(GetProcessHeap(),0,(*ippdsb)->buflen); + if ((*ippdsb)->buffer == NULL) + err = DSERR_OUTOFMEMORY; + } + + /* Allocate the hardware buffer */ + if (use_hw && (err == DS_OK)) { + err = IDsDriver_CreateSoundBuffer(This->driver,wfex,dsbd->dwFlags,0, + &((*ippdsb)->buflen),&((*ippdsb)->buffer), + (LPVOID*)&((*ippdsb)->hwbuf)); + } } - (*ippdsb)->buffer = (LPBYTE)HeapAlloc(GetProcessHeap(),0,(*ippdsb)->buflen); - if ((*ippdsb)->buffer == NULL) { + + if (err != DS_OK) { + if ((*ippdsb)->buffer) + HeapFree(GetProcessHeap(),0,(*ippdsb)->buffer); HeapFree(GetProcessHeap(),0,(*ippdsb)); *ippdsb = NULL; - return DSERR_OUTOFMEMORY; + return err; } + /* calculate fragment size and write lead */ + DSOUND_RecalcFormat(*ippdsb); + /* It's not necessary to initialize values to zero since */ /* we allocated this structure with HEAP_ZERO_MEMORY... */ (*ippdsb)->playpos = 0; - (*ippdsb)->writepos = 0; - (*ippdsb)->parent = NULL; - ICOM_VTBL(*ippdsb) = &dsbvt; - (*ippdsb)->dsound = This; - (*ippdsb)->playing = 0; - (*ippdsb)->lVolAdjust = (1 << 15); - (*ippdsb)->rVolAdjust = (1 << 15); + (*ippdsb)->mixpos = 0; + (*ippdsb)->state = STATE_STOPPED; + DSOUND_RecalcVolPan(&((*ippdsb)->volpan)); if (!(dsbd->dwFlags & DSBCAPS_PRIMARYBUFFER)) { (*ippdsb)->freqAdjust = ((*ippdsb)->freq << DSOUND_FREQSHIFT) / @@ -1557,8 +1841,6 @@ static HRESULT WINAPI IDirectSoundImpl_CreateSoundBuffer( dsbd->lpwfxFormat->nBlockAlign; } - memcpy(&((*ippdsb)->dsbd),dsbd,sizeof(*dsbd)); - EnterCriticalSection(&(This->lock)); /* register buffer */ if (!(dsbd->dwFlags & DSBCAPS_PRIMARYBUFFER)) { @@ -1570,9 +1852,6 @@ static HRESULT WINAPI IDirectSoundImpl_CreateSoundBuffer( IDirectSound_AddRef(iface); - if (dsbd->lpwfxFormat) - memcpy(&((*ippdsb)->wfx), dsbd->lpwfxFormat, sizeof((*ippdsb)->wfx)); - InitializeCriticalSection(&((*ippdsb)->lock)); #if USE_DSOUND3D @@ -1626,13 +1905,17 @@ static HRESULT WINAPI IDirectSoundImpl_DuplicateSoundBuffer( IDirectSoundBufferImpl** ippdsb=(IDirectSoundBufferImpl**)ppdsb; TRACE("(%p,%p,%p)\n",This,ipdsb,ippdsb); + if (ipdsb->hwbuf) { + FIXME("need to duplicate hardware buffer\n"); + } + *ippdsb = (IDirectSoundBufferImpl*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(IDirectSoundBufferImpl)); IDirectSoundBuffer_AddRef(pdsb); memcpy(*ippdsb, ipdsb, sizeof(IDirectSoundBufferImpl)); (*ippdsb)->ref = 1; (*ippdsb)->playpos = 0; - (*ippdsb)->writepos = 0; + (*ippdsb)->mixpos = 0; (*ippdsb)->dsound = This; (*ippdsb)->parent = ipdsb; memcpy(&((*ippdsb)->wfx), &(ipdsb->wfx), sizeof((*ippdsb)->wfx)); @@ -1658,13 +1941,9 @@ static HRESULT WINAPI IDirectSoundImpl_GetCaps(LPDIRECTSOUND iface,LPDSCAPS caps /* We should check this value, not set it. See Inside DirectX, p215. */ caps->dwSize = sizeof(*caps); - caps->dwFlags = - DSCAPS_PRIMARYSTEREO | - DSCAPS_PRIMARY16BIT | - DSCAPS_SECONDARYSTEREO | - DSCAPS_SECONDARY16BIT | - DSCAPS_CONTINUOUSRATE; - /* FIXME: query OSS */ + caps->dwFlags = This->drvcaps.dwFlags; + + /* FIXME: copy caps from This->drvcaps */ caps->dwMinSecondarySampleRate = DSBFREQUENCY_MIN; caps->dwMaxSecondarySampleRate = DSBFREQUENCY_MAX; @@ -1709,7 +1988,9 @@ static ULONG WINAPI IDirectSoundImpl_Release(LPDIRECTSOUND iface) { TRACE("(%p), ref was %ld\n",This,This->ref); if (!--(This->ref)) { UINT i; - DSOUND_CloseAudio(); + + timeKillEvent(This->timerID); + timeEndPeriod(DS_TIME_RES); IDirectSoundBuffer_Release((LPDIRECTSOUNDBUFFER)primarybuf); @@ -1722,6 +2003,19 @@ static ULONG WINAPI IDirectSoundImpl_Release(LPDIRECTSOUND iface) { IDirectSoundBuffer_Release((LPDIRECTSOUNDBUFFER)This->primary); DeleteCriticalSection(&This->lock); + if (This->driver) { + IDsDriver_Close(This->driver); + } else { + unsigned c; + for (c=0; cpwave[c]); + } + if (This->drvdesc.dwFlags & DSDDESC_DOMMSYSTEMOPEN) { + waveOutClose(This->hwo); + } + if (This->driver) + IDsDriver_Release(This->driver); + HeapFree(GetProcessHeap(),0,This); dsound = NULL; return 0; @@ -1829,62 +2123,6 @@ static ICOM_VTABLE(IDirectSound) dsvt = }; -/* See http://www.opensound.com/pguide/audio.html for more details */ - -static int -DSOUND_setformat(LPWAVEFORMATEX wfex) { - int xx,channels,speed,format,nformat; - - if (!audioOK) { - TRACE("(%p) deferred\n", wfex); - return 0; - } - switch (wfex->wFormatTag) { - default: - WARN("unknown WAVE_FORMAT tag %d\n",wfex->wFormatTag); - return DSERR_BADFORMAT; - case WAVE_FORMAT_PCM: - break; - } - if (wfex->wBitsPerSample==8) - format = AFMT_U8; - else - format = AFMT_S16_LE; - - if (-1==ioctl(audiofd,SNDCTL_DSP_GETFMTS,&xx)) { - perror("ioctl SNDCTL_DSP_GETFMTS"); - return -1; - } - if ((xx&format)!=format) {/* format unsupported */ - FIXME("SNDCTL_DSP_GETFMTS: format not supported\n"); - return -1; - } - nformat = format; - if (-1==ioctl(audiofd,SNDCTL_DSP_SETFMT,&nformat)) { - perror("ioctl SNDCTL_DSP_SETFMT"); - return -1; - } - if (nformat!=format) {/* didn't work */ - FIXME("SNDCTL_DSP_GETFMTS: format not set\n"); - return -1; - } - - channels = wfex->nChannels-1; - if (-1==ioctl(audiofd,SNDCTL_DSP_STEREO,&channels)) { - perror("ioctl SNDCTL_DSP_STEREO"); - return -1; - } - speed = wfex->nSamplesPerSec; - if (-1==ioctl(audiofd,SNDCTL_DSP_SPEED,&speed)) { - perror("ioctl SNDCTL_DSP_SPEED"); - return -1; - } - TRACE("(freq=%ld,channels=%d,bits=%d)\n", - wfex->nSamplesPerSec,wfex->nChannels,wfex->wBitsPerSample - ); - return 0; -} - static void DSOUND_CheckEvent(IDirectSoundBufferImpl *dsb, int len) { int i; @@ -1907,7 +2145,7 @@ static void DSOUND_CheckEvent(IDirectSoundBufferImpl *dsb, int len) /* This also means we can't sort the entries by offset, */ /* because DSBPN_OFFSETSTOP == -1 */ if (offset == DSBPN_OFFSETSTOP) { - if (dsb->playing == 0) { + if (dsb->state == STATE_STOPPED) { SetEvent(event->hEventNotify); TRACE("signalled event %d (%d)\n", event->hEventNotify, i); return; @@ -2027,15 +2265,15 @@ static INT DSOUND_MixerNorm(IDirectSoundBufferImpl *dsb, BYTE *buf, INT len) INT iAdvance = dsb->wfx.nBlockAlign; INT oAdvance = primarybuf->wfx.nBlockAlign; - ibp = dsb->buffer + dsb->playpos; + ibp = dsb->buffer + dsb->mixpos; obp = buf; - TRACE("(%p, %p, %p), playpos=%8.8lx\n", dsb, ibp, obp, dsb->playpos); + TRACE("(%p, %p, %p), mixpos=%8.8lx\n", dsb, ibp, obp, dsb->mixpos); /* Check for the best case */ if ((dsb->freq == primarybuf->wfx.nSamplesPerSec) && (dsb->wfx.wBitsPerSample == primarybuf->wfx.wBitsPerSample) && (dsb->wfx.nChannels == primarybuf->wfx.nChannels)) { - DWORD bytesleft = dsb->buflen - dsb->playpos; + DWORD bytesleft = dsb->buflen - dsb->mixpos; TRACE("(%p) Best case\n", dsb); if (len <= bytesleft ) memcpy(obp, ibp, len); @@ -2075,7 +2313,7 @@ static INT DSOUND_MixerNorm(IDirectSoundBufferImpl *dsb, BYTE *buf, INT len) ilen = ((size * dsb->freqAdjust) >> DSOUND_FREQSHIFT) * iAdvance; for (i = 0; i < size; i++) { - ipos = (((i * dsb->freqAdjust) >> DSOUND_FREQSHIFT) * iAdvance) + dsb->playpos; + ipos = (((i * dsb->freqAdjust) >> DSOUND_FREQSHIFT) * iAdvance) + dsb->mixpos; if (ipos >= dsb->buflen) ipos %= dsb->buflen; /* wrap */ @@ -2094,9 +2332,9 @@ static void DSOUND_MixerVol(IDirectSoundBufferImpl *dsb, BYTE *buf, INT len) INT16 *bps = (INT16 *) buf; TRACE("(%p) left = %lx, right = %lx\n", dsb, - dsb->lVolAdjust, dsb->rVolAdjust); - if ((!(dsb->dsbd.dwFlags & DSBCAPS_CTRLPAN) || (dsb->pan == 0)) && - (!(dsb->dsbd.dwFlags & DSBCAPS_CTRLVOLUME) || (dsb->volume == 0)) && + dsb->volpan.dwTotalLeftAmpFactor, dsb->volpan.dwTotalRightAmpFactor); + if ((!(dsb->dsbd.dwFlags & DSBCAPS_CTRLPAN) || (dsb->volpan.lPan == 0)) && + (!(dsb->dsbd.dwFlags & DSBCAPS_CTRLVOLUME) || (dsb->volpan.lVolume == 0)) && !(dsb->dsbd.dwFlags & DSBCAPS_CTRL3D)) return; /* Nothing to do */ @@ -2113,14 +2351,14 @@ static void DSOUND_MixerVol(IDirectSoundBufferImpl *dsb, BYTE *buf, INT len) /* 8-bit WAV is unsigned, but we need to operate */ /* on signed data for this to work properly */ val = *bpc - 128; - val = ((val * (i & inc ? dsb->rVolAdjust : dsb->lVolAdjust)) >> 15); + val = ((val * (i & inc ? dsb->volpan.dwTotalRightAmpFactor : dsb->volpan.dwTotalLeftAmpFactor)) >> 16); *bpc = val + 128; bpc++; break; case 2: /* 16-bit WAV is signed -- much better */ val = *bps; - val = ((val * ((i & inc) ? dsb->rVolAdjust : dsb->lVolAdjust)) >> 15); + val = ((val * ((i & inc) ? dsb->volpan.dwTotalRightAmpFactor : dsb->volpan.dwTotalLeftAmpFactor)) >> 16); *bps = val; bps++; break; @@ -2135,25 +2373,25 @@ static void DSOUND_MixerVol(IDirectSoundBufferImpl *dsb, BYTE *buf, INT len) static void DSOUND_Mixer3D(IDirectSoundBufferImpl *dsb, BYTE *buf, INT len) { BYTE *ibp, *obp; - DWORD buflen, playpos; + DWORD buflen, mixpos; buflen = dsb->ds3db->buflen; - playpos = (dsb->playpos * primarybuf->wfx.nBlockAlign) / dsb->wfx.nBlockAlign; - ibp = dsb->ds3db->buffer + playpos; + mixpos = (dsb->mixpos * primarybuf->wfx.nBlockAlign) / dsb->wfx.nBlockAlign; + ibp = dsb->ds3db->buffer + mixpos; obp = buf; - if (playpos > buflen) { + if (mixpos > buflen) { FIXME("Major breakage"); return; } - if (len <= (playpos + buflen)) + if (len <= (mixpos + buflen)) memcpy(obp, ibp, len); else { /* wrap */ - memcpy(obp, ibp, buflen - playpos); - memcpy(obp + (buflen - playpos), + memcpy(obp, ibp, buflen - mixpos); + memcpy(obp + (buflen - mixpos), dsb->buffer, - len - (buflen - playpos)); + len - (buflen - mixpos)); } return; } @@ -2175,18 +2413,18 @@ static void *DSOUND_tmpbuffer(size_t len) return tmp_buffer; } -static DWORD DSOUND_MixInBuffer(IDirectSoundBufferImpl *dsb) +static DWORD DSOUND_MixInBuffer(IDirectSoundBufferImpl *dsb, DWORD writepos) { INT i, len, ilen, temp, field; INT advance = primarybuf->wfx.wBitsPerSample >> 3; BYTE *buf, *ibuf, *obuf; INT16 *ibufs, *obufs; - len = DSOUND_FRAGLEN; /* The most we will use */ + len = dsound->fraglen; if (!(dsb->playflags & DSBPLAY_LOOPING)) { temp = MulDiv(primarybuf->wfx.nAvgBytesPerSec, dsb->buflen, dsb->nAvgBytesPerSec) - - MulDiv(primarybuf->wfx.nAvgBytesPerSec, dsb->playpos, + MulDiv(primarybuf->wfx.nAvgBytesPerSec, dsb->mixpos, dsb->nAvgBytesPerSec); len = (len > temp) ? temp : len; } @@ -2196,11 +2434,11 @@ static DWORD DSOUND_MixInBuffer(IDirectSoundBufferImpl *dsb) /* This should only happen if we aren't looping and temp < 4 */ /* We skip the remainder, so check for possible events */ - DSOUND_CheckEvent(dsb, dsb->buflen - dsb->playpos); + DSOUND_CheckEvent(dsb, dsb->buflen - dsb->mixpos); /* Stop */ - dsb->playing = 0; - dsb->writepos = 0; + dsb->state = STATE_STOPPED; dsb->playpos = 0; + dsb->mixpos = 0; /* Check for DSBPN_OFFSETSTOP */ DSOUND_CheckEvent(dsb, 0); return 0; @@ -2211,14 +2449,14 @@ static DWORD DSOUND_MixInBuffer(IDirectSoundBufferImpl *dsb) if ((buf = ibuf = (BYTE *) DSOUND_tmpbuffer(len)) == NULL) return 0; - TRACE("MixInBuffer (%p) len = %d\n", dsb, len); + TRACE("MixInBuffer (%p) len = %d, 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); - obuf = primarybuf->buffer + primarybuf->playpos; + obuf = primarybuf->buffer + writepos; for (i = 0; i < len; i += advance) { obufs = (INT16 *) obuf; ibufs = (INT16 *) ibuf; @@ -2247,26 +2485,32 @@ static DWORD DSOUND_MixInBuffer(IDirectSoundBufferImpl *dsb) if (dsb->dsbd.dwFlags & DSBCAPS_CTRLPOSITIONNOTIFY) DSOUND_CheckEvent(dsb, ilen); - dsb->playpos += ilen; - dsb->writepos = dsb->playpos + ilen; + dsb->mixpos += ilen; + /* dsb->writepos = dsb->playpos + ilen; */ - if (dsb->playpos >= dsb->buflen) { + if (dsb->mixpos >= dsb->buflen) { if (!(dsb->playflags & DSBPLAY_LOOPING)) { - dsb->playing = 0; - dsb->writepos = 0; + dsb->state = STATE_STOPPED; dsb->playpos = 0; + dsb->mixpos = 0; DSOUND_CheckEvent(dsb, 0); /* For DSBPN_OFFSETSTOP */ - } else - dsb->playpos %= dsb->buflen; /* wrap */ + } else { + dsb->mixpos %= dsb->buflen; /* wrap */ + /* HACK... leadin should be reset when the PLAY position reaches the wrap, + * not the MIX position... but if the sound buffer is bigger than our prebuffering + * (which must be the case for the streaming buffers that need this hack anyway) + * plus DS_HEL_MARGIN or equivalent, then this ought to work anyway. */ + dsb->leadin = FALSE; + } } - if (dsb->writepos >= dsb->buflen) - dsb->writepos %= dsb->buflen; + /* if (dsb->writepos >= dsb->buflen) + dsb->writepos %= dsb->buflen; */ return len; } -static DWORD WINAPI DSOUND_MixPrimary(void) +static DWORD WINAPI DSOUND_MixPrimary(DWORD writepos, BOOL starting) { INT i, len, maxlen = 0; IDirectSoundBufferImpl *dsb; @@ -2277,10 +2521,15 @@ static DWORD WINAPI DSOUND_MixPrimary(void) if (!dsb || !(ICOM_VTBL(dsb))) continue; IDirectSoundBuffer_AddRef((LPDIRECTSOUNDBUFFER)dsb); - if (dsb->buflen && dsb->playing) { + if (dsb->buflen && dsb->state && !(starting && (dsb->state != STATE_STARTING))) { EnterCriticalSection(&(dsb->lock)); - len = DSOUND_MixInBuffer(dsb); - maxlen = len > maxlen ? len : maxlen; + if (dsb->state == STATE_STOPPING) { + /* FIXME: perhaps attempt to remove the buffer from the prebuffer */ + dsb->state = STATE_STOPPED; + } else { + len = DSOUND_MixInBuffer(dsb, writepos); + maxlen = len > maxlen ? len : maxlen; + } LeaveCriticalSection(&(dsb->lock)); } IDirectSoundBuffer_Release((LPDIRECTSOUNDBUFFER)dsb); @@ -2289,209 +2538,144 @@ static DWORD WINAPI DSOUND_MixPrimary(void) return maxlen; } -static int DSOUND_OpenAudio(void) +static void WINAPI DSOUND_MarkPlaying(void) { - int audioFragment,flags; + INT i; + IDirectSoundBufferImpl *dsb; - if (primarybuf == NULL) - return DSERR_OUTOFMEMORY; + for (i = dsound->nrofbuffers - 1; i >= 0; i--) { + dsb = dsound->buffers[i]; - while (audiofd >= 0) + if (!dsb || !(ICOM_VTBL(dsb))) + continue; + if (dsb->buflen && (dsb->state == STATE_STARTING)) + dsb->state = STATE_PLAYING; + } +} - sleep(5); - /* we will most likely not get one, avoid excessive opens ... */ - if (audiofd == -ENODEV) - return -1; - audiofd = open("/dev/audio",O_WRONLY|O_NDELAY); - if (audiofd==-1) { - /* Don't worry if sound is busy at the moment */ - if ((errno != EBUSY) && (errno != ENODEV)) - perror("open /dev/audio"); - audiofd = -errno; - return -1; /* -1 */ +static void CALLBACK DSOUND_timer(UINT timerID, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2) +{ + DWORD len; + int nfiller; + + if (!dsound || !primarybuf) { + ERR("dsound died without killing us?\n"); + timeKillEvent(timerID); + timeEndPeriod(DS_TIME_RES); + return; } - /* We should probably do something here if SETFRAGMENT fails... */ - audioFragment=0x0002000c; - if (-1==ioctl(audiofd,SNDCTL_DSP_SETFRAGMENT,&audioFragment)) - perror("ioctl SETFRAGMENT"); - - if ((flags = fcntl(audiofd,F_GETFL,0)) != -1) { - flags &= ~O_NDELAY; - if (-1==fcntl(audiofd,F_SETFL,flags)) - perror("clearing the non-blocking flags in DSOUND_OpenAudio"); - } else - perror("cannot get flags of audiofd"); + EnterCriticalSection(&(dsound->lock)); - audioOK = 1; - DSOUND_setformat(&(primarybuf->wfx)); + /* the sound of silence */ + nfiller = primarybuf->wfx.wBitsPerSample == 8 ? 128 : 0; - return 0; -} - -static void DSOUND_CloseAudio(void) -{ - int neutral; - - neutral = primarybuf->wfx.wBitsPerSample == 8 ? 128 : 0; - audioOK = 0; /* race condition */ - Sleep(5); - /* It's possible we've been called with audio closed */ - /* from SetFormat()... this is just to force a call */ - /* to OpenAudio() to reset the hardware properly */ - if (audiofd >= 0) - close(audiofd); - primarybuf->playpos = 0; - primarybuf->writepos = DSOUND_FRAGLEN; - memset(primarybuf->buffer, neutral, primarybuf->buflen); - audiofd = -1; - TRACE("Audio stopped\n"); -} - -static int DSOUND_WriteAudio(char *buf, int len) -{ - int result, left = 0; - - while (left < len) { - result = write(audiofd, buf + left, len - left); - if (result == -1) { - if (errno == EINTR) - continue; - else - return result; - } - left += result; - } - return 0; -} - -static void DSOUND_OutputPrimary(int len) -{ - int neutral, flen1, flen2; - char *frag1, *frag2; - - /* This is a bad place for this. We need to clear the */ - /* buffer with a neutral value, for unsigned 8-bit WAVE */ - /* that's 128, for signed 16-bit it's 0 */ - neutral = primarybuf->wfx.wBitsPerSample == 8 ? 128 : 0; - - /* **** */ - EnterCriticalSection(&(primarybuf->lock)); - - /* Write out the */ - if ((audioOK == 1) || (DSOUND_OpenAudio() == 0)) { - if (primarybuf->playpos + len >= primarybuf->buflen) { - frag1 = primarybuf->buffer + primarybuf->playpos; - flen1 = primarybuf->buflen - primarybuf->playpos; - frag2 = primarybuf->buffer; - flen2 = len - (primarybuf->buflen - primarybuf->playpos); - if (DSOUND_WriteAudio(frag1, flen1) != 0) { - perror("DSOUND_WriteAudio"); - LeaveCriticalSection(&(primarybuf->lock)); - ExitThread(0); - } - memset(frag1, neutral, flen1); - if (DSOUND_WriteAudio(frag2, flen2) != 0) { - perror("DSOUND_WriteAudio"); - LeaveCriticalSection(&(primarybuf->lock)); - ExitThread(0); - } - memset(frag2, neutral, flen2); - } else { - frag1 = primarybuf->buffer + primarybuf->playpos; - flen1 = len; - if (DSOUND_WriteAudio(frag1, flen1) != 0) { - perror("DSOUND_WriteAudio"); - LeaveCriticalSection(&(primarybuf->lock)); - ExitThread(0); - } - memset(frag1, neutral, flen1); - } + if (primarybuf->hwbuf) { + DWORD playpos, writepos; + IDsDriverBuffer_GetPosition(primarybuf->hwbuf, &playpos, &writepos); + /* Well, we *could* do Just-In-Time mixing using the writepos, + * but that's a little bit ambitious and unnecessary... + * let's stick with the fraglen */ + /* FIXME: do something clever */ } else { - /* Can't play audio at the moment -- we need to sleep */ - /* to make up for the time we'd be blocked in write() */ - /* to /dev/audio */ - Sleep(60); - } - primarybuf->playpos += len; - if (primarybuf->playpos >= primarybuf->buflen) - primarybuf->playpos %= primarybuf->buflen; - primarybuf->writepos = primarybuf->playpos + DSOUND_FRAGLEN; - if (primarybuf->writepos >= primarybuf->buflen) - primarybuf->writepos %= primarybuf->buflen; - - LeaveCriticalSection(&(primarybuf->lock)); - /* **** */ -} - -static DWORD WINAPI DSOUND_thread(LPVOID arg) -{ - int len; - - TRACE("dsound is at pid %d\n",getpid()); - while (1) { - if (!dsound) { - WARN("DSOUND thread giving up.\n"); - ExitThread(0); + /* using waveOut stuff */ + /* if no buffers are playing, we should be in pause mode now */ + BOOL in_pause = !dsound->pwqueue; + DWORD writepos; + /* 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--; } -#if 0 - /* EP: since the thread creating this thread can - * die before the end of the DSOUND one, this - * test is of no use - * What shall be tested is whether the DSOUND thread - * is the last one in the process - */ - if (getppid()==1) { - WARN("DSOUND father died? Giving up.\n"); - ExitThread(0); - } -#endif - /* RACE: dsound could be deleted */ - EnterCriticalSection(&(dsound->lock)); - if (primarybuf == NULL) { - /* Should never happen */ - WARN("Lost the primary buffer!\n"); - IDirectSound_Release((LPDIRECTSOUND)dsound); - ExitThread(0); + primarybuf->playpos = dsound->pwplay * dsound->fraglen; + TRACE("primary playpos=%ld, mixpos=%ld\n",primarybuf->playpos,primarybuf->mixpos); + if (!(in_pause || 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 */ + waveOutPause(dsound->hwo); + in_pause = TRUE; } + /* find next write position, plus some extra margin */ + writepos = primarybuf->playpos + DS_HEL_MARGIN * dsound->fraglen; + while (writepos >= primarybuf->buflen) writepos -= primarybuf->buflen; + EnterCriticalSection(&(primarybuf->lock)); + if (primarybuf->state == STATE_STARTING) + primarybuf->state = STATE_PLAYING; + /* see if some new buffers have been started that we want to merge into our prebuffer; + * this should minimize latency even when we have a large prebuffer */ + if (dsound->priolevel != DSSCL_WRITEPRIMARY) { + if (!in_pause) { + while (writepos != primarybuf->mixpos) { + len = DSOUND_MixPrimary(writepos, TRUE); + if (!len) break; + writepos += dsound->fraglen; + if (writepos >= primarybuf->buflen) + writepos -= primarybuf->buflen; + } + } + DSOUND_MarkPlaying(); + } - len = DSOUND_MixPrimary(); - + /* we want at least DS_HEL_QUEUE fragments in the prebuffer outqueue; + * mix a bunch of fragments now as necessary */ + while (dsound->pwqueue < DS_HEL_QUEUE) { + if (dsound->priolevel != DSSCL_WRITEPRIMARY) { + len = DSOUND_MixPrimary(primarybuf->mixpos, FALSE); + } else len=0; + if (primarybuf->state == STATE_PLAYING) + len = dsound->fraglen; + /* if we have nothing to play, don't bother to */ + if (!len) break; + if (len < dsound->fraglen) { + TRACE("len=%ld is less than fraglen=%ld\n",len,dsound->fraglen); + } + /* ok, we have something to play */ + /* advance mix positions */ + primarybuf->mixpos += dsound->fraglen; + if (primarybuf->mixpos >= primarybuf->buflen) + primarybuf->mixpos -= primarybuf->buflen; + /* output it */ + waveOutWrite(dsound->hwo, dsound->pwave[dsound->pwwrite], sizeof(WAVEHDR)); + dsound->pwwrite++; + if (dsound->pwwrite >= DS_HEL_FRAGS) dsound->pwwrite = 0; + dsound->pwqueue++; + } + if (primarybuf->state == STATE_STOPPING) + primarybuf->state = STATE_STOPPED; LeaveCriticalSection(&(primarybuf->lock)); - LeaveCriticalSection(&(dsound->lock)); - - if (primarybuf->playing) - len = DSOUND_FRAGLEN > len ? DSOUND_FRAGLEN : len; - if (len) { - /* This does all the work */ - DSOUND_OutputPrimary(len); - } else { - /* no buffers playing -- close and wait */ - if (audioOK) - DSOUND_CloseAudio(); - Sleep(100); + if (in_pause && dsound->pwqueue) { + /* buffers have been filled, restart playback */ + waveOutRestart(dsound->hwo); } } - ExitThread(0); + LeaveCriticalSection(&(dsound->lock)); } -#endif /* HAVE_OSS */ - /******************************************************************************* * DirectSoundCreate */ HRESULT WINAPI DirectSoundCreate(REFGUID lpGUID,LPDIRECTSOUND *ppDS,IUnknown *pUnkOuter ) { IDirectSoundImpl** ippDS=(IDirectSoundImpl**)ppDS; + PIDSDRIVER drv = NULL; + WAVEOUTCAPSA wcaps; + unsigned wod, wodn; + HRESULT err = DS_OK; + if (lpGUID) TRACE("(%p,%p,%p)\n",lpGUID,ippDS,pUnkOuter); else TRACE("DirectSoundCreate (%p)\n", ippDS); -#ifdef HAVE_OSS - if (ippDS == NULL) return DSERR_INVALIDPARAM; @@ -2501,26 +2685,19 @@ HRESULT WINAPI DirectSoundCreate(REFGUID lpGUID,LPDIRECTSOUND *ppDS,IUnknown *pU return DS_OK; } - /* Check that we actually have audio capabilities */ - /* If we do, whether it's busy or not, we continue */ - /* otherwise we return with DSERR_NODRIVER */ + /* Enumerate WINMM audio devices and find the one we want */ + wodn = waveOutGetNumDevs(); + if (!wodn) return DSERR_NODRIVER; - audiofd = open("/dev/audio",O_WRONLY|O_NDELAY); - if (audiofd == -1) { - audiofd = -errno; - if (errno == ENODEV) { - MESSAGE("No sound hardware found, but continuing anyway.\n"); - } else if (errno == EBUSY) { - MESSAGE("Sound device busy, will keep trying.\n"); - } else { - MESSAGE("Unexpected error (%d) while checking for sound support.\n",errno); - return DSERR_GENERIC; - } - } else { - close(audiofd); - audiofd = -1; - } + /* FIXME: How do we find the GUID of an audio device? */ + /* Well, just use the first available device for now... */ + wod = 0; + /* Get output device caps */ + waveOutGetDevCapsA(wod, &wcaps, sizeof(wcaps)); + /* 0x810 is a "Wine extension" to get the DSound interface */ + waveOutMessage(wod, 0x810, (DWORD)&drv, 0); + /* Allocate memory */ *ippDS = (IDirectSoundImpl*)HeapAlloc(GetProcessHeap(),0,sizeof(IDirectSoundImpl)); if (*ippDS == NULL) return DSERR_OUTOFMEMORY; @@ -2528,32 +2705,88 @@ HRESULT WINAPI DirectSoundCreate(REFGUID lpGUID,LPDIRECTSOUND *ppDS,IUnknown *pU ICOM_VTBL(*ippDS) = &dsvt; (*ippDS)->ref = 1; + (*ippDS)->driver = drv; + (*ippDS)->fraglen = 0; (*ippDS)->priolevel = DSSCL_NORMAL; (*ippDS)->nrofbuffers = 0; (*ippDS)->buffers = NULL; (*ippDS)->primary = NULL; (*ippDS)->listener = NULL; - (*ippDS)->wfx.wFormatTag = 1; + /* Get driver description */ + if (drv) { + IDsDriver_GetDriverDesc(drv,&((*ippDS)->drvdesc)); + } else { + /* if no DirectSound interface available, use WINMM API instead */ + (*ippDS)->drvdesc.dwFlags = DSDDESC_DOMMSYSTEMOPEN | DSDDESC_DOMMSYSTEMSETFORMAT; + (*ippDS)->drvdesc.dnDevNode = wod; /* FIXME? */ + } + + /* Set default wave format (may need it for waveOutOpen) */ + (*ippDS)->wfx.wFormatTag = WAVE_FORMAT_PCM; (*ippDS)->wfx.nChannels = 2; (*ippDS)->wfx.nSamplesPerSec = 22050; (*ippDS)->wfx.nAvgBytesPerSec = 44100; (*ippDS)->wfx.nBlockAlign = 2; (*ippDS)->wfx.wBitsPerSample = 8; + /* If the driver requests being opened through MMSYSTEM + * (which is recommended by the DDK), it is supposed to happen + * before the DirectSound interface is opened */ + if ((*ippDS)->drvdesc.dwFlags & DSDDESC_DOMMSYSTEMOPEN) { + /* FIXME: is this right? */ + err = mmErr(waveOutOpen(&((*ippDS)->hwo), (*ippDS)->drvdesc.dnDevNode, &((*ippDS)->wfx), + 0, 0, CALLBACK_NULL)); + } + + if (drv && (err == DS_OK)) + err = IDsDriver_Open(drv); + + /* FIXME: do we want to handle a temporarily busy device? */ + if (err != DS_OK) { + HeapFree(GetProcessHeap(),0,*ippDS); + *ippDS = NULL; + return err; + } + + /* the driver is now open, so it's now allowed to call GetCaps */ + if (drv) { + IDsDriver_GetCaps(drv,&((*ippDS)->drvcaps)); + } else { + unsigned c; + + /* FIXME: look at wcaps */ + (*ippDS)->drvcaps.dwFlags = DSCAPS_EMULDRIVER | + DSCAPS_PRIMARY16BIT | DSCAPS_PRIMARYSTEREO | DSCAPS_CONTINUOUSRATE; + + /* Allocate memory for HEL buffer headers */ + for (c=0; cpwave[c] = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(WAVEHDR)); + if (!(*ippDS)->pwave[c]) { + /* Argh, out of memory */ + while (c--) { + HeapFree(GetProcessHeap(),0,(*ippDS)->pwave[c]); + waveOutClose((*ippDS)->hwo); + HeapFree(GetProcessHeap(),0,*ippDS); + *ippDS = NULL; + return DSERR_OUTOFMEMORY; + } + } + } + /* Start in pause mode until buffers are filled */ + waveOutPause((*ippDS)->hwo); + } + InitializeCriticalSection(&((*ippDS)->lock)); if (!dsound) { - HANDLE hnd; - DWORD xid; - dsound = (*ippDS); if (primarybuf == NULL) { DSBUFFERDESC dsbd; HRESULT hr; dsbd.dwSize = sizeof(DSBUFFERDESC); - dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER; + dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_GETCURRENTPOSITION2; dsbd.dwBufferBytes = 0; dsbd.lpwfxFormat = &(dsound->wfx); hr = IDirectSound_CreateSoundBuffer(*ppDS, &dsbd, (LPDIRECTSOUNDBUFFER*)&primarybuf, NULL); @@ -2564,14 +2797,11 @@ HRESULT WINAPI DirectSoundCreate(REFGUID lpGUID,LPDIRECTSOUND *ppDS,IUnknown *pU dsound->primary = primarybuf; IDirectSoundBuffer_AddRef((LPDIRECTSOUNDBUFFER)primarybuf); } - memset(primarybuf->buffer, 128, primarybuf->buflen); - hnd = CreateThread(NULL,0,DSOUND_thread,0,0,&xid); + timeBeginPeriod(DS_TIME_RES); + dsound->timerID = timeSetEvent(DS_TIME_DEL, DS_TIME_RES, DSOUND_timer, + (DWORD)dsound, TIME_PERIODIC | TIME_CALLBACK_FUNCTION); } return DS_OK; -#else - MessageBoxA(0,"DirectSound needs the Open Sound System Driver, which has not been found by ./configure.","WINE DirectSound",MB_OK|MB_ICONSTOP); - return DSERR_NODRIVER; -#endif } /*************************************************************************** diff --git a/include/dsdriver.h b/include/dsdriver.h index fd301373d33..e485c957227 100644 --- a/include/dsdriver.h +++ b/include/dsdriver.h @@ -155,7 +155,7 @@ ICOM_DEFINE(IDsDriverBuffer,IUnknown) #define IDsDriverBuffer_SetVolumePan(p,a) ICOM_CALL1(SetVolumePan,p,a) #define IDsDriverBuffer_SetPosition(p,a) ICOM_CALL1(SetPosition,p,a) #define IDsDriverBuffer_GetPosition(p,a,b) ICOM_CALL2(GetPosition,p,a,b) -#define IDsDriverBuffer_Play(p,a,b,c) ICOM_CALL2(Play,p,a,b,c) +#define IDsDriverBuffer_Play(p,a,b,c) ICOM_CALL3(Play,p,a,b,c) #define IDsDriverBuffer_Stop(p) ICOM_CALL (Stop,p) /*****************************************************************************