Commit 9dacf206 authored by Robert Reif's avatar Robert Reif Committed by Alexandre Julliard

3D buffer and listener reference counts should be fixed.

3D sound now plays in 3D. COM interfaces should be correct for buffers and 3D stuff. The COM interface is broken on notifications but I will fix that next. Fixed the bug when closing and opening the same or another sound device quickly. Fixed some locking but there is a lot of work left yet.
parent 0d6a2e9a
...@@ -48,8 +48,10 @@ ...@@ -48,8 +48,10 @@
#endif #endif
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <math.h> /* Insomnia - pow() function */ #include <math.h>
#define NONAMELESSSTRUCT
#define NONAMELESSUNION
#include "windef.h" #include "windef.h"
#include "winbase.h" #include "winbase.h"
#include "winreg.h" #include "winreg.h"
...@@ -471,8 +473,12 @@ static HRESULT WINAPI IDirectSoundImpl_SetCooperativeLevel( ...@@ -471,8 +473,12 @@ static HRESULT WINAPI IDirectSoundImpl_SetCooperativeLevel(
LPDIRECTSOUND8 iface,HWND hwnd,DWORD level LPDIRECTSOUND8 iface,HWND hwnd,DWORD level
) { ) {
ICOM_THIS(IDirectSoundImpl,iface); ICOM_THIS(IDirectSoundImpl,iface);
TRACE("(%p,%08lx,%ld)\n",This,(DWORD)hwnd,level);
FIXME("(%p,%08lx,%ld):stub\n",This,(DWORD)hwnd,level); if (level==DSSCL_PRIORITY || level==DSSCL_EXCLUSIVE) {
FIXME("level=%s not fully supported\n",
level==DSSCL_PRIORITY ? "DSSCL_PRIORITY" : "DSSCL_EXCLUSIVE");
}
This->priolevel = level; This->priolevel = level;
...@@ -484,7 +490,7 @@ static HRESULT WINAPI IDirectSoundImpl_CreateSoundBuffer( ...@@ -484,7 +490,7 @@ static HRESULT WINAPI IDirectSoundImpl_CreateSoundBuffer(
) { ) {
ICOM_THIS(IDirectSoundImpl,iface); ICOM_THIS(IDirectSoundImpl,iface);
LPWAVEFORMATEX wfex; LPWAVEFORMATEX wfex;
HRESULT hres; HRESULT hres = DS_OK;
TRACE("(%p,%p,%p,%p)\n",This,dsbd,ppdsb,lpunk); TRACE("(%p,%p,%p,%p)\n",This,dsbd,ppdsb,lpunk);
...@@ -522,13 +528,19 @@ static HRESULT WINAPI IDirectSoundImpl_CreateSoundBuffer( ...@@ -522,13 +528,19 @@ static HRESULT WINAPI IDirectSoundImpl_CreateSoundBuffer(
wfex->wBitsPerSample, wfex->cbSize); wfex->wBitsPerSample, wfex->cbSize);
if (dsbd->dwFlags & DSBCAPS_PRIMARYBUFFER) { if (dsbd->dwFlags & DSBCAPS_PRIMARYBUFFER) {
hres = PrimaryBuffer_Create(This, (PrimaryBufferImpl**)ppdsb, dsbd); *ppdsb=(LPDIRECTSOUNDBUFFER8)This->primary;
if (hres != DS_OK) if (*ppdsb==NULL)
WARN("PrimaryBuffer_Create failed\n"); WARN("PrimaryBuffer_Create failed\n");
else {
This->dsbd = *dsbd;
IDirectSoundBuffer_AddRef(*ppdsb);
}
} else { } else {
hres = SecondaryBuffer_Create(This, (IDirectSoundBufferImpl**)ppdsb, dsbd); hres = SecondaryBuffer_Create(This, (IDirectSoundBufferImpl**)ppdsb, dsbd);
if (hres != DS_OK) if (hres != DS_OK)
WARN("SecondaryBuffer_Create failed\n"); WARN("SecondaryBuffer_Create failed\n");
else
IDirectSoundBuffer_AddRef(*ppdsb);
} }
return hres; return hres;
...@@ -577,28 +589,15 @@ static HRESULT WINAPI IDirectSoundImpl_DuplicateSoundBuffer( ...@@ -577,28 +589,15 @@ static HRESULT WINAPI IDirectSoundImpl_DuplicateSoundBuffer(
return DSERR_OUTOFMEMORY; return DSERR_OUTOFMEMORY;
} }
IDirectSoundBuffer8_AddRef(pdsb);
memcpy(dsb, ipdsb, sizeof(IDirectSoundBufferImpl)); memcpy(dsb, ipdsb, sizeof(IDirectSoundBufferImpl));
dsb->ref = 1; dsb->ref = 1;
dsb->state = STATE_STOPPED; dsb->state = STATE_STOPPED;
dsb->playpos = 0; dsb->playpos = 0;
dsb->buf_mixpos = 0; dsb->buf_mixpos = 0;
dsb->dsound = This; dsb->dsound = This;
dsb->parent = ipdsb; dsb->buffer->ref++;
dsb->hwbuf = NULL; dsb->hwbuf = NULL;
if (ipdsb->ds3db != NULL) { dsb->ds3db = NULL;
HRESULT hres;
hres = IDirectSound3DBufferImpl_Create(dsb, &(dsb->ds3db));
if (hres != DS_OK) {
WARN("IDirectSound3DBufferImpl_Create failed\n");
} else {
IDirectSound3DBuffer_AddRef((LPDIRECTSOUND3DBUFFER)(dsb->ds3db));
dsb->dsbd.dwFlags |= DSBCAPS_CTRL3D;
memcpy(&(dsb->ds3db->ds3db), &(ipdsb->ds3db->ds3db), sizeof(DS3DBUFFER));
}
} else {
dsb->ds3db = NULL;
}
dsb->iks = NULL; /* FIXME? */ dsb->iks = NULL; /* FIXME? */
memcpy(&(dsb->wfx), &(ipdsb->wfx), sizeof(dsb->wfx)); memcpy(&(dsb->wfx), &(ipdsb->wfx), sizeof(dsb->wfx));
InitializeCriticalSection(&(dsb->lock)); InitializeCriticalSection(&(dsb->lock));
...@@ -689,45 +688,57 @@ static HRESULT WINAPI IDirectSoundImpl_GetCaps(LPDIRECTSOUND8 iface,LPDSCAPS lpD ...@@ -689,45 +688,57 @@ static HRESULT WINAPI IDirectSoundImpl_GetCaps(LPDIRECTSOUND8 iface,LPDSCAPS lpD
static ULONG WINAPI IDirectSoundImpl_AddRef(LPDIRECTSOUND8 iface) { static ULONG WINAPI IDirectSoundImpl_AddRef(LPDIRECTSOUND8 iface) {
ICOM_THIS(IDirectSoundImpl,iface); ICOM_THIS(IDirectSoundImpl,iface);
TRACE("(%p) ref was %ld\n", This, This->ref); TRACE("(%p) ref was %ld, thread is %04lx\n", This, This->ref, GetCurrentThreadId());
return ++(This->ref); return InterlockedIncrement(&This->ref);
} }
static ULONG WINAPI IDirectSoundImpl_Release(LPDIRECTSOUND8 iface) { static ULONG WINAPI IDirectSoundImpl_Release(LPDIRECTSOUND8 iface) {
HRESULT hres;
ICOM_THIS(IDirectSoundImpl,iface); ICOM_THIS(IDirectSoundImpl,iface);
TRACE("(%p), ref was %ld\n",This,This->ref); ULONG ulReturn;
if (!--(This->ref)) {
TRACE("(%p) ref was %ld, thread is %04lx\n", This, This->ref, GetCurrentThreadId());
ulReturn = InterlockedDecrement(&This->ref);
if (ulReturn == 0) {
HRESULT hres;
UINT i; UINT i;
timeKillEvent(This->timerID); timeKillEvent(This->timerID);
timeEndPeriod(DS_TIME_RES); timeEndPeriod(DS_TIME_RES);
/* wait for timer to expire */
Sleep(DS_TIME_RES+1);
RtlAcquireResourceShared(&(This->lock), TRUE);
if (This->buffers) { if (This->buffers) {
for( i=0;i<This->nrofbuffers;i++) for( i=0;i<This->nrofbuffers;i++)
IDirectSoundBuffer8_Release((LPDIRECTSOUNDBUFFER8)This->buffers[i]); IDirectSoundBuffer8_Release((LPDIRECTSOUNDBUFFER8)This->buffers[i]);
} }
RtlReleaseResource(&(This->lock));
IDirectSoundBuffer8_Release((LPDIRECTSOUNDBUFFER8)This->primary);
hres = DSOUND_PrimaryDestroy(This); hres = DSOUND_PrimaryDestroy(This);
if (hres != DS_OK) if (hres != DS_OK)
WARN("DSOUND_PrimaryDestroy failed\n"); WARN("DSOUND_PrimaryDestroy failed\n");
RtlDeleteResource(&This->lock); if (This->driver)
DeleteCriticalSection(&This->mixlock);
if (This->driver) {
IDsDriver_Close(This->driver); IDsDriver_Close(This->driver);
}
if (This->drvdesc.dwFlags & DSDDESC_DOMMSYSTEMOPEN) { if (This->drvdesc.dwFlags & DSDDESC_DOMMSYSTEMOPEN)
waveOutClose(This->hwo); waveOutClose(This->hwo);
}
if (This->driver) if (This->driver)
IDsDriver_Release(This->driver); IDsDriver_Release(This->driver);
RtlDeleteResource(&This->lock);
DeleteCriticalSection(&This->mixlock);
DeleteCriticalSection(&This->ds3dl_lock);
HeapFree(GetProcessHeap(),0,This); HeapFree(GetProcessHeap(),0,This);
dsound = NULL; dsound = NULL;
return 0; return 0;
} }
return This->ref; return ulReturn;
} }
static HRESULT WINAPI IDirectSoundImpl_SetSpeakerConfig( static HRESULT WINAPI IDirectSoundImpl_SetSpeakerConfig(
...@@ -742,14 +753,24 @@ static HRESULT WINAPI IDirectSoundImpl_QueryInterface( ...@@ -742,14 +753,24 @@ static HRESULT WINAPI IDirectSoundImpl_QueryInterface(
LPDIRECTSOUND8 iface,REFIID riid,LPVOID *ppobj LPDIRECTSOUND8 iface,REFIID riid,LPVOID *ppobj
) { ) {
ICOM_THIS(IDirectSoundImpl,iface); ICOM_THIS(IDirectSoundImpl,iface);
TRACE("(%p,%s,%p)\n",This,debugstr_guid(riid),ppobj);
*ppobj = NULL; /* assume failure */
if ( IsEqualGUID(riid, &IID_IUnknown) ||
IsEqualGUID(riid, &IID_IDirectSound) ||
IsEqualGUID(riid, &IID_IDirectSound8) ) {
IDirectSound8_AddRef((LPDIRECTSOUND8)This);
*ppobj = This;
return S_OK;
}
if ( IsEqualGUID( &IID_IDirectSound3DListener, riid ) ) { if ( IsEqualGUID( &IID_IDirectSound3DListener, riid ) ) {
ERR("app requested IDirectSound3DListener on dsound object\n"); WARN("app requested IDirectSound3DListener on dsound object\n");
*ppobj = NULL; return E_NOINTERFACE;
return E_FAIL;
} }
FIXME("(%p,%s,%p)\n",This,debugstr_guid(riid),ppobj); FIXME( "Unknown IID %s\n", debugstr_guid( riid ) );
return E_NOINTERFACE; return E_NOINTERFACE;
} }
...@@ -927,7 +948,28 @@ HRESULT WINAPI DirectSoundCreate8(LPCGUID lpcGUID,LPDIRECTSOUND8 *ppDS,IUnknown ...@@ -927,7 +948,28 @@ HRESULT WINAPI DirectSoundCreate8(LPCGUID lpcGUID,LPDIRECTSOUND8 *ppDS,IUnknown
(*ippDS)->state = STATE_STOPPED; (*ippDS)->state = STATE_STOPPED;
(*ippDS)->nrofbuffers = 0; (*ippDS)->nrofbuffers = 0;
(*ippDS)->buffers = NULL; (*ippDS)->buffers = NULL;
(*ippDS)->primary = NULL;
/* 3D listener initial parameters */
(*ippDS)->listener = NULL; (*ippDS)->listener = NULL;
(*ippDS)->ds3dl.dwSize = sizeof(DS3DLISTENER);
(*ippDS)->ds3dl.vPosition.u1.x = 0.0;
(*ippDS)->ds3dl.vPosition.u2.y = 0.0;
(*ippDS)->ds3dl.vPosition.u3.z = 0.0;
(*ippDS)->ds3dl.vVelocity.u1.x = 0.0;
(*ippDS)->ds3dl.vVelocity.u2.y = 0.0;
(*ippDS)->ds3dl.vVelocity.u3.z = 0.0;
(*ippDS)->ds3dl.vOrientFront.u1.x = 0.0;
(*ippDS)->ds3dl.vOrientFront.u2.y = 0.0;
(*ippDS)->ds3dl.vOrientFront.u3.z = 1.0;
(*ippDS)->ds3dl.vOrientTop.u1.x = 0.0;
(*ippDS)->ds3dl.vOrientTop.u2.y = 1.0;
(*ippDS)->ds3dl.vOrientTop.u3.z = 0.0;
(*ippDS)->ds3dl.flDistanceFactor = DS3D_DEFAULTDISTANCEFACTOR;
(*ippDS)->ds3dl.flRolloffFactor = DS3D_DEFAULTROLLOFFFACTOR;
(*ippDS)->ds3dl.flDopplerFactor = DS3D_DEFAULTDOPPLERFACTOR;
InitializeCriticalSection(&(*ippDS)->ds3dl_lock);
(*ippDS)->prebuf = ds_snd_queue_max; (*ippDS)->prebuf = ds_snd_queue_max;
(*ippDS)->guid = devGuid; (*ippDS)->guid = devGuid;
...@@ -1064,7 +1106,14 @@ HRESULT WINAPI DirectSoundCreate8(LPCGUID lpcGUID,LPDIRECTSOUND8 *ppDS,IUnknown ...@@ -1064,7 +1106,14 @@ HRESULT WINAPI DirectSoundCreate8(LPCGUID lpcGUID,LPDIRECTSOUND8 *ppDS,IUnknown
dsound->timerID = timeSetEvent(DS_TIME_DEL, DS_TIME_RES, DSOUND_timer, dsound->timerID = timeSetEvent(DS_TIME_DEL, DS_TIME_RES, DSOUND_timer,
(DWORD)dsound, TIME_PERIODIC | TIME_CALLBACK_FUNCTION); (DWORD)dsound, TIME_PERIODIC | TIME_CALLBACK_FUNCTION);
} }
return DS_OK;
/* create a user accessable primary buffer */
(*ippDS)->dsbd.dwSize = sizeof((*ippDS)->dsbd);
err = PrimaryBuffer_Create((*ippDS), (PrimaryBufferImpl**)&((*ippDS)->primary), &((*ippDS)->dsbd));
if ((*ippDS)->primary)
IDirectSoundBuffer_AddRef((LPDIRECTSOUNDBUFFER8)(*ippDS)->primary);
return err;
} }
......
...@@ -62,7 +62,7 @@ struct IDirectSoundImpl ...@@ -62,7 +62,7 @@ struct IDirectSoundImpl
{ {
/* IUnknown fields */ /* IUnknown fields */
ICOM_VFIELD(IDirectSound8); ICOM_VFIELD(IDirectSound8);
DWORD ref; DWORD ref;
/* IDirectSoundImpl fields */ /* IDirectSoundImpl fields */
GUID guid; GUID guid;
PIDSDRIVER driver; PIDSDRIVER driver;
...@@ -80,12 +80,26 @@ struct IDirectSoundImpl ...@@ -80,12 +80,26 @@ struct IDirectSoundImpl
BOOL need_remix; BOOL need_remix;
int nrofbuffers; int nrofbuffers;
IDirectSoundBufferImpl** buffers; IDirectSoundBufferImpl** buffers;
IDirectSound3DListenerImpl* listener;
RTL_RWLOCK lock; RTL_RWLOCK lock;
CRITICAL_SECTION mixlock; CRITICAL_SECTION mixlock;
DSVOLUMEPAN volpan; DSVOLUMEPAN volpan;
PrimaryBufferImpl* primary;
DSBUFFERDESC dsbd;
/* DirectSound3DListener fields */
IDirectSound3DListenerImpl* listener;
DS3DLISTENER ds3dl;
CRITICAL_SECTION ds3dl_lock;
BOOL ds3dl_need_recalc;
}; };
/* reference counted buffer memory for duplicated buffer memory */
typedef struct BufferMemory
{
DWORD ref;
LPBYTE memory;
} BufferMemory;
/***************************************************************************** /*****************************************************************************
* IDirectSoundBuffer implementation structure * IDirectSoundBuffer implementation structure
*/ */
...@@ -94,30 +108,34 @@ struct IDirectSoundBufferImpl ...@@ -94,30 +108,34 @@ struct IDirectSoundBufferImpl
/* FIXME: document */ /* FIXME: document */
/* IUnknown fields */ /* IUnknown fields */
ICOM_VFIELD(IDirectSoundBuffer8); ICOM_VFIELD(IDirectSoundBuffer8);
DWORD ref; DWORD ref;
/* IDirectSoundBufferImpl fields */ /* IDirectSoundBufferImpl fields */
IDirectSoundImpl* dsound; IDirectSoundImpl* dsound;
IDirectSoundBufferImpl* parent; /* for duplicates */ IDirectSound3DBufferImpl* ds3db;
IDirectSound3DBufferImpl* ds3db; IKsPropertySetImpl* iks;
IKsPropertySetImpl* iks; CRITICAL_SECTION lock;
CRITICAL_SECTION lock; PIDSDRIVERBUFFER hwbuf;
PIDSDRIVERBUFFER hwbuf; WAVEFORMATEX wfx;
WAVEFORMATEX wfx; BufferMemory* buffer;
LPBYTE buffer; DWORD playflags,state,leadin;
DWORD playflags,state,leadin; DWORD playpos,startpos,writelead,buflen;
DWORD playpos,startpos,writelead,buflen; DWORD nAvgBytesPerSec;
DWORD nAvgBytesPerSec; DWORD freq;
DWORD freq; DSVOLUMEPAN volpan, cvolpan;
DSVOLUMEPAN volpan, cvolpan; DSBUFFERDESC dsbd;
DSBUFFERDESC dsbd;
/* used for frequency conversion (PerfectPitch) */ /* used for frequency conversion (PerfectPitch) */
ULONG freqAdjust, freqAcc; ULONG freqAdjust, freqAcc;
/* used for intelligent (well, sort of) prebuffering */ /* used for intelligent (well, sort of) prebuffering */
DWORD probably_valid_to, last_playpos; DWORD probably_valid_to, last_playpos;
DWORD primary_mixpos, buf_mixpos; DWORD primary_mixpos, buf_mixpos;
BOOL need_remix; BOOL need_remix;
/* IDirectSoundNotifyImpl fields */ /* IDirectSoundNotifyImpl fields */
IDirectSoundNotifyImpl *notify; IDirectSoundNotifyImpl* notify;
/* DirectSound3DBuffer fields */
DS3DBUFFER ds3db_ds3db;
LONG ds3db_lVolume;
BOOL ds3db_need_recalc;
}; };
HRESULT WINAPI SecondaryBuffer_Create( HRESULT WINAPI SecondaryBuffer_Create(
...@@ -126,10 +144,9 @@ HRESULT WINAPI SecondaryBuffer_Create( ...@@ -126,10 +144,9 @@ HRESULT WINAPI SecondaryBuffer_Create(
LPDSBUFFERDESC dsbd); LPDSBUFFERDESC dsbd);
struct PrimaryBufferImpl { struct PrimaryBufferImpl {
ICOM_VFIELD(IDirectSoundBuffer8); ICOM_VFIELD(IDirectSoundBuffer8);
DWORD ref; DWORD ref;
IDirectSoundImpl* dsound; IDirectSoundImpl* dsound;
DSBUFFERDESC dsbd;
}; };
HRESULT WINAPI PrimaryBuffer_Create( HRESULT WINAPI PrimaryBuffer_Create(
...@@ -182,15 +199,15 @@ struct IDirectSoundCaptureBufferImpl ...@@ -182,15 +199,15 @@ struct IDirectSoundCaptureBufferImpl
{ {
/* IUnknown fields */ /* IUnknown fields */
ICOM_VFIELD(IDirectSoundCaptureBuffer8); ICOM_VFIELD(IDirectSoundCaptureBuffer8);
DWORD ref; DWORD ref;
/* IDirectSoundCaptureBufferImpl fields */ /* IDirectSoundCaptureBufferImpl fields */
IDirectSoundCaptureImpl* dsound; IDirectSoundCaptureImpl* dsound;
/* FIXME: don't need this */ /* FIXME: don't need this */
LPDSCBUFFERDESC pdscbd; LPDSCBUFFERDESC pdscbd;
DWORD flags; DWORD flags;
/* IDirectSoundNotifyImpl fields */ /* IDirectSoundNotifyImpl fields */
IDirectSoundNotifyImpl *notify; IDirectSoundNotifyImpl* notify;
}; };
/***************************************************************************** /*****************************************************************************
...@@ -200,10 +217,10 @@ struct IDirectSoundFullDuplexImpl ...@@ -200,10 +217,10 @@ struct IDirectSoundFullDuplexImpl
{ {
/* IUnknown fields */ /* IUnknown fields */
ICOM_VFIELD(IDirectSoundFullDuplex); ICOM_VFIELD(IDirectSoundFullDuplex);
DWORD ref; DWORD ref;
/* IDirectSoundFullDuplexImpl fields */ /* IDirectSoundFullDuplexImpl fields */
CRITICAL_SECTION lock; CRITICAL_SECTION lock;
}; };
/***************************************************************************** /*****************************************************************************
...@@ -213,12 +230,12 @@ struct IDirectSoundNotifyImpl ...@@ -213,12 +230,12 @@ struct IDirectSoundNotifyImpl
{ {
/* IUnknown fields */ /* IUnknown fields */
ICOM_VFIELD(IDirectSoundNotify); ICOM_VFIELD(IDirectSoundNotify);
DWORD ref; DWORD ref;
/* IDirectSoundNotifyImpl fields */ /* IDirectSoundNotifyImpl fields */
LPDSBPOSITIONNOTIFY notifies; LPDSBPOSITIONNOTIFY notifies;
int nrofnotifies; int nrofnotifies;
PIDSDRIVERNOTIFY hwnotify; PIDSDRIVERNOTIFY hwnotify;
}; };
/***************************************************************************** /*****************************************************************************
...@@ -228,12 +245,9 @@ struct IDirectSound3DListenerImpl ...@@ -228,12 +245,9 @@ struct IDirectSound3DListenerImpl
{ {
/* IUnknown fields */ /* IUnknown fields */
ICOM_VFIELD(IDirectSound3DListener); ICOM_VFIELD(IDirectSound3DListener);
DWORD ref; DWORD ref;
/* IDirectSound3DListenerImpl fields */ /* IDirectSound3DListenerImpl fields */
PrimaryBufferImpl* dsb; IDirectSoundImpl* dsound;
DS3DLISTENER ds3dl;
CRITICAL_SECTION lock;
BOOL need_recalc;
}; };
HRESULT WINAPI IDirectSound3DListenerImpl_Create( HRESULT WINAPI IDirectSound3DListenerImpl_Create(
...@@ -263,13 +277,10 @@ struct IDirectSound3DBufferImpl ...@@ -263,13 +277,10 @@ struct IDirectSound3DBufferImpl
{ {
/* IUnknown fields */ /* IUnknown fields */
ICOM_VFIELD(IDirectSound3DBuffer); ICOM_VFIELD(IDirectSound3DBuffer);
DWORD ref; DWORD ref;
/* IDirectSound3DBufferImpl fields */ /* IDirectSound3DBufferImpl fields */
IDirectSoundBufferImpl* dsb; IDirectSoundBufferImpl* dsb;
DS3DBUFFER ds3db; CRITICAL_SECTION lock;
LONG lVolume;
CRITICAL_SECTION lock;
BOOL need_recalc;
}; };
HRESULT WINAPI IDirectSound3DBufferImpl_Create( HRESULT WINAPI IDirectSound3DBufferImpl_Create(
...@@ -302,6 +313,10 @@ void DSOUND_PerformMix(void); ...@@ -302,6 +313,10 @@ void DSOUND_PerformMix(void);
void CALLBACK DSOUND_timer(UINT timerID, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2); void CALLBACK DSOUND_timer(UINT timerID, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2);
void CALLBACK DSOUND_callback(HWAVEOUT hwo, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2); void CALLBACK DSOUND_callback(HWAVEOUT hwo, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2);
/* sound3d.c */
void DSOUND_Calc3DBuffer(IDirectSoundBufferImpl *dsb);
#define STATE_STOPPED 0 #define STATE_STOPPED 0
#define STATE_STARTING 1 #define STATE_STARTING 1
#define STATE_PLAYING 2 #define STATE_PLAYING 2
...@@ -313,12 +328,6 @@ void CALLBACK DSOUND_callback(HWAVEOUT hwo, UINT msg, DWORD dwUser, DWORD dw1, D ...@@ -313,12 +328,6 @@ void CALLBACK DSOUND_callback(HWAVEOUT hwo, UINT msg, DWORD dwUser, DWORD dw1, D
extern IDirectSoundImpl* dsound; extern IDirectSoundImpl* dsound;
extern IDirectSoundCaptureImpl* dsound_capture; extern IDirectSoundCaptureImpl* dsound_capture;
struct PrimaryBuffer {
DWORD ref;
PIDSDRIVERBUFFER hwbuf;
DWORD state;
};
extern ICOM_VTABLE(IDirectSoundNotify) dsnvt; extern ICOM_VTABLE(IDirectSoundNotify) dsnvt;
extern HRESULT mmErr(UINT err); extern HRESULT mmErr(UINT err);
extern void setup_dsound_options(void); extern void setup_dsound_options(void);
...@@ -50,6 +50,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(dsound); ...@@ -50,6 +50,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(dsound);
void DSOUND_RecalcVolPan(PDSVOLUMEPAN volpan) void DSOUND_RecalcVolPan(PDSVOLUMEPAN volpan)
{ {
double temp; double temp;
TRACE("(%p)\n",volpan);
/* the AmpFactors are expressed in 16.16 fixed point */ /* the AmpFactors are expressed in 16.16 fixed point */
volpan->dwVolAmpFactor = (ULONG) (pow(2.0, volpan->lVolume / 600.0) * 65536); volpan->dwVolAmpFactor = (ULONG) (pow(2.0, volpan->lVolume / 600.0) * 65536);
...@@ -190,7 +191,7 @@ static INT DSOUND_MixerNorm(IDirectSoundBufferImpl *dsb, BYTE *buf, INT len) ...@@ -190,7 +191,7 @@ static INT DSOUND_MixerNorm(IDirectSoundBufferImpl *dsb, BYTE *buf, INT len)
INT iAdvance = dsb->wfx.nBlockAlign; INT iAdvance = dsb->wfx.nBlockAlign;
INT oAdvance = dsb->dsound->wfx.nBlockAlign; INT oAdvance = dsb->dsound->wfx.nBlockAlign;
ibp = dsb->buffer + dsb->buf_mixpos; ibp = dsb->buffer->memory + dsb->buf_mixpos;
obp = buf; obp = buf;
TRACE("(%p, %p, %p), buf_mixpos=%ld\n", dsb, ibp, obp, dsb->buf_mixpos); TRACE("(%p, %p, %p), buf_mixpos=%ld\n", dsb, ibp, obp, dsb->buf_mixpos);
...@@ -204,7 +205,7 @@ static INT DSOUND_MixerNorm(IDirectSoundBufferImpl *dsb, BYTE *buf, INT len) ...@@ -204,7 +205,7 @@ static INT DSOUND_MixerNorm(IDirectSoundBufferImpl *dsb, BYTE *buf, INT len)
memcpy(obp, ibp, len); memcpy(obp, ibp, len);
else { /* wrap */ else { /* wrap */
memcpy(obp, ibp, bytesleft ); memcpy(obp, ibp, bytesleft );
memcpy(obp + bytesleft, dsb->buffer, len - bytesleft); memcpy(obp + bytesleft, dsb->buffer->memory, len - bytesleft);
} }
return len; return len;
} }
...@@ -219,8 +220,8 @@ static INT DSOUND_MixerNorm(IDirectSoundBufferImpl *dsb, BYTE *buf, INT len) ...@@ -219,8 +220,8 @@ static INT DSOUND_MixerNorm(IDirectSoundBufferImpl *dsb, BYTE *buf, INT len)
ibp += iAdvance; ibp += iAdvance;
ilen += iAdvance; ilen += iAdvance;
obp += oAdvance; obp += oAdvance;
if (ibp >= (BYTE *)(dsb->buffer + dsb->buflen)) if (ibp >= (BYTE *)(dsb->buffer->memory + dsb->buflen))
ibp = dsb->buffer; /* wrap */ ibp = dsb->buffer->memory; /* wrap */
} }
return (ilen); return (ilen);
} }
...@@ -240,7 +241,7 @@ static INT DSOUND_MixerNorm(IDirectSoundBufferImpl *dsb, BYTE *buf, INT len) ...@@ -240,7 +241,7 @@ static INT DSOUND_MixerNorm(IDirectSoundBufferImpl *dsb, BYTE *buf, INT len)
ilen = 0; ilen = 0;
ipos = dsb->buf_mixpos; ipos = dsb->buf_mixpos;
for (i = 0; i < size; i++) { for (i = 0; i < size; i++) {
cp_fields(dsb, (dsb->buffer + ipos), obp); cp_fields(dsb, (dsb->buffer->memory + ipos), obp);
obp += oAdvance; obp += oAdvance;
dsb->freqAcc += dsb->freqAdjust; dsb->freqAcc += dsb->freqAdjust;
if (dsb->freqAcc >= (1<<DSOUND_FREQSHIFT)) { if (dsb->freqAcc >= (1<<DSOUND_FREQSHIFT)) {
...@@ -380,7 +381,8 @@ static DWORD DSOUND_MixInBuffer(IDirectSoundBufferImpl *dsb, DWORD writepos, DWO ...@@ -380,7 +381,8 @@ static DWORD DSOUND_MixInBuffer(IDirectSoundBufferImpl *dsb, DWORD writepos, DWO
ilen = DSOUND_MixerNorm(dsb, ibuf, len); ilen = DSOUND_MixerNorm(dsb, ibuf, len);
if ((dsb->dsbd.dwFlags & DSBCAPS_CTRLPAN) || if ((dsb->dsbd.dwFlags & DSBCAPS_CTRLPAN) ||
(dsb->dsbd.dwFlags & DSBCAPS_CTRLVOLUME)) (dsb->dsbd.dwFlags & DSBCAPS_CTRLVOLUME) ||
(dsb->dsbd.dwFlags & DSBCAPS_CTRL3D))
DSOUND_MixerVol(dsb, ibuf, len); DSOUND_MixerVol(dsb, ibuf, len);
obuf = dsb->dsound->buffer + writepos; obuf = dsb->dsound->buffer + writepos;
...@@ -450,7 +452,8 @@ static void DSOUND_PhaseCancel(IDirectSoundBufferImpl *dsb, DWORD writepos, DWOR ...@@ -450,7 +452,8 @@ static void DSOUND_PhaseCancel(IDirectSoundBufferImpl *dsb, DWORD writepos, DWOR
ilen = DSOUND_MixerNorm(dsb, ibuf, len); ilen = DSOUND_MixerNorm(dsb, ibuf, len);
if ((dsb->dsbd.dwFlags & DSBCAPS_CTRLPAN) || if ((dsb->dsbd.dwFlags & DSBCAPS_CTRLPAN) ||
(dsb->dsbd.dwFlags & DSBCAPS_CTRLVOLUME)) (dsb->dsbd.dwFlags & DSBCAPS_CTRLVOLUME) ||
(dsb->dsbd.dwFlags & DSBCAPS_CTRL3D))
DSOUND_MixerVol(dsb, ibuf, len); DSOUND_MixerVol(dsb, ibuf, len);
/* subtract instead of add, to phase out premixed data */ /* subtract instead of add, to phase out premixed data */
...@@ -844,21 +847,12 @@ void DSOUND_PerformMix(void) ...@@ -844,21 +847,12 @@ void DSOUND_PerformMix(void)
TRACE("()\n"); TRACE("()\n");
RtlAcquireResourceShared(&(dsound->lock), TRUE);
if (!dsound || !dsound->ref) {
/* seems the dsound object is currently being released */
RtlReleaseResource(&(dsound->lock));
return;
}
/* the sound of silence */ /* the sound of silence */
nfiller = dsound->wfx.wBitsPerSample == 8 ? 128 : 0; nfiller = dsound->wfx.wBitsPerSample == 8 ? 128 : 0;
/* whether the primary is forced to play even without secondary buffers */ /* whether the primary is forced to play even without secondary buffers */
forced = ((dsound->state == STATE_PLAYING) || (dsound->state == STATE_STARTING)); forced = ((dsound->state == STATE_PLAYING) || (dsound->state == STATE_STARTING));
TRACE("entering at %ld\n", GetTickCount());
if (dsound->priolevel != DSSCL_WRITEPRIMARY) { if (dsound->priolevel != DSSCL_WRITEPRIMARY) {
BOOL paused = ((dsound->state == STATE_STOPPED) || (dsound->state == STATE_STARTING)); BOOL paused = ((dsound->state == STATE_STOPPED) || (dsound->state == STATE_STARTING));
/* FIXME: document variables */ /* FIXME: document variables */
...@@ -866,7 +860,7 @@ void DSOUND_PerformMix(void) ...@@ -866,7 +860,7 @@ void DSOUND_PerformMix(void)
if (dsound->hwbuf) { if (dsound->hwbuf) {
hres = IDsDriverBuffer_GetPosition(dsound->hwbuf, &playpos, &writepos); hres = IDsDriverBuffer_GetPosition(dsound->hwbuf, &playpos, &writepos);
if (hres) { if (hres) {
RtlReleaseResource(&(dsound->lock)); WARN("IDsDriverBuffer_GetPosition failed\n");
return; return;
} }
/* Well, we *could* do Just-In-Time mixing using the writepos, /* Well, we *could* do Just-In-Time mixing using the writepos,
...@@ -954,7 +948,7 @@ void DSOUND_PerformMix(void) ...@@ -954,7 +948,7 @@ void DSOUND_PerformMix(void)
hres = IDsDriverBuffer_GetPosition(dsound->hwbuf, &playpos, NULL); hres = IDsDriverBuffer_GetPosition(dsound->hwbuf, &playpos, NULL);
if (hres) { if (hres) {
LeaveCriticalSection(&(dsound->mixlock)); LeaveCriticalSection(&(dsound->mixlock));
RtlReleaseResource(&(dsound->lock)); WARN("IDsDriverBuffer_GetPosition failed\n");
return; return;
} }
} else { } else {
...@@ -1012,12 +1006,13 @@ void DSOUND_PerformMix(void) ...@@ -1012,12 +1006,13 @@ void DSOUND_PerformMix(void)
dsound->state = STATE_STOPPED; dsound->state = STATE_STOPPED;
} }
} }
TRACE("completed processing at %ld\n", GetTickCount());
RtlReleaseResource(&(dsound->lock));
} }
void CALLBACK DSOUND_timer(UINT timerID, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2) void CALLBACK DSOUND_timer(UINT timerID, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2)
{ {
TRACE("(%d,%d,0x%lx,0x%lx,0x%lx)\n",timerID,msg,dwUser,dw1,dw2);
TRACE("entering at %ld\n", GetTickCount());
if (!dsound) { if (!dsound) {
ERR("dsound died without killing us?\n"); ERR("dsound died without killing us?\n");
timeKillEvent(timerID); timeKillEvent(timerID);
...@@ -1025,8 +1020,15 @@ void CALLBACK DSOUND_timer(UINT timerID, UINT msg, DWORD dwUser, DWORD dw1, DWOR ...@@ -1025,8 +1020,15 @@ void CALLBACK DSOUND_timer(UINT timerID, UINT msg, DWORD dwUser, DWORD dw1, DWOR
return; return;
} }
TRACE("entered\n"); RtlAcquireResourceShared(&(dsound->lock), TRUE);
DSOUND_PerformMix();
if (dsound->ref) {
DSOUND_PerformMix();
}
RtlReleaseResource(&(dsound->lock));
TRACE("completed processing at %ld\n", GetTickCount());
} }
void CALLBACK DSOUND_callback(HWAVEOUT hwo, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2) void CALLBACK DSOUND_callback(HWAVEOUT hwo, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2)
......
...@@ -430,7 +430,7 @@ static HRESULT WINAPI PrimaryBufferImpl_SetVolume( ...@@ -430,7 +430,7 @@ static HRESULT WINAPI PrimaryBufferImpl_SetVolume(
TRACE("(%p,%ld)\n",This,vol); TRACE("(%p,%ld)\n",This,vol);
/* I'm not sure if we need this for primary buffer */ /* I'm not sure if we need this for primary buffer */
if (!(This->dsbd.dwFlags & DSBCAPS_CTRLVOLUME)) { if (!(This->dsound->dsbd.dwFlags & DSBCAPS_CTRLVOLUME)) {
WARN("control unavailable\n"); WARN("control unavailable\n");
return DSERR_CONTROLUNAVAIL; return DSERR_CONTROLUNAVAIL;
} }
...@@ -480,6 +480,11 @@ static HRESULT WINAPI PrimaryBufferImpl_GetVolume( ...@@ -480,6 +480,11 @@ static HRESULT WINAPI PrimaryBufferImpl_GetVolume(
ICOM_THIS(PrimaryBufferImpl,iface); ICOM_THIS(PrimaryBufferImpl,iface);
TRACE("(%p,%p)\n",This,vol); TRACE("(%p,%p)\n",This,vol);
if (!(This->dsound->dsbd.dwFlags & DSBCAPS_CTRLVOLUME)) {
WARN("control unavailable\n");
return DSERR_CONTROLUNAVAIL;
}
if (vol == NULL) { if (vol == NULL) {
WARN("invalid parameter: vol = NULL\n"); WARN("invalid parameter: vol = NULL\n");
return DSERR_INVALIDPARAM; return DSERR_INVALIDPARAM;
...@@ -555,29 +560,21 @@ static DWORD WINAPI PrimaryBufferImpl_AddRef(LPDIRECTSOUNDBUFFER8 iface) { ...@@ -555,29 +560,21 @@ static DWORD WINAPI PrimaryBufferImpl_AddRef(LPDIRECTSOUNDBUFFER8 iface) {
ICOM_THIS(PrimaryBufferImpl,iface); ICOM_THIS(PrimaryBufferImpl,iface);
DWORD ref; DWORD ref;
TRACE("(%p) ref was %ld, thread is %lx\n",This, This->ref, GetCurrentThreadId()); TRACE("(%p) ref was %ld, thread is %04lx\n",This, This->ref, GetCurrentThreadId());
ref = InterlockedIncrement(&(This->ref)); ref = InterlockedIncrement(&(This->ref));
if (!ref) {
FIXME("thread-safety alert! AddRef-ing with a zero refcount!\n");
}
return ref; return ref;
} }
static DWORD WINAPI PrimaryBufferImpl_Release(LPDIRECTSOUNDBUFFER8 iface) { static DWORD WINAPI PrimaryBufferImpl_Release(LPDIRECTSOUNDBUFFER8 iface) {
ICOM_THIS(PrimaryBufferImpl,iface); ICOM_THIS(PrimaryBufferImpl,iface);
DWORD ref; DWORD ref;
TRACE("(%p) ref was %ld, thread is %lx\n",This, This->ref, GetCurrentThreadId()); TRACE("(%p) ref was %ld, thread is %04lx\n",This, This->ref, GetCurrentThreadId());
ref = InterlockedDecrement(&(This->ref)); ref = InterlockedDecrement(&(This->ref));
if (ref == 0) { if (ref == -1) {
IDirectSound_Release((LPDIRECTSOUND)This->dsound); This->dsound->primary = NULL;
if (This->dsound->listener) {
IDirectSound3DListener_Release((LPDIRECTSOUND3DLISTENER)This->dsound->listener);
This->dsound->listener = NULL;
}
HeapFree(GetProcessHeap(),0,This); HeapFree(GetProcessHeap(),0,This);
} }
...@@ -611,7 +608,7 @@ static HRESULT WINAPI PrimaryBufferImpl_GetStatus( ...@@ -611,7 +608,7 @@ static HRESULT WINAPI PrimaryBufferImpl_GetStatus(
LPDIRECTSOUNDBUFFER8 iface,LPDWORD status LPDIRECTSOUNDBUFFER8 iface,LPDWORD status
) { ) {
ICOM_THIS(PrimaryBufferImpl,iface); ICOM_THIS(PrimaryBufferImpl,iface);
TRACE("(%p,%p), thread is %lx\n",This,status,GetCurrentThreadId()); TRACE("(%p,%p), thread is %04lx\n",This,status,GetCurrentThreadId());
if (status == NULL) { if (status == NULL) {
WARN("invalid parameter: status == NULL\n"); WARN("invalid parameter: status == NULL\n");
...@@ -812,6 +809,11 @@ static HRESULT WINAPI PrimaryBufferImpl_GetFrequency( ...@@ -812,6 +809,11 @@ static HRESULT WINAPI PrimaryBufferImpl_GetFrequency(
return DSERR_INVALIDPARAM; return DSERR_INVALIDPARAM;
} }
if (!(This->dsound->dsbd.dwFlags & DSBCAPS_CTRLFREQUENCY)) {
WARN("control unavailable\n");
return DSERR_CONTROLUNAVAIL;
}
*freq = This->dsound->wfx.nSamplesPerSec; *freq = This->dsound->wfx.nSamplesPerSec;
TRACE("-> %ld\n", *freq); TRACE("-> %ld\n", *freq);
...@@ -885,7 +887,7 @@ static HRESULT WINAPI PrimaryBufferImpl_GetCaps( ...@@ -885,7 +887,7 @@ static HRESULT WINAPI PrimaryBufferImpl_GetCaps(
return DSERR_INVALIDPARAM; return DSERR_INVALIDPARAM;
} }
caps->dwFlags = This->dsbd.dwFlags; caps->dwFlags = This->dsound->dsbd.dwFlags;
if (This->dsound->hwbuf) caps->dwFlags |= DSBCAPS_LOCHARDWARE; if (This->dsound->hwbuf) caps->dwFlags |= DSBCAPS_LOCHARDWARE;
else caps->dwFlags |= DSBCAPS_LOCSOFTWARE; else caps->dwFlags |= DSBCAPS_LOCSOFTWARE;
...@@ -905,56 +907,58 @@ static HRESULT WINAPI PrimaryBufferImpl_QueryInterface( ...@@ -905,56 +907,58 @@ static HRESULT WINAPI PrimaryBufferImpl_QueryInterface(
LPDIRECTSOUNDBUFFER8 iface,REFIID riid,LPVOID *ppobj LPDIRECTSOUNDBUFFER8 iface,REFIID riid,LPVOID *ppobj
) { ) {
ICOM_THIS(PrimaryBufferImpl,iface); ICOM_THIS(PrimaryBufferImpl,iface);
TRACE("(%p,%s,%p)\n",This,debugstr_guid(riid),ppobj); TRACE("(%p,%s,%p)\n",This,debugstr_guid(riid),ppobj);
*ppobj = NULL; /* assume failure */
if ( IsEqualGUID(riid, &IID_IUnknown) ||
IsEqualGUID(riid, &IID_IDirectSoundBuffer) ) {
IDirectSoundBuffer_AddRef((LPDIRECTSOUNDBUFFER)This);
*ppobj = This;
return S_OK;
}
/* DirectSoundBuffer and DirectSoundBuffer8 are different and */
/* a primary buffer can't have a DirectSoundBuffer8 interface */
if ( IsEqualGUID( &IID_IDirectSoundBuffer8, riid ) ) {
WARN("app requested DirectSoundBuffer8 on primary buffer\n");
return E_NOINTERFACE;
}
if ( IsEqualGUID( &IID_IDirectSoundNotify, riid ) ) { if ( IsEqualGUID( &IID_IDirectSoundNotify, riid ) ) {
ERR("app requested IDirectSoundNotify on primary buffer\n"); ERR("app requested IDirectSoundNotify on primary buffer\n");
/* FIXME: should we support this? */ /* FIXME: should we support this? */
*ppobj = NULL; return E_NOINTERFACE;
return E_FAIL;
} }
if ( IsEqualGUID( &IID_IDirectSound3DBuffer, riid ) ) { if ( IsEqualGUID( &IID_IDirectSound3DBuffer, riid ) ) {
ERR("app requested IDirectSound3DBuffer on primary buffer\n"); ERR("app requested IDirectSound3DBuffer on primary buffer\n");
*ppobj = NULL;
return E_NOINTERFACE; return E_NOINTERFACE;
} }
if ( IsEqualGUID( &IID_IDirectSound3DListener, riid ) ) { if ( IsEqualGUID( &IID_IDirectSound3DListener, riid ) ) {
if (!This->dsound->listener) if (This->dsound->dsbd.dwFlags & DSBCAPS_CTRL3D) {
IDirectSound3DListenerImpl_Create(This, &This->dsound->listener); if (!This->dsound->listener)
*ppobj = This->dsound->listener; IDirectSound3DListenerImpl_Create(This, &This->dsound->listener);
if (This->dsound->listener) {
IDirectSound3DListener_AddRef((LPDIRECTSOUND3DLISTENER)*ppobj); *ppobj = This->dsound->listener;
return DS_OK;
if (This->dsound->listener) {
IDirectSound3DListener_AddRef((LPDIRECTSOUND3DLISTENER)*ppobj);
return S_OK;
}
} }
WARN("IID_IDirectSound3DListener failed\n"); WARN("IID_IDirectSound3DListener failed\n");
*ppobj = NULL; return E_NOINTERFACE;
return E_FAIL;
} }
if ( IsEqualGUID( &IID_IKsPropertySet, riid ) ) { if ( IsEqualGUID( &IID_IKsPropertySet, riid ) ) {
#if 0
if (!This->iks)
IKsPropertySetImpl_Create(This, &This->iks);
*ppobj = This->iks;
if (*ppobj) {
IKsPropertySet_AddRef((LPKSPROPERTYSET)*ppobj);
return S_OK;
}
return E_FAIL;
#else
FIXME("app requested IKsPropertySet on primary buffer\n"); FIXME("app requested IKsPropertySet on primary buffer\n");
*ppobj = NULL; return E_NOINTERFACE;
return E_FAIL;
#endif
} }
FIXME( "Unknown IID %s\n", debugstr_guid( riid ) ); FIXME( "Unknown IID %s\n", debugstr_guid( riid ) );
*ppobj = NULL;
return E_NOINTERFACE; return E_NOINTERFACE;
} }
...@@ -1010,11 +1014,11 @@ HRESULT WINAPI PrimaryBuffer_Create( ...@@ -1010,11 +1014,11 @@ HRESULT WINAPI PrimaryBuffer_Create(
return DSERR_OUTOFMEMORY; return DSERR_OUTOFMEMORY;
} }
dsb->ref = 1; dsb->ref = -1;
dsb->dsound = This; dsb->dsound = This;
dsb->lpVtbl = &dspbvt; dsb->lpVtbl = &dspbvt;
memcpy(&dsb->dsbd, dsbd, sizeof(*dsbd)); memcpy(&This->dsbd, dsbd, sizeof(*dsbd));
TRACE("Created primary buffer at %p\n", dsb); TRACE("Created primary buffer at %p\n", dsb);
TRACE("(formattag=0x%04x,chans=%d,samplerate=%ld," TRACE("(formattag=0x%04x,chans=%d,samplerate=%ld,"
...@@ -1023,18 +1027,6 @@ HRESULT WINAPI PrimaryBuffer_Create( ...@@ -1023,18 +1027,6 @@ HRESULT WINAPI PrimaryBuffer_Create(
This->wfx.nAvgBytesPerSec, This->wfx.nBlockAlign, This->wfx.nAvgBytesPerSec, This->wfx.nBlockAlign,
This->wfx.wBitsPerSample, This->wfx.cbSize); This->wfx.wBitsPerSample, This->wfx.cbSize);
if (dsbd->dwFlags & DSBCAPS_CTRL3D) {
HRESULT hres;
hres = IDirectSound3DListenerImpl_Create(dsb, &This->listener);
if (hres != DS_OK) {
WARN("IDirectSound3DListenerImpl_Create failed\n");
} else {
IDirectSound3DListener_AddRef((LPDIRECTSOUND3DLISTENER)This->listener);
}
}
IDirectSound8_AddRef((LPDIRECTSOUND8)This);
*pdsb = dsb; *pdsb = dsb;
return S_OK; return S_OK;
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment