mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2072 lines
56 KiB
2072 lines
56 KiB
//--------------------------------------------------------------------------;
|
|
//
|
|
// File: DSound.c
|
|
//
|
|
// Copyright (c) 1995 Microsoft Corporation. All Rights Reserved.
|
|
//
|
|
// Abstract:
|
|
//
|
|
//
|
|
// Contents:
|
|
// IDSWECreateSoundBuffer()
|
|
// DirectSoundCreate()
|
|
// DirectSoundEnumerateA()
|
|
// CreateNewDirectSoundObject()
|
|
//
|
|
// History:
|
|
// Date By Reason
|
|
// ==== == ======
|
|
// 3/5/96 angusm Added use of fInitialized
|
|
//
|
|
//--------------------------------------------------------------------------;
|
|
#define INITGUID
|
|
#include "dsoundpr.h"
|
|
#include <mmddk.h>
|
|
#include <initguid.h>
|
|
#include "dsdriver.h"
|
|
#include "grace.h"
|
|
#include "resource.h"
|
|
#include "flocks.h"
|
|
|
|
#define TID_INIT_VALUE 0xffffffff // This is a dummy value that can not be a valid TID
|
|
|
|
#if !defined NUMELMS
|
|
#define NUMELMS(aa) (sizeof(aa)/sizeof((aa)[0]))
|
|
#endif
|
|
|
|
// internal Kernel32 API
|
|
extern DWORD WINAPI OpenVxDHandle(HANDLE hSource);
|
|
|
|
LPDSOUNDINFO gpdsinfo = NULL;
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------------;
|
|
//
|
|
// BOOL wavIsMappable
|
|
//
|
|
// Description:
|
|
// This function determines whether a specified wave device is
|
|
// a mappable device
|
|
//
|
|
// Arguments:
|
|
// UINT uWaveId :
|
|
//
|
|
// Return (BOOL): TRUE if and only if it the wave device is a mappable device
|
|
//
|
|
// History:
|
|
// 09/09/95 FrankYe Created
|
|
//
|
|
//--------------------------------------------------------------------------;
|
|
BOOL wavIsMappable(UINT uWaveId)
|
|
{
|
|
MMRESULT mmr;
|
|
|
|
mmr = waveOutMessage((HWAVEOUT)uWaveId, DRV_QUERYMAPPABLE, 0, 0);
|
|
return (MMSYSERR_NOERROR == mmr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------;
|
|
//
|
|
// void wavGetPreferredId
|
|
//
|
|
// Description:
|
|
// This function accesses the registry settings maintained by the
|
|
// wave mapper and multimedia control panel to determine the wave id
|
|
// of the preferred sound device.
|
|
//
|
|
// Arguments:
|
|
// LPUINT puWaveId :
|
|
//
|
|
// LPBOOL pfPreferredOnly :
|
|
//
|
|
// Return (void):
|
|
//
|
|
// History:
|
|
// 09/09/95 FrankYe Created
|
|
//
|
|
//--------------------------------------------------------------------------;
|
|
const TCHAR gszRegKeynameWaveMapper[] =
|
|
TEXT("Software\\Microsoft\\Multimedia\\Sound Mapper");
|
|
const TCHAR gszRegValuenamePreferredPlayback[] =
|
|
TEXT("Playback");
|
|
const TCHAR gszRegValuenamePreferredOnly[] =
|
|
TEXT("PreferredOnly");
|
|
|
|
BOOL
|
|
wavGetPreferredId(LPUINT puWaveId, LPBOOL pfPreferredOnly)
|
|
{
|
|
UINT u;
|
|
WAVEOUTCAPS woc;
|
|
HKEY hkeyWaveMapper;
|
|
TCHAR szPreferred[MAXPNAMELEN];
|
|
DWORD dwPreferredOnly;
|
|
DWORD dwType;
|
|
DWORD cbData;
|
|
LONG lr;
|
|
|
|
*puWaveId = 0;
|
|
*pfPreferredOnly = 0;
|
|
|
|
|
|
lr = RegOpenKeyEx(HKEY_CURRENT_USER, gszRegKeynameWaveMapper, 0,
|
|
KEY_QUERY_VALUE, &hkeyWaveMapper);
|
|
|
|
if (ERROR_SUCCESS == lr) {
|
|
|
|
szPreferred[0] = '\0';
|
|
dwType = REG_SZ;
|
|
cbData = sizeof(szPreferred);
|
|
lr = RegQueryValueEx(hkeyWaveMapper, gszRegValuenamePreferredPlayback,
|
|
NULL, &dwType, szPreferred, &cbData);
|
|
|
|
if (ERROR_SUCCESS == lr) {
|
|
|
|
dwType = REG_DWORD;
|
|
cbData = sizeof(dwPreferredOnly);
|
|
lr = RegQueryValueEx(hkeyWaveMapper, gszRegValuenamePreferredOnly,
|
|
NULL, &dwType, (LPBYTE)&dwPreferredOnly, &cbData);
|
|
|
|
if (ERROR_SUCCESS != lr) {
|
|
dwPreferredOnly = 0;
|
|
}
|
|
|
|
for (u = 0; u < waveOutGetNumDevs(); u++) {
|
|
if (!waveOutGetDevCaps(u, &woc, sizeof(woc))) {
|
|
|
|
woc.szPname[SIZEOF(woc.szPname)-1] = '\0';
|
|
if (!lstrcmp(woc.szPname, szPreferred)) {
|
|
*puWaveId = u;
|
|
*pfPreferredOnly = (0 != dwPreferredOnly);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
RegCloseKey(hkeyWaveMapper);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------;
|
|
//
|
|
// HRESULT wavGetIdFromDrvGuid
|
|
//
|
|
// Description:
|
|
// This function finds the mmsystem wave ID of the device that
|
|
// corresponds the the specified ds driver
|
|
//
|
|
// Arguments:
|
|
// LPGUID refGuid : guid of a ds driver
|
|
//
|
|
// LPUINT puWaveId : pointer to UINT to receive the mmsystem wave id
|
|
//
|
|
// Return (HRESULT):
|
|
//
|
|
// History:
|
|
//
|
|
//--------------------------------------------------------------------------;
|
|
HRESULT wavGetIdFromDrvGuid(REFGUID refGuid, LPUINT puWaveId)
|
|
{
|
|
DSDRIVERDESC dsDrvDesc;
|
|
DWORD dnDevNode;
|
|
UINT uDeviceNum;
|
|
UINT uWaveId;
|
|
UINT cWaveDevices;
|
|
BOOL fFound;
|
|
UINT i;
|
|
MMRESULT mmr;
|
|
DSVAL dsv;
|
|
|
|
|
|
FillMemory(&dsDrvDesc, sizeof(dsDrvDesc), 0);
|
|
dsv = vxdDrvGetDesc(refGuid, &dsDrvDesc);
|
|
if (DS_OK != dsv) return dsv;
|
|
|
|
uDeviceNum = dsDrvDesc.ulDeviceNum+1;
|
|
|
|
cWaveDevices = waveOutGetNumDevs();
|
|
|
|
DPF(3, "Looking for corresponding mmsystem driver...");
|
|
|
|
for (uWaveId = 0; uWaveId < cWaveDevices; uWaveId++) {
|
|
WORD awVxdIds[50]; // we won't handle more than 50 vxds on the devnode
|
|
DWORD cReturnedIds;
|
|
|
|
mmr = waveOutMessage((HWAVEOUT)uWaveId,
|
|
DRV_QUERYDEVNODE,
|
|
(DWORD)&dnDevNode, 0);
|
|
|
|
if (MMSYSERR_NOERROR != mmr) break;
|
|
if (dnDevNode != dsDrvDesc.dnDevNode) continue;
|
|
|
|
cReturnedIds = sizeof(awVxdIds) / sizeof(awVxdIds[0]);
|
|
mmr = waveOutMessage((HWAVEOUT)uWaveId,
|
|
DRV_QUERYDRIVERIDS,
|
|
(DWORD)awVxdIds, (DWORD)&cReturnedIds);
|
|
|
|
if (MMSYSERR_INVALPARAM == mmr) continue;
|
|
if (MMSYSERR_NOERROR != mmr) break;
|
|
|
|
fFound = FALSE;
|
|
for (i=0; i < cReturnedIds; i++) {
|
|
if (awVxdIds[i] == dsDrvDesc.wVxdId) {
|
|
fFound = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( (fFound) && (0 == --uDeviceNum) ) break;
|
|
}
|
|
|
|
if (0 == uDeviceNum) {
|
|
*puWaveId = uWaveId;
|
|
dsv = DS_OK;
|
|
} else {
|
|
dsv = DSERR_NODRIVER;
|
|
}
|
|
|
|
return dsv;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------;
|
|
//
|
|
// HRESULT wavGetDrvGuidFromId
|
|
//
|
|
// Description:
|
|
//
|
|
// Arguments:
|
|
// UINT uWaveId : wave id
|
|
//
|
|
// LPGUID pGuid : ptr to guid to receive ds driver guid
|
|
//
|
|
// Return (HRESULT):
|
|
//
|
|
// History:
|
|
//
|
|
//--------------------------------------------------------------------------;
|
|
HRESULT wavGetDrvGuidFromId(UINT uWaveIdCaller, LPGUID pGuidCaller)
|
|
{
|
|
DSDRIVERDESC dsDrvDesc;
|
|
GUID guid, guidLast;
|
|
UINT uWaveId;
|
|
HRESULT hr;
|
|
|
|
FillMemory(&dsDrvDesc, sizeof(dsDrvDesc), 0);
|
|
hr = vxdDrvGetNextDriverDesc(NULL, &guid, &dsDrvDesc);
|
|
while (DS_OK == hr) {
|
|
hr = wavGetIdFromDrvGuid(&guid, &uWaveId);
|
|
if ((DS_OK == hr) && (uWaveId == uWaveIdCaller)) {
|
|
*pGuidCaller = guid;
|
|
return DS_OK;
|
|
}
|
|
guidLast = guid;
|
|
FillMemory(&dsDrvDesc, sizeof(dsDrvDesc), 0);
|
|
hr = vxdDrvGetNextDriverDesc(&guidLast, &guid, &dsDrvDesc);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------;
|
|
//
|
|
// LPDSBUFFER ICreateSoundBuffer
|
|
//
|
|
// Description:
|
|
// This function is the member function for CreateSoundBuffer.
|
|
//
|
|
// Arguments:
|
|
// LPDIRECTSOUND pids: Pointer to Direct Sound Object.
|
|
//
|
|
// LPDSBUFFERCREATE pdsbc: Pointer to a DSBufferCreate structure.
|
|
//
|
|
// Return (LPDSBUFFER):
|
|
// Pointer to a DSBUFFER structure.
|
|
//
|
|
// History:
|
|
//
|
|
//--------------------------------------------------------------------------;
|
|
|
|
HRESULT FAR PASCAL WaveEmulateCreateSoundBuffer
|
|
(
|
|
LPDSOUND pds,
|
|
LPDSBUFFER pdsb,
|
|
LPDSBUFFERDESC pdsbd
|
|
)
|
|
{
|
|
MMRESULT mmr;
|
|
|
|
|
|
DPF(3,"Wave Emulate CreateSoundBuffer");
|
|
|
|
// All validation done in other case
|
|
|
|
//If not primary, allocate the actual buffer
|
|
if (!((pdsbd->dwFlags & DSBCAPS_PRIMARYBUFFER))) {
|
|
pdsb->pDSBuffer = MemAlloc(pdsbd->dwBufferBytes);
|
|
if (!pdsb->pDSBuffer) {
|
|
DPF(1,"WE CreateSoundBuffer cannot allocate buffer");
|
|
return DSERR_OUTOFMEMORY;
|
|
}
|
|
} else {
|
|
pdsb->pDSBuffer = NULL;
|
|
}
|
|
|
|
pdsb->cbBufferSize = pdsbd->dwBufferBytes;
|
|
|
|
pdsb->fdwDsbI |= DSB_INTERNALF_WAVEEMULATED;
|
|
pdsb->fdwDsbI |= DSB_INTERNALF_EMULATED;
|
|
|
|
DPF(3,"--------Alloc data for WAVE Emulated obj %X buff %X len %X",
|
|
pdsb, pdsb->pDSBuffer, pdsb->cbBufferSize );
|
|
|
|
|
|
if (pdsbd->dwFlags & DSBCAPS_PRIMARYBUFFER) {
|
|
|
|
mmr = DSInitializeEmulator(pds);
|
|
if (mmr) {
|
|
DPF(0,"DSInitEmu returns %u", (UINT)mmr);
|
|
if (MMSYSERR_ALLOCATED == mmr) return DSERR_ALLOCATED;
|
|
if (MMSYSERR_NOMEM == mmr) return DSERR_OUTOFMEMORY;
|
|
return DSERR_GENERIC;
|
|
}
|
|
}
|
|
|
|
|
|
DPF(3,"Wave Emulate CreateSoundBuffer Exit");
|
|
return DS_OK;
|
|
} // IDSCreateSoundBuffer()
|
|
|
|
|
|
//--------------------------------------------------------------------------;
|
|
//
|
|
// DsGetDriverVersion
|
|
//
|
|
// Description: Figure out driver version, and sets the pds version fields.
|
|
//
|
|
//--------------------------------------------------------------------------;
|
|
BOOL DsGetDriverVersion
|
|
(
|
|
LPDSOUND pds,
|
|
LPTSTR szDrvname
|
|
)
|
|
{
|
|
TCHAR szDriverPath[MAX_PATH];
|
|
int iStrLen;
|
|
DWORD dwHandle;
|
|
DWORD dwVerLen;
|
|
LPVOID pvVerInfo;
|
|
DWORD dwVsffiLen;
|
|
VS_FIXEDFILEINFO* pvsffi;
|
|
BOOL fSucceeded;
|
|
|
|
if( !GetSystemDirectory( szDriverPath, MAX_PATH ) ) {
|
|
DPF(0,"DsGetDriverVersion - GetSystemDirectory failed!");
|
|
return FALSE;
|
|
}
|
|
|
|
iStrLen = lstrlen( szDriverPath );
|
|
if( iStrLen + 1 + lstrlen(szDrvname) >= MAX_PATH ) {
|
|
DPF(0,"DsGetDriverVersion - Ran out of path room!");
|
|
return FALSE;
|
|
}
|
|
|
|
// We have enough room.
|
|
if( szDriverPath[iStrLen-1] != TEXT('\\') )
|
|
{
|
|
szDriverPath[iStrLen++] = TEXT('\\');
|
|
szDriverPath[iStrLen] = TEXT('\0');
|
|
}
|
|
|
|
lstrcat( szDriverPath, szDrvname );
|
|
dwVerLen = GetFileVersionInfoSize( szDriverPath, &dwHandle );
|
|
if( 0 == dwVerLen ) {
|
|
DPF(0,"DsGetDriverVersion - GetFileVersionInfoSize failed!");
|
|
return FALSE;
|
|
}
|
|
|
|
pvVerInfo = LocalAlloc ( LPTR, dwVerLen );
|
|
if( NULL == pvVerInfo ) {
|
|
DPF(0,"DsGetDriverVersion - Couldn't allocate memory for pvVerInfo!");
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
// Side effects begin... no more returns.
|
|
fSucceeded = FALSE;
|
|
|
|
if( !GetFileVersionInfo( szDriverPath, dwHandle, dwVerLen, pvVerInfo ) ) {
|
|
DPF(0,"DsGetDriverVersion - GetFileVersionInfo failed!");
|
|
}
|
|
else
|
|
{
|
|
dwVsffiLen = sizeof( VS_FIXEDFILEINFO );
|
|
if( !VerQueryValue( pvVerInfo, TEXT("\\"), (LPVOID *)&pvsffi, &dwVsffiLen ) ) {
|
|
DPF(0,"DsGetDriverVersion - VerQueryValue failed!");
|
|
} else {
|
|
pds->dwDriverVersionMajor = pvsffi->dwFileVersionMS;
|
|
pds->dwDriverVersionMinor = pvsffi->dwFileVersionLS;
|
|
DPF(3,"DsGetDriverVersion - succeeded, major=0x%08x, minor=0x%08x",pds->dwDriverVersionMajor,pds->dwDriverVersionMinor);
|
|
fSucceeded = TRUE;
|
|
}
|
|
}
|
|
|
|
LocalFree( pvVerInfo );
|
|
return fSucceeded;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------;
|
|
//
|
|
// CreateDsNative
|
|
//
|
|
// This function creates a DSOUND object (a pds) using a native mode driver
|
|
// identified by a guid.
|
|
//
|
|
// Parameters:
|
|
// REFGUID rguid : identifies the guid of the requested driver
|
|
//
|
|
// LPDSOUND *ppds : receives a pointer to the new DSOUND object
|
|
//
|
|
// Return value (HRESULT):
|
|
// DS_OK : if and only if a DSOUND object has been created successfully
|
|
//
|
|
// DSERR_NODRIVER : if there no native driver having the specified guid
|
|
//
|
|
// other : some other failure
|
|
//
|
|
// Notes:
|
|
// For return values other than DS_OK, there should be no side effects
|
|
//
|
|
//--------------------------------------------------------------------------;
|
|
|
|
HRESULT WINAPI CreateDsNative(REFGUID rguid, LPDSOUND *ppds)
|
|
{
|
|
LPDSOUND pds;
|
|
DSDRIVERDESC dsDrvDesc;
|
|
UINT uWaveDeviceID;
|
|
BOOL fHaveWaveId;
|
|
int cb;
|
|
HRESULT hr;
|
|
MMRESULT mmr;
|
|
|
|
*ppds = NULL;
|
|
|
|
pds = (LPDSOUND)MemAlloc(sizeof(*pds));
|
|
if (NULL == pds) return DSERR_OUTOFMEMORY;
|
|
|
|
// We have a device that is for a Direct Sound MINIDRIVER
|
|
|
|
DPF(3,"Create DS pds = %X", pds );
|
|
|
|
ZeroMemory(&dsDrvDesc, sizeof(dsDrvDesc));
|
|
hr = vxdDrvGetDesc(rguid, &dsDrvDesc);
|
|
if (DS_OK == hr) {
|
|
|
|
pds->fdwDriverDesc = dsDrvDesc.dwFlags;
|
|
pds->dwHeapType = dsDrvDesc.dwHeapType;
|
|
pds->dwMemAllocExtra= dsDrvDesc.dwMemAllocExtra;
|
|
|
|
// Figure out if we're running on a certified driver.
|
|
// Sum of driver filename chars + DSCAPS_FILENAMECOOKIE
|
|
// mod DSCAPS_FILENAMEMODVALUE must equal dsDrvDesc.wReserved.
|
|
{
|
|
DWORD dwCertifiedCookie = (DWORD)dsDrvDesc.wReserved;
|
|
DWORD dwSum = DSCAPS_FILENAMECOOKIE;
|
|
PTCHAR p = dsDrvDesc.szDrvname;
|
|
|
|
for ( ; *p != TEXT('\0'); dwSum += (DWORD)(*p++));
|
|
|
|
if( dwSum%DSCAPS_FILENAMEMODVALUE == dwCertifiedCookie ) {
|
|
DPF(1,"Running on certified driver.");
|
|
pds->fdwInternal |= DS_INTERNALF_CERTIFIED;
|
|
}
|
|
}
|
|
|
|
// Set driver version numbers.
|
|
if( !DsGetDriverVersion( pds, dsDrvDesc.szDrvname ) ) {
|
|
DPF(0,"DirectSoundCreateFromGuid - DsGetDriverVersion failed!");
|
|
ASSERT( pds->dwDriverVersionMajor == 0 );
|
|
ASSERT( pds->dwDriverVersionMinor == 0 );
|
|
}
|
|
|
|
hr = wavGetIdFromDrvGuid(rguid, &uWaveDeviceID);
|
|
fHaveWaveId = (DS_OK == hr);
|
|
if (!(DSDDESC_DOMMSYSTEMOPEN & pds->fdwDriverDesc)) hr = DS_OK;
|
|
|
|
if ((DS_OK == hr) && fHaveWaveId) {
|
|
|
|
WAVEFORMATEX wfxJustToOpen = {
|
|
WAVE_FORMAT_PCM, // wFormatTag
|
|
1, // nChannels
|
|
11025, // nSamplesPerSec
|
|
11025, // nAvgBytesPerSec
|
|
1, // nBlockAlign
|
|
8, // wBitsPerSample
|
|
0 // cbSize
|
|
};
|
|
|
|
// The wfx is used only to open the mmsystem wave device, it
|
|
// has nothing to do with the format of any DirectSound
|
|
// buffers.
|
|
|
|
// We have to copy the wfx to shared memory since the ddhelp
|
|
// process will do the actuall open on the wave driver. The
|
|
// wfxDefault member of the ds object is a convenient place
|
|
// to put it since it has not yet been initialized with the
|
|
// default format for this ds object.
|
|
ASSERT(0 == pds->wfxDefault.wFormatTag);
|
|
CopyMemory(&pds->wfxDefault, &wfxJustToOpen, sizeof(wfxJustToOpen));
|
|
|
|
mmr = (DWORD)HelperWaveOpen(&pds->hwo,
|
|
uWaveDeviceID,
|
|
&pds->wfxDefault);
|
|
|
|
pds->wfxDefault.wFormatTag = 0;
|
|
|
|
if (MMSYSERR_NOERROR != mmr) {
|
|
DPF(0," Wave Open Failed ");
|
|
pds->hwo = NULL;
|
|
// We'll consider this an error only if the driver
|
|
// specified that we must do mmsystem open
|
|
if (DSDDESC_DOMMSYSTEMOPEN & pds->fdwDriverDesc) {
|
|
if (MMSYSERR_ALLOCATED == mmr) {
|
|
hr = DSERR_ALLOCATED;
|
|
} else {
|
|
hr = DSERR_NODRIVER;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If driver did not specify that we must do mmsystem open,
|
|
// then immediately close the device after opening it.
|
|
if ( !(DSDDESC_DOMMSYSTEMOPEN & pds->fdwDriverDesc) && (NULL != pds->hwo) ) {
|
|
HelperWaveClose((DWORD)pds->hwo);
|
|
pds->hwo = NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Try to open driver
|
|
//
|
|
if (DS_OK == hr) {
|
|
hr = vxdDrvOpen(rguid, &(pds->hHal));
|
|
if (DS_OK == hr) {
|
|
DPF(3,"Finished Open HAL layer %X", pds->hHal );
|
|
|
|
// Handle on-card memory management if necessary
|
|
//
|
|
pds->pDriverHeap = NULL;
|
|
if (DSDHEAP_USEDIRECTDRAWHEAP == pds->dwHeapType) {
|
|
pds->pDriverHeap = dsDrvDesc.pvDirectDrawHeap;
|
|
} else if (DSDHEAP_CREATEHEAP == pds->dwHeapType) {
|
|
pds->pDriverHeap = VidMemInit( VMEMHEAP_LINEAR,
|
|
dsDrvDesc.dwMemStartAddress,
|
|
dsDrvDesc.dwMemEndAddress,
|
|
0, 0 );
|
|
if (NULL == pds->pDriverHeap) {
|
|
RPF("DirectSoundCreate: Couldn't create driver heap for on-card memory" );
|
|
hr = DSERR_OUTOFMEMORY;
|
|
}
|
|
}
|
|
|
|
if (DS_OK == hr) {
|
|
mxInitialize(pds);
|
|
|
|
cb = sizeof(DSBWAVEBLTI) + (sizeof(DSBWAVEBLTSRCI)*LIMIT_BLT_SOURCES);
|
|
pds->pdswb = (LPDSBWAVEBLTI)MemAlloc(cb);
|
|
if (pds->pdswb) {
|
|
pds->pdswb->padswbs = (LPDSBWAVEBLTSRCI)(((LPSTR)pds->pdswb) + sizeof(*pds->pdswb));
|
|
pds->pdswb->dwSize = sizeof(DSBWAVEBLT);
|
|
pds->pdswb->dwCount = 0;
|
|
|
|
pds->guid = *rguid;
|
|
pds->uDeviceID = uWaveDeviceID;
|
|
pds->pds3d = NULL;
|
|
pds->uRefCount = 1;
|
|
pds->fdwInternal |= DS_INTERNALF_ALLOCATED;
|
|
pds->dwSig = DSOUNDSIG;
|
|
|
|
pds->pNext = gpdsinfo->pDSoundObj;
|
|
gpdsinfo->pDSoundObj = pds;
|
|
} else {
|
|
hr = DSERR_OUTOFMEMORY;
|
|
}
|
|
|
|
if (hr) mxTerminate(pds);
|
|
}
|
|
|
|
if (hr) {
|
|
if (pds->pDriverHeap && (pds->dwHeapType & DSDHEAP_CREATEHEAP)) {
|
|
VidMemFini(pds->pDriverHeap);
|
|
pds->pDriverHeap = NULL;
|
|
}
|
|
if (pds->hwo) {
|
|
HelperWaveClose((ULONG)pds->hwo);
|
|
pds->hwo = NULL;
|
|
}
|
|
vxdDrvClose(pds->hHal);
|
|
pds->hHal = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hr) {
|
|
MemFree(pds);
|
|
pds = NULL;
|
|
}
|
|
|
|
if (DS_OK == hr) *ppds = pds;
|
|
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------;
|
|
//
|
|
// CreateDsEmulated
|
|
//
|
|
// This function creates a DSOUND object (a pds) using an mmsystem wave driver
|
|
// identified by a guid.
|
|
//
|
|
// Parameters:
|
|
// REFGUID rguid : identifies the guid of the requested wave driver
|
|
//
|
|
// LPDSOUND *ppds : receives a pointer to the new DSOUND object
|
|
//
|
|
// Return value (HRESULT):
|
|
// DS_OK : if and only if a DSOUND object has been created successfully
|
|
//
|
|
// DSERR_NODRIVER : if there is no wave driver having the specified guid
|
|
//
|
|
// other : some other failure
|
|
//
|
|
// Notes:
|
|
// For return values other than DS_OK, there should be no side effects
|
|
//
|
|
//--------------------------------------------------------------------------;
|
|
|
|
HRESULT WINAPI CreateDsEmulated(REFGUID rguid, LPDSOUND *ppds)
|
|
{
|
|
LPDSOUND pds;
|
|
LPDSOUND pdsT;
|
|
UINT cWaveDev;
|
|
UINT iWaveDev;
|
|
HRESULT hr;
|
|
int cb;
|
|
|
|
*ppds = NULL;
|
|
pds = NULL;
|
|
|
|
cWaveDev = waveOutGetNumDevs();
|
|
cWaveDev = min(cWaveDev, LIMIT_WAVE_DEVICES-1);
|
|
|
|
hr = DS_OK;
|
|
for (iWaveDev = 0; iWaveDev < cWaveDev; iWaveDev++) {
|
|
if (IsEqualGUID(rguid, &gpdsinfo->aguidWave[iWaveDev])) break;
|
|
}
|
|
if (iWaveDev >= cWaveDev) hr = DSERR_NODRIVER;
|
|
|
|
if (DS_OK == hr) {
|
|
// REMIND here is a temporary block to prevent us from
|
|
// creating a second waveem ds object on a different
|
|
// wave device. This would result in a deadlock. We
|
|
// look for another waveem ds object with a different
|
|
// guid.
|
|
for (pdsT = gpdsinfo->pDSoundObj; pdsT; pdsT = pdsT->pNext) {
|
|
if ( (pdsT->fdwInternal & DS_INTERNALF_WAVEEMULATED) &&
|
|
(!IsEqualGUID(rguid, &pdsT->guid)) )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (pdsT) hr = DSERR_ALLOCATED;
|
|
|
|
if (DS_OK == hr) {
|
|
WAVEOUTCAPS woc;
|
|
MMRESULT mmr;
|
|
|
|
mmr = waveOutGetDevCaps(iWaveDev, &woc, sizeof(woc));
|
|
switch (mmr) {
|
|
case MMSYSERR_NOERROR:
|
|
if (woc.dwSupport & WAVECAPS_SYNC) {
|
|
hr = DSERR_NODRIVER;
|
|
} else {
|
|
hr = DS_OK;
|
|
}
|
|
break;
|
|
case MMSYSERR_NOMEM:
|
|
hr = DSERR_OUTOFMEMORY;
|
|
break;
|
|
case MMSYSERR_NODRIVER:
|
|
hr = DSERR_NODRIVER;
|
|
break;
|
|
default:
|
|
hr = DSERR_GENERIC;
|
|
}
|
|
}
|
|
|
|
if (DS_OK == hr) {
|
|
|
|
pds = (LPDSOUND)MemAlloc(sizeof(*pds));
|
|
if (pds) {
|
|
pds->guid = *rguid;
|
|
|
|
cb = sizeof(DSBWAVEBLTI) + (sizeof(DSBWAVEBLTSRCI)*LIMIT_BLT_SOURCES);
|
|
pds->pdswb = (LPDSBWAVEBLTI)MemAlloc(cb);
|
|
if (pds->pdswb) {
|
|
pds->pdswb->padswbs = (LPDSBWAVEBLTSRCI)(((LPSTR)pds->pdswb) + sizeof(*pds->pdswb));
|
|
pds->pdswb->dwSize = sizeof(DSBWAVEBLT);
|
|
pds->pdswb->dwCount = 0;
|
|
|
|
//
|
|
pds->uDeviceID = iWaveDev;
|
|
pds->pds3d = NULL;
|
|
pds->uRefCount = 1;
|
|
pds->fdwInternal |= DS_INTERNALF_ALLOCATED;
|
|
pds->fdwInternal |= DS_INTERNALF_WAVEEMULATED;
|
|
pds->dwSig = DSOUNDSIG;
|
|
|
|
//
|
|
pds->pNext = gpdsinfo->pDSoundObj;
|
|
gpdsinfo->pDSoundObj = pds;
|
|
} else {
|
|
hr = DSERR_OUTOFMEMORY;
|
|
}
|
|
|
|
if (hr) {
|
|
MemFree(pds);
|
|
pds = NULL;
|
|
}
|
|
|
|
} else {
|
|
hr = DSERR_OUTOFMEMORY;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (DS_OK == hr) *ppds = pds;
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------;
|
|
//
|
|
// DseInitializeFromGuid
|
|
//
|
|
// This function attempts to initialize a DSOUNDEXTERNAL object to use
|
|
// the direct sound driver identified by rguid.
|
|
//
|
|
// Parameters:
|
|
// REFGUID rguid : identifies the guid of the requested driver
|
|
//
|
|
// Return value (HRESULT):
|
|
// DS_OK : if and only if a DSOUND object has been created successfully
|
|
//
|
|
// DSERR_NODRIVER : if there is no driver having the specified guid
|
|
//
|
|
// other : some other failure
|
|
//
|
|
// Notes:
|
|
// For return values other than DS_OK, there should be no side effects
|
|
//
|
|
//--------------------------------------------------------------------------;
|
|
|
|
HRESULT WINAPI DseInitializeFromGuid(LPDSOUNDEXTERNAL pdse, REFGUID rguid)
|
|
{
|
|
LPDSOUND pds;
|
|
DSBUFFERDESC dsbd;
|
|
HANDLE hFocusLock;
|
|
HRESULT hr;
|
|
|
|
DPF(0, "DseInitializeFromGuid()");
|
|
|
|
// First look for an existing ds object with same guid
|
|
hr = DSERR_NODRIVER;
|
|
for (pds = gpdsinfo->pDSoundObj; pds; pds = pds->pNext) {
|
|
if (IsEqualGUID(rguid, &pds->guid)) {
|
|
// We've found one! AddRef it since we're going to use it.
|
|
DsAddRef(pds);
|
|
pdse->pds = pds;
|
|
pdse->pNext = NULL;
|
|
pdse->dwPID = GetCurrentProcessId();
|
|
pdse->pdsbe = NULL;
|
|
pdse->tidSound = TID_INIT_VALUE;
|
|
pdse->dwPriority = DSSCL_NORMAL;
|
|
pdse->dwSpeakerConfig = DSSPEAKER_STEREO;
|
|
pdse->pwfxApp = NULL;
|
|
|
|
if (!GetFocusLock(&hFocusLock)) {
|
|
DPF(2, "DirectSoundCreateFromGuid: note: error getting focus lock");
|
|
}
|
|
pdse->pNext = gpdsinfo->pDSoundExternalObj;
|
|
gpdsinfo->pDSoundExternalObj = pdse;
|
|
if (!ReleaseFocusLock(hFocusLock)) {
|
|
DPF(2, "DirectSoundCreateFromGuid: note: error releasing focus lock");
|
|
}
|
|
return DS_OK;
|
|
}
|
|
}
|
|
|
|
// Try native ds
|
|
if (DSERR_NODRIVER == hr) hr = CreateDsNative(rguid, &pds);
|
|
|
|
// Try emulated ds
|
|
if (DSERR_NODRIVER == hr) hr = CreateDsEmulated(rguid, &pds);
|
|
|
|
// If none of the above worked, return
|
|
if (DS_OK != hr) return hr;
|
|
|
|
// At this point, we have a pds that has been addrefed
|
|
hr = DsInitializeDefaultFormat(pds);
|
|
if (DS_OK != hr) {
|
|
// We don't really care about this failure
|
|
DPF(0, "DirectSoundCreateFromGuid: note: DsInitializeDefaultFormat returned %08Xh", hr);
|
|
hr = DS_OK;
|
|
}
|
|
|
|
//
|
|
pdse->pds = pds;
|
|
pdse->pNext = NULL;
|
|
pdse->dwPID = GetCurrentProcessId();
|
|
pdse->pdsbe = NULL;
|
|
pdse->tidSound = TID_INIT_VALUE;
|
|
pdse->dwPriority = DSSCL_NORMAL;
|
|
pdse->dwSpeakerConfig = DSSPEAKER_STEREO;
|
|
pdse->pwfxApp = NULL;
|
|
|
|
//
|
|
ZeroMemory(&dsbd, sizeof(dsbd));
|
|
dsbd.dwSize = sizeof(dsbd);
|
|
dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER;
|
|
dsbd.dwBufferBytes = 0;
|
|
hr = DseCreateDsbe(pdse, &dsbd, &pds->pdsbePrimary);
|
|
if (DS_OK == hr) {
|
|
pds->pdsbePrimary->dwPriority = DSSCL_WRITEPRIMARY;
|
|
pds->pdsbePrimary->dwPID = DWPRIMARY_INTERNAL_PID;
|
|
pds->pdsbePrimary->pNext = NULL;
|
|
pds->pdsbePrimary->pdse = NULL;
|
|
|
|
pds->pdsbPrimary->dsbe.pdse = NULL;
|
|
pds->pdsbPrimary->plProcess->dwPID = DWPRIMARY_INTERNAL_PID;
|
|
|
|
pdse->pdsbe = NULL;
|
|
}
|
|
|
|
if (DS_OK == hr) {
|
|
//
|
|
if (!GetFocusLock(&hFocusLock)) {
|
|
DPF(2, "DirectSoundCreateFromGuid: note: error getting focus lock");
|
|
}
|
|
pdse->pNext = gpdsinfo->pDSoundExternalObj;
|
|
gpdsinfo->pDSoundExternalObj = pdse;
|
|
if (!ReleaseFocusLock(hFocusLock)) {
|
|
DPF(2, "DirectSoundCreateFromGuid: note: error releasing focus lock");
|
|
}
|
|
}
|
|
|
|
if (DS_OK != hr) {
|
|
DsRelease(pds);
|
|
pds = NULL;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT WINAPI DirectSoundCreate
|
|
(
|
|
LPGUID lpGUID,
|
|
LPDIRECTSOUND *ppIDs,
|
|
IUnknown FAR *pUnkOuter
|
|
)
|
|
{
|
|
LPDIRECTSOUND pIDs;
|
|
HRESULT hr;
|
|
|
|
if( !VALID_DWORD_PTR(ppIDs) ) {
|
|
RPF("DirectSoundCreate: Invalid pointer to DS object pointer");
|
|
return DSERR_INVALIDPARAM;
|
|
}
|
|
*ppIDs = NULL;
|
|
|
|
DPF(3, "DirectSoundCreate pGUID %X",(DWORD)lpGUID );
|
|
if( lpGUID != NULL ) {
|
|
DPF(3, "DirectSoundCreate GUID %X %X %X %X",
|
|
*(((LPDWORD)lpGUID) + 0 ),
|
|
*(((LPDWORD)lpGUID) + 1 ),
|
|
*(((LPDWORD)lpGUID) + 2 ),
|
|
*(((LPDWORD)lpGUID) + 3 ) );
|
|
}
|
|
|
|
hr = CreateNewDirectSoundObject (&pIDs, pUnkOuter);
|
|
if (S_OK != hr) return hr;
|
|
|
|
hr = IDirectSound_Initialize(pIDs, lpGUID);
|
|
if (S_OK == hr) {
|
|
*ppIDs = pIDs;
|
|
} else {
|
|
IDirectSound_Release(pIDs);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
// Enumerate
|
|
//
|
|
// This is a helper function for DirectSoundEnumerate. This function will
|
|
// execute the callback to the client. The caller of this function provides
|
|
// a pointer to a BOOL which this function uses to determine whether to
|
|
// enumerate the "Primary Sound Device".
|
|
//
|
|
// Arguments:
|
|
// LPDSENUMCALLBACK lpCallback:
|
|
// LPVOID lpContext:
|
|
// REFGUID rguid:
|
|
// LPTSTR pszDescription:
|
|
// LPTSTR pszDriver:
|
|
// These are required to execute the callback to the client
|
|
// PBOOL pfDidPrimary:
|
|
// Indicates whether the primary driver has already been enumerated.
|
|
// If it hasn't, this function will enumerate it and set this BOOL
|
|
// to TRUE.
|
|
//
|
|
// Return (BOOL):
|
|
// The return value from the client's callback.
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
BOOL EnumerateA(LPDSENUMCALLBACKA lpCallback, LPVOID lpContext, LPGUID pguid,
|
|
char *pszDescription, char *pszDriver, PBOOL pfDidPrimary)
|
|
{
|
|
BOOL fReturn;
|
|
char szDriver[MAX_PATH];
|
|
char szDescription[MAX_PATH];
|
|
|
|
if (!*pfDidPrimary) {
|
|
*pfDidPrimary = TRUE;
|
|
|
|
szDescription[0] = '\0';
|
|
szDriver[0] = '\0';
|
|
|
|
if( !LoadStringA( hModule, IDS_PRIMARYDRIVER, szDescription, sizeof(szDescription) ) ||
|
|
!LoadStringA( hModule, IDS_SOUND, szDriver, sizeof(szDriver) ) ) {
|
|
DPF(0,"EnumerateA : error: LoadString failed.");
|
|
}
|
|
|
|
DPF(3,"EnumerateA : note: enumerating primary driver: Desc=\"%s\", Filename=\"%s\".",szDescription,szDriver);
|
|
fReturn = lpCallback( NULL, szDescription, szDriver, lpContext );
|
|
if (!fReturn) return fReturn;
|
|
}
|
|
|
|
fReturn = lpCallback(pguid, pszDescription, pszDriver, lpContext);
|
|
|
|
return fReturn;
|
|
}
|
|
|
|
BOOL EnumerateW(LPDSENUMCALLBACKW lpCallback, LPVOID lpContext, LPGUID pguid,
|
|
WCHAR *pszDescription, WCHAR *pszDriver, PBOOL pfDidPrimary)
|
|
{
|
|
BOOL fReturn;
|
|
WCHAR szDriver[MAX_PATH];
|
|
WCHAR szDescription[MAX_PATH];
|
|
|
|
if (!*pfDidPrimary) {
|
|
*pfDidPrimary = TRUE;
|
|
|
|
szDescription[0] = '\0';
|
|
szDriver[0] = '\0';
|
|
|
|
if( !LoadStringW( hModule, IDS_PRIMARYDRIVER, szDescription, SIZEOF(szDescription) ) ||
|
|
!LoadStringW( hModule, IDS_SOUND, szDriver, SIZEOF(szDriver) ) ) {
|
|
DPF(0,"EnumerateW : error: LoadString failed.");
|
|
}
|
|
|
|
DPF(3,"EnumerateW : note: enumerating primary driver: Desc=\"%ls\", Filename=\"%ls\".",szDescription,szDriver);
|
|
fReturn = lpCallback( NULL, szDescription, szDriver, lpContext );
|
|
if (!fReturn) return fReturn;
|
|
}
|
|
|
|
fReturn = lpCallback(pguid, pszDescription, pszDriver, lpContext);
|
|
|
|
return fReturn;
|
|
}
|
|
|
|
/*
|
|
* DirectSoundEnumerateA
|
|
*/
|
|
HRESULT WINAPI DirectSoundEnumerateA(
|
|
LPDSENUMCALLBACKA lpCallback,
|
|
LPVOID lpContext )
|
|
{
|
|
DWORD rc;
|
|
DSDRIVERDESC dsDrvDesc;
|
|
GUID guid;
|
|
DSVAL dsv;
|
|
DWORD dw;
|
|
DWORD dwNumDev;
|
|
BOOL fDidPrimary;
|
|
char szDriver[MAX_PATH];
|
|
char szDescription[MAX_PATH];
|
|
|
|
struct _wave_em_info {
|
|
DWORD dnDevNode;
|
|
UINT ulDeviceNum;
|
|
UINT nVxdIds;
|
|
BOOL bEnumerated;
|
|
WORD awVxdIds[50];
|
|
} * pWaveEm = NULL;
|
|
|
|
if( !VALID_CODE_PTR( lpCallback ) )
|
|
{
|
|
RPF("DirectSoundEnumerate: Invalid callback routine" );
|
|
return DSERR_INVALIDPARAM;
|
|
}
|
|
|
|
fDidPrimary = FALSE;
|
|
|
|
// gather up devnode & vxd id information from mmsystem
|
|
// and build a table devices in pWaveEm. we will check off
|
|
// devices from this table as we enumerate so that we wont
|
|
// end up enumerating the same device twice
|
|
//
|
|
DPF(3, "enum: getting devnodes for wave devices");
|
|
dwNumDev = waveOutGetNumDevs();
|
|
dwNumDev = min(dwNumDev, LIMIT_WAVE_DEVICES-1);
|
|
pWaveEm = NULL;
|
|
if (dwNumDev) {
|
|
// if we fail to allocate memory, go ahead anyway.
|
|
// we will just end up enumerating some devices more than
|
|
// once in this case.
|
|
//
|
|
pWaveEm = MemAlloc (sizeof(*pWaveEm) * dwNumDev);
|
|
if (pWaveEm) {
|
|
for (dw = 0; dw < dwNumDev; ++dw) {
|
|
DWORD ii;
|
|
|
|
if (waveOutMessage ((HWAVEOUT)dw, DRV_QUERYDEVNODE,
|
|
(DWORD)&pWaveEm[dw].dnDevNode, 0))
|
|
{
|
|
pWaveEm[dw].dnDevNode = 0;
|
|
}
|
|
|
|
pWaveEm[dw].nVxdIds = NUMELMS(pWaveEm[dw].awVxdIds);
|
|
if (waveOutMessage ((HWAVEOUT)dw, DRV_QUERYDRIVERIDS,
|
|
(DWORD)pWaveEm[dw].awVxdIds,
|
|
(DWORD)&pWaveEm[dw].nVxdIds))
|
|
{
|
|
pWaveEm[dw].nVxdIds = 0;
|
|
}
|
|
|
|
pWaveEm[dw].ulDeviceNum = 0;
|
|
for (ii = 0; ii < dw; ++ii)
|
|
{
|
|
if (pWaveEm[ii].dnDevNode == pWaveEm[dw].dnDevNode)
|
|
{
|
|
++pWaveEm[dw].ulDeviceNum;
|
|
}
|
|
}
|
|
|
|
DPF(3, "Wave[%d,%d] dn=%08lX awVxd=%d:{%x,%x,%x,%x}", dw,
|
|
pWaveEm[dw].ulDeviceNum,
|
|
pWaveEm[dw].dnDevNode,
|
|
pWaveEm[dw].nVxdIds,
|
|
pWaveEm[dw].awVxdIds[0],
|
|
pWaveEm[dw].awVxdIds[1],
|
|
pWaveEm[dw].awVxdIds[2],
|
|
pWaveEm[dw].awVxdIds[3]
|
|
);
|
|
|
|
pWaveEm[dw].bEnumerated = FALSE;
|
|
}
|
|
|
|
} else {
|
|
DPF(0, "Direct Sound: Failed to allocate pWaveEm, may enumerate some devices more than once!");
|
|
}
|
|
}
|
|
|
|
//
|
|
// REMIND handle error conditions better, also enumerate emulated devices
|
|
//
|
|
#if defined(RDEBUG) || defined(DEBUG)
|
|
if (!(gpdsinfo->fEnumOnlyWaveDevs))
|
|
#endif
|
|
{
|
|
ZeroMemory(&dsDrvDesc, sizeof(dsDrvDesc));
|
|
dsv = vxdDrvGetNextDriverDesc(NULL, &guid, &dsDrvDesc);
|
|
while (DS_OK == dsv) {
|
|
GUID guidLast;
|
|
|
|
DPF(3, "DirectSoundEnum GUID %X %X %X %X",
|
|
*(((LPDWORD)&guid) + 0 ),
|
|
*(((LPDWORD)&guid) + 1 ),
|
|
*(((LPDWORD)&guid) + 2 ),
|
|
*(((LPDWORD)&guid) + 3 ) );
|
|
|
|
// if there is a wave device corresponding to this device,
|
|
// check it off as having been enumerated already.
|
|
//
|
|
if (pWaveEm) {
|
|
for (dw = 0; dw < dwNumDev; ++dw) {
|
|
DPF(3, "\tdn=%08lX num=%d vxd=%X",
|
|
dsDrvDesc.dnDevNode, dsDrvDesc.ulDeviceNum, dsDrvDesc.wVxdId);
|
|
if ((pWaveEm[dw].dnDevNode == dsDrvDesc.dnDevNode) &&
|
|
(pWaveEm[dw].ulDeviceNum == dsDrvDesc.ulDeviceNum)) {
|
|
UINT ii;
|
|
for (ii = 0; ii < pWaveEm[dw].nVxdIds; ++ii) {
|
|
if ((pWaveEm[dw].awVxdIds[ii] == dsDrvDesc.wVxdId)) {
|
|
pWaveEm[dw].bEnumerated = TRUE;
|
|
DPF(3, "Enum: marking wave as already enum'd");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DPF(3,"DsEnum - enumerating driver: Desc=\"%s\", Filename=\"%s\".",dsDrvDesc.szDesc,dsDrvDesc.szDrvname);
|
|
rc = EnumerateA(lpCallback, lpContext, &guid, dsDrvDesc.szDesc, dsDrvDesc.szDrvname, &fDidPrimary);
|
|
if ( !rc ) goto bailout;
|
|
guidLast = guid;
|
|
ZeroMemory(&dsDrvDesc, sizeof(dsDrvDesc));
|
|
dsv = vxdDrvGetNextDriverDesc(&guidLast, &guid, &dsDrvDesc);
|
|
}
|
|
}
|
|
|
|
// loop though the wave devices, enumererating as emulated,
|
|
// those devices that have not already been enumerated as native.
|
|
//
|
|
for (dw = 0; dw < dwNumDev; ++dw) {
|
|
struct {
|
|
WAVEOUTCAPSA woc;
|
|
char szFill[MAX_PATH]; // gurantee room for appended string
|
|
} uu;
|
|
|
|
guid = gpdsinfo->aguidWave[dw];
|
|
|
|
//#define ENUM_DSDEVS_AS_WAVE_ALSO
|
|
|
|
// if we already enumerated this device as non-emulated
|
|
// skip it here.
|
|
//
|
|
if (pWaveEm && pWaveEm[dw].bEnumerated) {
|
|
if (!(gpdsinfo->fDupEnumWaveDevs)) {
|
|
DPF(3, "Direct Sound Enum Wave%d already enumerated", dw);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
DPF(3, "DirectSoundEnum Wave%d GUID %X %X %X %X", dw,
|
|
*(((LPDWORD)&guid) + 0 ),
|
|
*(((LPDWORD)&guid) + 1 ),
|
|
*(((LPDWORD)&guid) + 2 ),
|
|
*(((LPDWORD)&guid) + 3 ) );
|
|
|
|
waveOutGetDevCapsA (dw, &uu.woc, sizeof(uu.woc));
|
|
|
|
szDescription[0] = '\0';
|
|
if( !LoadStringA( hModule, IDS_EMULATED, szDescription, sizeof(szDescription) ) ) {
|
|
DPF(0,"DirectSoundEnumerateA - LoadString2 failed.");
|
|
}
|
|
lstrcatA (uu.woc.szPname, szDescription);
|
|
|
|
|
|
szDescription[0] = TEXT('\0');
|
|
szDriver[0] = TEXT('\0');
|
|
if( !LoadStringA( hModule, IDS_DRIVERLD, szDescription, sizeof(szDescription) ) ) {
|
|
DPF(0,"DirectSoundEnumerateA - LoadString3 failed.");
|
|
} else {
|
|
wsprintfA (szDriver, szDescription, dw);
|
|
}
|
|
|
|
if (0 == (uu.woc.dwSupport & WAVECAPS_SYNC)) {
|
|
DPF(3,"DsEnum - enumerating driver: Desc=\"%s\", Filename=\"%s\".",uu.woc.szPname,szDriver);
|
|
rc = EnumerateA(lpCallback, lpContext, &guid, uu.woc.szPname, szDriver, &fDidPrimary);
|
|
if ( !rc ) goto bailout;
|
|
} else {
|
|
DPF(2, "DsEnum: note: driver \"%s\" reported WAVECAPS_SYNC, not emumerating", uu.woc.szPname);
|
|
}
|
|
}
|
|
|
|
bailout:
|
|
if (pWaveEm) MemFree (pWaveEm);
|
|
return DS_OK;
|
|
|
|
} /* DirectSoundEnumerateA */
|
|
|
|
HRESULT WINAPI DirectSoundEnumerateW(
|
|
LPDSENUMCALLBACKW lpCallback,
|
|
LPVOID lpContext )
|
|
{
|
|
#ifndef WINNT
|
|
if( !VALID_CODE_PTR( lpCallback ) )
|
|
{
|
|
RPF("DirectSoundEnumerate: Invalid callback routine" );
|
|
return DSERR_INVALIDPARAM;
|
|
}
|
|
|
|
return DSERR_UNSUPPORTED;
|
|
|
|
#else
|
|
|
|
#ifndef DSBLD_EMULONLY
|
|
// this is implemented for only emulation mode
|
|
#error
|
|
#endif
|
|
|
|
DWORD rc;
|
|
GUID guid;
|
|
DWORD dw;
|
|
DWORD dwNumDev;
|
|
BOOL fDidPrimary;
|
|
WCHAR szDriver[MAX_PATH];
|
|
WCHAR szDescription[MAX_PATH];
|
|
|
|
if( !VALID_CODE_PTR( lpCallback ) )
|
|
{
|
|
RPF("DirectSoundEnumerate: Invalid callback routine" );
|
|
return DSERR_INVALIDPARAM;
|
|
}
|
|
|
|
fDidPrimary = FALSE;
|
|
|
|
// loop though the wave devices, enumererating as emulated,
|
|
// those devices that have not already been enumerated as native.
|
|
|
|
dwNumDev = waveOutGetNumDevs();
|
|
for (dw = 0; dw < dwNumDev; ++dw) {
|
|
struct {
|
|
WAVEOUTCAPSW woc;
|
|
WCHAR szFill[MAX_PATH]; // gurantee room for appended string
|
|
} uu;
|
|
|
|
guid = gpdsinfo->aguidWave[dw];
|
|
|
|
DPF(3, "DirectSoundEnum Wave%d GUID %X %X %X %X", dw,
|
|
*(((LPDWORD)&guid) + 0 ),
|
|
*(((LPDWORD)&guid) + 1 ),
|
|
*(((LPDWORD)&guid) + 2 ),
|
|
*(((LPDWORD)&guid) + 3 ) );
|
|
|
|
waveOutGetDevCapsW (dw, &uu.woc, sizeof(uu.woc));
|
|
|
|
szDescription[0] = L'\0';
|
|
if( !LoadStringW( hModule, IDS_EMULATED, szDescription, SIZEOF(szDescription) ) ) {
|
|
DPF(0,"DirectSoundEnumerateW - LoadString2 failed.");
|
|
}
|
|
lstrcatW (uu.woc.szPname, szDescription);
|
|
|
|
|
|
szDescription[0] = L'\0';
|
|
szDriver[0] = L'\0';
|
|
if( !LoadStringW( hModule, IDS_DRIVERLD, szDescription, SIZEOF(szDescription) ) ) {
|
|
DPF(0,"DirectSoundEnumerateW - LoadString3 failed.");
|
|
} else {
|
|
wsprintfW (szDriver, szDescription, dw);
|
|
}
|
|
|
|
|
|
if (0 == (uu.woc.dwSupport & WAVECAPS_SYNC)) {
|
|
DPF(3,"DsEnum - enumerating driver: Desc=\"%ls\", Filename=\"%ls\".",uu.woc.szPname,szDriver);
|
|
rc = EnumerateW(lpCallback, lpContext, &guid, uu.woc.szPname, szDriver, &fDidPrimary);
|
|
if ( !rc ) goto bailout;
|
|
} else {
|
|
DPF(2, "DsEnum: note: driver \"%ls\" reported WAVECAPS_SYNC, not emumerating", uu.woc.szPname);
|
|
}
|
|
}
|
|
|
|
bailout:
|
|
return DS_OK;
|
|
#endif
|
|
} /* DirectSoundEnumerateW */
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// DSDetermineDMASize
|
|
//
|
|
// Determine DMA buffer size on the given emulated direct sound device and
|
|
// munge it to figure out the desired size for the emulator to allocate
|
|
// per wave header.
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
#define TIMEOUT_PERIOD 5000
|
|
|
|
MMRESULT DSDetermineDMASize
|
|
(
|
|
LPDSOUND pds,
|
|
LPWAVEFORMATEX pwfx
|
|
)
|
|
{
|
|
UINT idx;
|
|
WAVEHDR whdr;
|
|
UINT mmResult;
|
|
BYTE aWaveData[4];
|
|
DWORD dwTotalTime;
|
|
DWORD dwBeginTime;
|
|
DWORD ulDmaSizeBytes;
|
|
|
|
DPF(3, "DSDetermineDMASize");
|
|
|
|
// we'll send a packet of 4 bytes
|
|
// (that's at least 1 sample in every format)
|
|
|
|
for (idx = 0; idx < sizeof(aWaveData); ++idx)
|
|
aWaveData[idx] = (pwfx->wBitsPerSample == 16) ? 0 : 0x80;
|
|
|
|
// prepare header
|
|
//
|
|
whdr.lpData = (LPBYTE)aWaveData;
|
|
whdr.dwBufferLength = sizeof(aWaveData);
|
|
whdr.dwFlags = 0;
|
|
whdr.dwLoops = 0;
|
|
whdr.dwUser = 0;
|
|
mmResult = waveOutPrepareHeader (pds->hwo, &whdr, sizeof(whdr));
|
|
if (mmResult)
|
|
return mmResult;
|
|
|
|
dwTotalTime = 0;
|
|
dwBeginTime = timeGetTime();
|
|
|
|
// play our buffer
|
|
mmResult = waveOutWrite(pds->hwo, &whdr, sizeof(whdr));
|
|
if (!mmResult)
|
|
{
|
|
// spin until the done bit is set, or 5 seconds
|
|
while (!(whdr.dwFlags & WHDR_DONE))
|
|
{
|
|
if (dwTotalTime >= TIMEOUT_PERIOD)
|
|
{
|
|
DPF (0, "TIMEOUT getting dma buffer size");
|
|
mmResult = MMSYSERR_ERROR;
|
|
//
|
|
// Without the reset we were freeing the header before the
|
|
// driver could process it, thus causing faults in the driver.
|
|
//
|
|
waveOutReset(pds->hwo);
|
|
break;
|
|
}
|
|
|
|
// This thread is THREAD_PRIORITY_TIME_CRITICAL so it would be
|
|
// very dangerous to busy wait without explicitly giving up the
|
|
// CPU for a while.
|
|
Sleep(10);
|
|
|
|
dwTotalTime = timeGetTime() - dwBeginTime;
|
|
}
|
|
} else {
|
|
DPF(0, "waveOutWrite (determine DMA size) returned %u", mmResult);
|
|
}
|
|
|
|
waveOutUnprepareHeader(pds->hwo, &whdr, sizeof(whdr));
|
|
|
|
if (!mmResult)
|
|
{
|
|
// if it's smaller than 62ms, it probably isn't
|
|
// a dma based card.
|
|
dwTotalTime = max(dwTotalTime, 62);
|
|
|
|
DPF(3, "DSDetermineDMASize dwTotalTime %lu", dwTotalTime);
|
|
|
|
ulDmaSizeBytes = dwTotalTime * pwfx->nSamplesPerSec;
|
|
ulDmaSizeBytes *= pwfx->nBlockAlign;
|
|
ulDmaSizeBytes /= 1000;
|
|
|
|
DPF(3, "ulDmaSizeBytes %lu", (DWORD)ulDmaSizeBytes);
|
|
|
|
// add in 10% for slop
|
|
// and to account for drivers that deal with dma wrapping
|
|
ulDmaSizeBytes += (ulDmaSizeBytes * 10) / 100;
|
|
// make sure it's a mod 4 (no samples spanning buffers);
|
|
|
|
ulDmaSizeBytes &= 0xfffffffc;
|
|
|
|
pds->cbDMASize = ulDmaSizeBytes;
|
|
|
|
}
|
|
else
|
|
{
|
|
DPF(3, "DSDetermineDMASize: waveOutWRite returned %u",
|
|
(UINT)mmResult);
|
|
|
|
}
|
|
|
|
DPF(3, "DSDetermineDMASize done");
|
|
return mmResult;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// DSGetCurrentSample
|
|
//
|
|
// Basically sample-accurate waveOutGetPosition
|
|
//
|
|
// If we're sample accurate, just call waveOutGetPosition
|
|
//
|
|
// Otherwise, call QueryPerformanceCounter and calculate what sample
|
|
// should be playing.
|
|
//
|
|
// NOTE: What happens if the wave device was ever starved? We will
|
|
// be out of sync if that happens
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
DWORD FNGLOBAL DSGetCurrentSample
|
|
(
|
|
LPDSOUND pds
|
|
)
|
|
{
|
|
LARGE_INTEGER qwTicks;
|
|
|
|
if (pds->fdwInternal & DS_INTERNALF_SAMPLEACCURATE)
|
|
{
|
|
MMTIME mmtTime;
|
|
mmtTime.wType = TIME_SAMPLES;
|
|
waveOutGetPosition(pds->hwo, &mmtTime, sizeof(mmtTime));
|
|
|
|
return mmtTime.u.sample;
|
|
|
|
} // implied "ELSE"
|
|
|
|
// have we re-set the device?
|
|
if (pds->qwTicks.LowPart == 0)
|
|
{
|
|
// yep
|
|
return 0;
|
|
}
|
|
|
|
QueryPerformanceCounter (&qwTicks);
|
|
|
|
// assert(qwTicks.LowPart - pMixData->qwTicks.LowPart);
|
|
// assert(pMixData->ulTickRate);
|
|
// this deals with the wrap case cuz everyting is 32 bits
|
|
//
|
|
return UMulDivRDClip(qwTicks.LowPart - pds->qwTicks.LowPart,
|
|
pds->pdsbPrimary->pwfx->nSamplesPerSec,
|
|
pds->ulTickRate);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// DSSetCurrentSample
|
|
//
|
|
// Find a synchronization point between waveOutGetPosition and
|
|
// QueryPerformanceCounter
|
|
//
|
|
// If we have a sample accurate device, don't bother - we'll just
|
|
// trust the driver
|
|
//
|
|
// Otherwise, wait for the instant waveOutGetPosition changes
|
|
// and save off the QueryPerformanceCounter at that time. Then
|
|
// calculate the QPC time when sample 0 played. This is the
|
|
// timebase we save.
|
|
//
|
|
// !!! Failure cases !!!
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
BOOL DSSetCurrentSample
|
|
(
|
|
LPDSOUND pds
|
|
)
|
|
{
|
|
LARGE_INTEGER qwFreq;
|
|
MMTIME mmtTime;
|
|
DWORD ulCardTime;
|
|
DWORD tmTimeout;
|
|
MMRESULT mmr;
|
|
|
|
DPF(3, "DSSetCurrentSample");
|
|
|
|
QueryPerformanceFrequency(&qwFreq);
|
|
|
|
mmtTime.wType = TIME_SAMPLES;
|
|
mmr = waveOutGetPosition(pds->hwo, &mmtTime, sizeof(mmtTime));
|
|
|
|
if ((!mmr) && mmtTime.wType != TIME_SAMPLES)
|
|
{
|
|
DPF(0, "This driver sucks! (Doesn't support TIME_SAMPLES)");
|
|
mmtTime.wType = TIME_MS;
|
|
mmr = waveOutGetPosition(pds->hwo, &mmtTime, sizeof(mmtTime));
|
|
if ((!mmr) && mmtTime.wType != TIME_MS)
|
|
{
|
|
DPF(0, "This driver REALLY sucks! (Doesn't support TIME_MS either)");
|
|
mmtTime.wType = TIME_BYTES;
|
|
mmr = waveOutGetPosition(pds->hwo, &mmtTime, sizeof(mmtTime));
|
|
if ((!mmr) && mmtTime.wType != TIME_BYTES)
|
|
{
|
|
DPF(0, "Last chance failed! Card does not even support TIME_BYTES!");
|
|
DPF(0, "Bob, I'm not fixing any card that exhibits this behavior!!!");
|
|
DPF(0, "Given time format was %u", (UINT)mmtTime.wType);
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
tmTimeout = timeGetTime();
|
|
ulCardTime = (mmtTime.wType == TIME_MS ?
|
|
mmtTime.u.ms :
|
|
(mmtTime.wType == TIME_SAMPLES ? mmtTime.u.sample :
|
|
mmtTime.u.cb));
|
|
do
|
|
{
|
|
mmr = waveOutGetPosition (pds->hwo, &mmtTime, sizeof(mmtTime));
|
|
if (mmr)
|
|
{
|
|
DPF(0, "waveOutGetPosition returned %u. BAD driver!!!w", mmr);
|
|
return FALSE;
|
|
}
|
|
|
|
if (timeGetTime() - tmTimeout >= TIMEOUT_PERIOD*2)
|
|
{
|
|
DPF(0, "Timeout in DSSetCurrentSample!");
|
|
return FALSE;
|
|
}
|
|
} while (ulCardTime == (mmtTime.wType == TIME_MS ?
|
|
mmtTime.u.ms :
|
|
(mmtTime.wType == TIME_SAMPLES ? mmtTime.u.sample :
|
|
mmtTime.u.cb)));
|
|
|
|
|
|
ASSERT(pds->pdsbPrimary->pwfx->wFormatTag == WAVE_FORMAT_PCM);
|
|
|
|
if (mmtTime.wType == TIME_BYTES)
|
|
{
|
|
mmtTime.wType = TIME_SAMPLES;
|
|
mmtTime.u.sample = mmtTime.u.cb / pds->pdsbPrimary->pwfx->nBlockAlign;
|
|
}
|
|
|
|
DPF(3, "DSSetCurrentSample: End profile");
|
|
|
|
QueryPerformanceCounter(&pds->qwTicks);
|
|
|
|
if (qwFreq.HighPart != 0)
|
|
{
|
|
DPF(0, "Clock > 4Ghz in SetCurrentSample ???");
|
|
DPF(0, "Please send this machine to [email protected]");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
pds->ulTickRate = qwFreq.LowPart;
|
|
|
|
// assert( pds->ulTickRate );
|
|
// assert( mmtTime.u.sample );
|
|
|
|
if (mmtTime.wType == TIME_SAMPLES)
|
|
{
|
|
pds->qwTicks.LowPart -= UMulDivRDClip(mmtTime.u.sample,
|
|
pds->ulTickRate,
|
|
pds->pdsbPrimary->pwfx->nSamplesPerSec);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(mmtTime.wType == TIME_MS);
|
|
|
|
pds->qwTicks.LowPart -= UMulDivRDClip(mmtTime.u.ms,
|
|
pds->ulTickRate,
|
|
1000);
|
|
}
|
|
|
|
DPF(3, "DSSetCurrentSample done");
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// waveUnprepareLoopingBuffers
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
MMRESULT waveUnprepareLoopingBuffers(LPDSOUND pds)
|
|
{
|
|
int i;
|
|
MMRESULT mmr;
|
|
|
|
for (i=0; i<NUMELMS(pds->aWaveHeader); i++) {
|
|
mmr = waveOutUnprepareHeader(pds->hwo, &pds->aWaveHeader[i], sizeof(pds->aWaveHeader[i]));
|
|
ASSERT(!mmr);
|
|
}
|
|
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// waveAllocLoopingBuffers
|
|
//
|
|
// Allocate the array of wavehdr's to point at the primary buffer.
|
|
// Prepare them and start them looping.
|
|
//
|
|
// !!! Add error checking to the waveOut calls !!!
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
MMRESULT waveAllocAndPrepareLoopingBuffers
|
|
(
|
|
LPDSOUND pds
|
|
)
|
|
{
|
|
int iawh;
|
|
MMRESULT mmr;
|
|
WAVEOUTCAPS woc;
|
|
|
|
DPF(3, "waveAllocLoopingBuffers");
|
|
|
|
pds->pLoopingBuffer = MemAlloc(N_EMU_WAVE_HDRS * pds->cbDMASize);
|
|
if (!pds->pLoopingBuffer) {
|
|
RPF("No memory for looping buffer!");
|
|
return MMSYSERR_NOMEM;
|
|
}
|
|
|
|
// Initialize to 8 or 16-bit silence
|
|
FillMemory(pds->pLoopingBuffer, N_EMU_WAVE_HDRS * pds->cbDMASize,
|
|
(BYTE)((pds->pdsbPrimary->pwfx->wBitsPerSample == 8) ? 0x80 : 0x00));
|
|
|
|
// build buffers and headers
|
|
for (iawh = 0; iawh < NUMELMS(pds->aWaveHeader); ++iawh)
|
|
{
|
|
pds->aWaveHeader[iawh].lpData =
|
|
(LPBYTE)(pds->pLoopingBuffer + (iawh * (pds->cbDMASize)));
|
|
pds->aWaveHeader[iawh].dwBufferLength = pds->cbDMASize;
|
|
pds->aWaveHeader[iawh].dwUser = iawh;
|
|
pds->aWaveHeader[iawh].dwFlags = 0;
|
|
}
|
|
|
|
pds->iawhPlaying = 0;
|
|
|
|
DPF(5, "waveAllocLoopingBuffers: note: first waveOutPrepare and waveOutWrite");
|
|
mmr = waveOutPrepareHeader(pds->hwo, &pds->aWaveHeader[0], sizeof(pds->aWaveHeader[0]));
|
|
ASSERT(!mmr);
|
|
mmr = waveOutWrite(pds->hwo, &pds->aWaveHeader[0], sizeof(pds->aWaveHeader[0]));
|
|
if (!mmr) {
|
|
mmr = waveOutPause(pds->hwo);
|
|
ASSERT(!mmr);
|
|
|
|
// send down the rest
|
|
for (iawh = 1; iawh < NUMELMS(pds->aWaveHeader); ++iawh)
|
|
{
|
|
mmr = waveOutPrepareHeader(pds->hwo, &pds->aWaveHeader[iawh],
|
|
sizeof(pds->aWaveHeader[iawh]));
|
|
if (mmr) break;
|
|
mmr = waveOutWrite (pds->hwo, &pds->aWaveHeader[iawh],
|
|
sizeof(pds->aWaveHeader[iawh]));
|
|
if (mmr) break;
|
|
}
|
|
|
|
// BUGBUG We should have better error recovery here to reset the wave
|
|
// device and unprepare any headers that were successfully prepared
|
|
if (!mmr) {
|
|
DPF(5, "waveOutRestart");
|
|
// start the device
|
|
mmr = waveOutRestart (pds->hwo);
|
|
ASSERT(!mmr);
|
|
|
|
DPF(5, "waveOutGetDevCaps");
|
|
|
|
// Determine if we're sample accurate
|
|
//
|
|
mmr = waveOutGetDevCaps((UINT)pds->hwo, &woc, sizeof(woc));
|
|
pds->fdwInternal &= ~DS_INTERNALF_SAMPLEACCURATE;
|
|
|
|
if (MMSYSERR_NOERROR == mmr && (woc.dwSupport & WAVECAPS_SAMPLEACCURATE)) {
|
|
DPF(1, "Emulate: Running on sample accurate driver");
|
|
pds->fdwInternal |= DS_INTERNALF_SAMPLEACCURATE;
|
|
} else {
|
|
mmr = MMSYSERR_NOERROR;
|
|
DPF(1, "Emulate: Driver is NOT sample accurate!!!");
|
|
// Set up our own timing
|
|
if (!DSSetCurrentSample(pds))
|
|
mmr = MMSYSERR_ERROR;
|
|
}
|
|
|
|
pds->pdsbPrimary->pDSBuffer = pds->pLoopingBuffer;
|
|
pds->pdsbPrimary->cbBufferSize = N_EMU_WAVE_HDRS * pds->cbDMASize;
|
|
}
|
|
}
|
|
|
|
if (mmr) MemFree(pds->pLoopingBuffer);
|
|
|
|
DPF(3, "DSSetupLoopingBuffers done, mmr=%08lX", mmr);
|
|
return mmr;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// waveThreadCallback
|
|
//
|
|
// This is a waveOutProc callback function. Its sole purpose is to
|
|
// increment a count of done headers and signal and event to waveThreadLoop
|
|
// that another header is done.
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
VOID CALLBACK _loadds waveThreadCallback
|
|
(
|
|
HWAVE hwo,
|
|
UINT uMsg,
|
|
DWORD dwUser,
|
|
DWORD dwParam1,
|
|
DWORD dwParam2
|
|
)
|
|
{
|
|
LPDSOUND pds = (LPDSOUND)dwUser; // get our context
|
|
|
|
if ((MM_WOM_DONE == uMsg) && (pds->hEventWaveHeaderDone)) {
|
|
InterlockedIncrement(&pds->cwhDone);
|
|
SetEvent(pds->hEventWaveHeaderDone);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// waveThreadLoop
|
|
//
|
|
// This function is responsible for continuously writing our wave headers
|
|
// to the wave device. It also calls the MixThreadCallback routine to
|
|
// mix more data into the wave headers.
|
|
//
|
|
// This function waits for a WaveHeaderDone event signalled by
|
|
// waveThreadCallback, which is a waveOutProc callback function. Upon
|
|
// receving the signal this function will write all done headers back to
|
|
// the wave device. Normally one header will be done on each signal. But
|
|
// there may be more in cases where more than one header finishex before this
|
|
// thread is scheduled.
|
|
//
|
|
// Once all done headers are rewritten to the wave device, the header
|
|
// following the last one written is considered to be the one currently
|
|
// playing. This header is called the "committed" header and an index to
|
|
// it is saved in pds->iawhPlaying.
|
|
//
|
|
// The count of done headers is maintained using the Interlocked APIs. The
|
|
// waveThreadCallback function will increment the count and this function will
|
|
// decrement it.
|
|
//
|
|
// This function will also react to a terminate event. This event is
|
|
// signalled during release of the DirectSound object. This loop will
|
|
// terminate and return to the waveThread function which will clean up
|
|
// and terminate.
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
void waveThreadLoop(LPDSOUND pds, HANDLE hEventTerminate)
|
|
{
|
|
MMRESULT mmr;
|
|
|
|
while(TRUE) {
|
|
int cIterations;
|
|
BOOL fPaused;
|
|
DWORD dwResult;
|
|
LONG l;
|
|
LPWAVEHDR pwh;
|
|
HANDLE ah[2] = { hEventTerminate,
|
|
pds->hEventWaveHeaderDone };
|
|
|
|
// The first wait is for either a terminate or headerdone event.
|
|
// The second wait is for either a terminate or the DLL mutex.
|
|
dwResult = WaitForMultipleObjectsEx(2, ah, FALSE, INFINITE, FALSE);
|
|
if (WAIT_OBJECT_0 == dwResult) break;
|
|
ASSERT((WAIT_OBJECT_0 + 1) == dwResult);
|
|
|
|
cIterations = 0;
|
|
fPaused = FALSE;
|
|
l = InterlockedDecrement(&pds->cwhDone);
|
|
while (l >= 0) {
|
|
|
|
dwResult = ENTER_DLL_CSECT_OR_EVENT(hEventTerminate);
|
|
if (WAIT_OBJECT_0 == dwResult) break;
|
|
|
|
pwh = &pds->aWaveHeader[pds->iawhPlaying];
|
|
pds->iawhPlaying = (++pds->iawhPlaying) % NUMELMS(pds->aWaveHeader);
|
|
MixThreadCallback(pds);
|
|
|
|
LEAVE_DLL_CSECT();
|
|
|
|
// If it looks like we're spending all our time mixing, then
|
|
// let's pause the wave device until we catch up. It is very
|
|
// important that we eventually catch up. If we are always
|
|
// behind then this high priority thread might starve every
|
|
// other thread in the system.
|
|
cIterations++;
|
|
if (cIterations > NUMELMS(pds->aWaveHeader)) {
|
|
mmr = waveOutPause(pds->hwo);
|
|
ASSERT(!mmr);
|
|
fPaused = TRUE;
|
|
cIterations = 0;
|
|
}
|
|
|
|
mmr = waveOutWrite(pds->hwo, pwh, sizeof(*pwh));
|
|
ASSERT(!mmr);
|
|
l = InterlockedDecrement(&pds->cwhDone);
|
|
}
|
|
InterlockedIncrement(&pds->cwhDone);
|
|
if (fPaused) {
|
|
mmr = waveOutRestart(pds->hwo);
|
|
ASSERT(!mmr);
|
|
}
|
|
|
|
if (WAIT_OBJECT_0 == dwResult) break;
|
|
}
|
|
DPF(0, "waveThreadLoop: note: exiting");
|
|
return;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// waveThread
|
|
//
|
|
// This thread proc initializes the wave device for ds emulation and then
|
|
// calls waveThreadLoop. See the waveThreadLoop comment header. Upon
|
|
// return from waveThreadLoop, this function will clean up and terminate.
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
DWORD __stdcall waveThread
|
|
(
|
|
PVOID pThreadParms
|
|
)
|
|
{
|
|
LPDSOUND pds = (LPDSOUND)pThreadParms;
|
|
HANDLE hEventInitDone;
|
|
HANDLE hEventTerminate;
|
|
DWORD dwPriority;
|
|
DWORD dwVolume;
|
|
MMRESULT mmrInit;
|
|
MMRESULT mmr;
|
|
|
|
//
|
|
// mmrInit - holds the result code to be passed back to the creator
|
|
// via pds->mmrWaveThreadInit.
|
|
//
|
|
// mmr - a temp result code
|
|
//
|
|
|
|
DPF(0, "waveThread startup for pds=%08lX", pds);
|
|
|
|
ASSERT(NULL == pds->hwo);
|
|
|
|
hEventInitDone = CreateEvent(NULL, FALSE, FALSE, pds->szEventWaveThreadInitDone);
|
|
if (!hEventInitDone) {
|
|
DPF(0, "waveThread: error: couldn't create hEventInitDone");
|
|
return 0;
|
|
}
|
|
|
|
dwPriority = GetPriorityClass(GetCurrentProcess());
|
|
SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS);
|
|
mmrInit = waveOutOpen(&pds->hwo, pds->uDeviceID, pds->pdsbPrimary->pwfx,
|
|
(DWORD)waveThreadCallback, (DWORD)pds, CALLBACK_FUNCTION);
|
|
SetPriorityClass(GetCurrentProcess(), dwPriority);
|
|
|
|
if (!mmrInit) {
|
|
// Some mmsystem wave drivers will program their wave mixer
|
|
// hardware only while the device is open. By doing the
|
|
// following, we can get such drivers to program the hardware
|
|
if (MMSYSERR_NOERROR == waveOutGetVolume(pds->hwo, &dwVolume)) {
|
|
waveOutSetVolume(pds->hwo, dwVolume);
|
|
}
|
|
|
|
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
|
|
|
|
mmrInit = DSDetermineDMASize(pds, pds->pdsbPrimary->pwfx);
|
|
if (!mmrInit) {
|
|
|
|
ASSERT(NULL == pds->hEventWaveHeaderDone);
|
|
pds->cwhDone = 0;
|
|
pds->hEventWaveHeaderDone = CreateEvent(NULL, FALSE, FALSE, pds->szEventWaveHeaderDone);
|
|
if (!pds->hEventWaveHeaderDone) mmrInit = MMSYSERR_NOMEM;
|
|
if (!mmrInit) {
|
|
|
|
mmrInit = waveAllocAndPrepareLoopingBuffers(pds);
|
|
if (!mmrInit) {
|
|
|
|
hEventTerminate = CreateEvent(NULL, FALSE, FALSE, pds->szEventTerminateWaveThread);
|
|
if (!hEventTerminate) mmrInit = MMSYSERR_NOMEM;
|
|
if (!mmrInit) {
|
|
|
|
// Signal that we're finished with initialization.
|
|
// mmrInit should not be modified below this point.
|
|
pds->mmrWaveThreadInit = mmrInit;
|
|
SetEvent(hEventInitDone);
|
|
|
|
waveThreadLoop(pds, hEventTerminate);
|
|
|
|
mmr = waveOutReset(pds->hwo);
|
|
ASSERT(!mmr);
|
|
|
|
CloseHandle(hEventTerminate);
|
|
}
|
|
|
|
waveUnprepareLoopingBuffers(pds);
|
|
}
|
|
|
|
CloseHandle(pds->hEventWaveHeaderDone);
|
|
pds->hEventWaveHeaderDone = NULL;
|
|
}
|
|
}
|
|
|
|
mmr = waveOutClose(pds->hwo);
|
|
pds->hwo = NULL;
|
|
ASSERT(!mmr);
|
|
}
|
|
|
|
// If init failed, set the result code and signal init done.
|
|
if (mmrInit) {
|
|
pds->mmrWaveThreadInit = mmrInit;
|
|
SetEvent(hEventInitDone);
|
|
}
|
|
|
|
CloseHandle(hEventInitDone);
|
|
return 0;
|
|
}
|
|
|
|
MMRESULT FNGLOBAL DSInitializeEmulator
|
|
(
|
|
LPDSOUND pds
|
|
)
|
|
{
|
|
DWORD dwResult;
|
|
HANDLE hEventInitDone;
|
|
MMRESULT mmr;
|
|
|
|
DPF(3, "DSInitializeEmulator");
|
|
ASSERT(pds->pdsbPrimary);
|
|
|
|
ASSERT(SIZEOF(pds->szEventWaveHeaderDone) >= 7+8+8+1);
|
|
wsprintf(pds->szEventWaveHeaderDone, "DS-EWHD%08lX%08lX", GetCurrentProcessId(), pds);
|
|
|
|
ASSERT(SIZEOF(pds->szEventWaveThreadInitDone) >= 8+8+8+1);
|
|
wsprintf(pds->szEventWaveThreadInitDone, "DS-EWTID%08lX%08lX", GetCurrentProcessId(), pds);
|
|
|
|
ASSERT(SIZEOF(pds->szEventTerminateWaveThread) >= 7+8+8+1);
|
|
wsprintf(pds->szEventTerminateWaveThread, "DS-ETWT%08lX%08lX", GetCurrentProcessId(), pds);
|
|
|
|
hEventInitDone = CreateEvent(NULL, FALSE, FALSE, pds->szEventWaveThreadInitDone);
|
|
if (!hEventInitDone) return MMSYSERR_NOMEM;
|
|
|
|
// side effects begin (hEventInitDone created)
|
|
|
|
// hWaveThread is the thread which recycles wave buffers
|
|
pds->hWaveThread = HelperCreateDSMixerThread(waveThread, pds, 0, NULL);
|
|
mmr = (pds->hWaveThread) ? MMSYSERR_NOERROR : MMSYSERR_NOMEM;
|
|
|
|
if (!mmr) {
|
|
DPF(0, "waveThread handle is %08lX", (LONG)pds->hWaveThread);
|
|
|
|
dwResult = WaitForSingleObjectEx(hEventInitDone, INFINITE, FALSE);
|
|
ASSERT(WAIT_OBJECT_0 == dwResult);
|
|
mmr = pds->mmrWaveThreadInit;
|
|
|
|
if (mmr) {
|
|
HANDLE hHelper;
|
|
HANDLE hWaveThreadOurs;
|
|
|
|
// Something went wrong. Clean up.
|
|
|
|
// Note that hWaveThread is relative to the helper process.
|
|
hHelper = OpenProcess(PROCESS_DUP_HANDLE, FALSE, gpdsinfo->pidHelper);
|
|
if (hHelper) {
|
|
if (DuplicateHandle(hHelper, pds->hWaveThread,
|
|
GetCurrentProcess(), &hWaveThreadOurs,
|
|
SYNCHRONIZE | THREAD_TERMINATE,
|
|
FALSE, DUPLICATE_CLOSE_SOURCE))
|
|
{
|
|
dwResult = WaitForSingleObjectEx(hWaveThreadOurs, INFINITE, FALSE);
|
|
ASSERT(WAIT_OBJECT_0 == dwResult);
|
|
dwResult = CloseHandle(hWaveThreadOurs);
|
|
ASSERT(dwResult);
|
|
}
|
|
dwResult = CloseHandle(hHelper);
|
|
ASSERT(dwResult);
|
|
}
|
|
pds->hWaveThread = NULL;
|
|
}
|
|
}
|
|
|
|
dwResult = CloseHandle(hEventInitDone);
|
|
ASSERT(dwResult);
|
|
|
|
return mmr;
|
|
}
|
|
|
|
MMRESULT FNGLOBAL DSShutdownEmulator
|
|
(
|
|
LPDSOUND pds
|
|
)
|
|
{
|
|
HANDLE hEventTerminate;
|
|
HANDLE hHelper;
|
|
HANDLE hWaveThreadOurs;
|
|
DWORD dwResult;
|
|
|
|
DPF(0, " !!! About to shutdown emulator !!!");
|
|
|
|
ASSERT(pds->hWaveThread);
|
|
|
|
// Signal wave thread to go away.
|
|
|
|
hEventTerminate = CreateEvent(NULL, FALSE, FALSE, pds->szEventTerminateWaveThread);
|
|
if (hEventTerminate) {
|
|
SetEvent(hEventTerminate);
|
|
CloseHandle(hEventTerminate);
|
|
hEventTerminate = NULL;
|
|
}
|
|
|
|
DPF(0, "Emulator: Wait for callback thread to die");
|
|
|
|
hHelper = OpenProcess(PROCESS_DUP_HANDLE, FALSE, gpdsinfo->pidHelper);
|
|
if (hHelper) {
|
|
if (DuplicateHandle(hHelper, pds->hWaveThread, GetCurrentProcess(),
|
|
&hWaveThreadOurs, SYNCHRONIZE | THREAD_TERMINATE,
|
|
FALSE, DUPLICATE_CLOSE_SOURCE))
|
|
{
|
|
dwResult = WaitForSingleObjectEx(hWaveThreadOurs, INFINITE, FALSE);
|
|
ASSERT(dwResult == WAIT_OBJECT_0);
|
|
|
|
dwResult = CloseHandle(hWaveThreadOurs);
|
|
ASSERT(dwResult);
|
|
}
|
|
dwResult = CloseHandle(hHelper);
|
|
ASSERT(dwResult);
|
|
} else {
|
|
DPF(0, "Emulator: couldn't open handle on helper");
|
|
}
|
|
|
|
ASSERT(NULL == pds->hwo); // waveThread should do this if
|
|
pds->hwo = NULL; // it terminates normally
|
|
|
|
pds->hWaveThread = NULL;
|
|
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
|
|
HRESULT CreateNewDirectSoundObject
|
|
(LPDIRECTSOUND *ppDS,
|
|
IUnknown *pUnkOuter)
|
|
{
|
|
LPDSOUNDEXTERNAL pdse;
|
|
|
|
DPF(2, "DSound: CreateNewDirectSoundObject: Function Enter");
|
|
|
|
/* Argument Validation */
|
|
*ppDS = NULL;
|
|
if( pUnkOuter != NULL ) {
|
|
RPF("DSound: Direct Sound does not support aggregation.");
|
|
return CLASS_E_NOAGGREGATION;
|
|
}
|
|
|
|
/* Object Allocation */
|
|
pdse = (LPDSOUNDEXTERNAL)MemAlloc(sizeof(DSOUNDEXTERNAL));
|
|
if (NULL == pdse) {
|
|
RPF("DSound: Direct Sound Object memory allocation failed.");
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
/* Initialize Data Members */
|
|
pdse->lpVtbl = gpdsinfo->lpVtblDS;
|
|
/* set to NULL to signal no */
|
|
pdse->fInitialized = IDSHWINITIALIZEF_UNINITIALIZED;
|
|
|
|
/* initialization */
|
|
pdse->uRefCount = 1;
|
|
|
|
*ppDS = (LPDIRECTSOUND)pdse;
|
|
|
|
DPF(2, "DSound: CreateNewDirectSoundObject: Successful Function Exit");
|
|
|
|
return S_OK;
|
|
}
|