|
|
//--------------------------------------------------------------------------;
//
// File: kegrace.cpp
//
// Copyright (c) 1995 Microsoft Corporation. All Rights Reserved.
//
// Abstract:
//
// Contents:
//
// History:
// 06/29/96 FrankYe Created
//
//--------------------------------------------------------------------------;
#define NODSOUNDSERVICETABLE
#include "dsoundi.h"
// never premix less than this
#define MIN_PREMIX 45
#pragma VxD_LOCKED_CODE_SEG
#pragma VxD_LOCKED_DATA_SEG
extern "C" void KeGrace_GlobalTimeOutProcAsm();
LONG lMixerMutex;
LONG glNum; DWORDLONG gdwlTotalWasted; DWORDLONG gdwlTotal; DWORDLONG _inline GetPentiumCounter(void) { _asm _emit 0x0F _asm _emit 0x31 }
ULONG VXDINLINE VMM_Get_System_Time(void) { ULONG Time;
Touch_Register(eax); VMMCall(Get_System_Time); _asm mov Time, eax; return Time; }
VOID _VMCPD_Get_Thread_State(PTCB Thread, PVOID pCPState) { _asm mov esi, pCPState; _asm mov edi, Thread; VxDCall(VMCPD_Get_Thread_State); }
VOID _VMCPD_Set_Thread_State(PTCB Thread, PVOID pCPState) { _asm mov esi, pCPState; _asm mov edi, Thread; VxDCall(VMCPD_Set_Thread_State); }
LONG _InterlockedExchange(PLONG pTarget, LONG Value) { LONG OldTarget; _asm push edi; _asm mov eax, Value; _asm mov edi, pTarget; _asm xchg [edi], eax; _asm mov OldTarget, eax; _asm pop edi; return OldTarget; }
// Must be in locked code
LONG _InterlockedExchangeAdd(PLONG pAddend, LONG Increment) { LONG OldAddend; _asm mov esi, pAddend; _asm mov ecx, Increment; _asm mov eax, [esi]; // Read it (possibly causing a fault in)
_asm add ecx, eax; _asm mov [esi], ecx; _asm mov OldAddend, eax; return OldAddend; }
VOID _ZeroMemory(PVOID pDestination, DWORD cbLength) { _asm mov edi, pDestination ; _asm mov esi, cbLength ; _asm xor eax, eax ; _asm mov ecx, esi ; _asm shr ecx, 2 ; _asm rep stosd ; _asm mov ecx, esi ; _asm and ecx, 3 ; _asm rep stosb ; } // Override the global new and delete operators
void * ::operator new(size_t size) { return MemAlloc(size); }
void ::operator delete(void * pv) { MemFree(pv); }
// Implement our own purecall
int __cdecl _purecall(void) { ASSERT(FALSE); return 0; }
typedef struct tEVENTPARAMS { HTIMEOUT hEvent; class CKeGrace *pThis; } EVENTPARAMS, *PEVENTPARAMS;
class CKeGrace : public CGrace { public: HRESULT Initialize(CGrDest *pGrDest); void Terminate(void); void SignalRemix(void); int GetMaxRemix(void); void GlobalTimeOutProc(int dtimeTardiness);
private: static const int MIXER_MINPREMIX; static const int MIXER_MAXPREMIX; LONG m_dtimePremix; LONG m_ddtimePremix; EVENTPARAMS m_EventParams; LONG m_timeBusyWaitForMutex; };
const int CKeGrace::MIXER_MINPREMIX = 45; const int CKeGrace::MIXER_MAXPREMIX = 200;
extern "C" void KeGrace_GlobalTimeOutProc(PVOID pKeGrace, int dtimeTardiness) { ((CKeGrace*)pKeGrace)->GlobalTimeOutProc(dtimeTardiness); }
void CKeGrace::SignalRemix() { HTIMEOUT hEvent;
#if 0
// If you wanna run without remixing, just enable this bit of code. You
// might want to lower the MIXER_MAXPREMIX constant as well.
m_fdwMixerSignal &= DSMIXERSIGNAL_REMIX; return; #endif
if (!(m_fdwMixerSignal & DSMIXERSIGNAL_REMIX)) { m_fdwMixerSignal |= DSMIXERSIGNAL_REMIX;
// Set a new time out for 2ms, and then cancel any prior pending timeout.
//
// Note that "2ms" is somewhat arbitrary. It just needs to be
// enough time to hopefully let this thread release the mixer
// mutex before the event executes.
hEvent = Set_Global_Time_Out(KeGrace_GlobalTimeOutProcAsm, 2, (ULONG)&m_EventParams); hEvent = _InterlockedExchange((PLONG)&m_EventParams.hEvent, hEvent); Cancel_Time_Out(hEvent); } }
void CKeGrace::GlobalTimeOutProc(int dtimeTardiness) { char CPState[108]; // Size of fp state per Intel prog. ref.
LONG dtime; LONG dtimeSleep; LONG dtimeInvalid; LONG dtimeNextNotify; int cSamplesPremixMax; int cSamplesPremixed;
// DPF(("CKeGrace::GlobalTimeOutProc"));
if (m_dtimePremix/2 < dtimeTardiness) { DPF(("CKeGrace_GlobalTimeOutProc : warning: %dms late", dtimeTardiness)); }
//
// We busy wait on the mutex, each iteration of the wait is longer
// than the previous.
//
if (_InterlockedExchange(&lMixerMutex, TRUE)) { HTIMEOUT hEvent; LONG timeOut; // DPF(("CKeGrace::GlobalTimeOutProc : note: mutex already owned"));
timeOut = _InterlockedExchangeAdd(&m_timeBusyWaitForMutex, 1); hEvent = Set_Global_Time_Out(KeGrace_GlobalTimeOutProcAsm, timeOut, (ULONG)&m_EventParams); hEvent = _InterlockedExchange((PLONG)&m_EventParams.hEvent, hEvent); Cancel_Time_Out(hEvent); return; } m_timeBusyWaitForMutex = 1; // Three cases:
// 1) mixer is stopped
// 2) mixer running and a remix is pending
// 3) mixer running and no remix is pending
//
// Around each call to Refresh we need to save and restore the thread's
// floating point state using the VMCPD Get/Set_Thread_State services.
//
if (MIXERSTATE_STOPPED == m_kMixerState) { dtimeSleep = 1000; // arbitrarily set for 1 second
} else {
// DWORDLONG dwlStartCycle;
// DWORDLONG dwlT;
// dwlStartCycle = dwlT = GetPentiumCounter();
dtime = VMM_Get_System_Time();
_ZeroMemory(&CPState, sizeof(CPState)); _VMCPD_Get_Thread_State(Get_Cur_Thread_Handle(), &CPState);
// gdwlTotalWasted += GetPentiumCounter() - dwlT;
// glNum++;
if (m_fdwMixerSignal & DSMIXERSIGNAL_REMIX) {
m_dtimePremix = MIXER_MINPREMIX; // Initial premix length
m_ddtimePremix = 2; // increment
cSamplesPremixMax = MulDivRD(m_dtimePremix, m_pDest->m_nFrequency, 1000); Refresh(TRUE, cSamplesPremixMax, &cSamplesPremixed, &dtimeNextNotify); } else {
m_dtimePremix += m_ddtimePremix; if (m_dtimePremix > MIXER_MAXPREMIX) { m_dtimePremix = MIXER_MAXPREMIX; } else { m_ddtimePremix += 2; }
cSamplesPremixMax = MulDivRD(m_dtimePremix, m_pDest->m_nFrequency, 1000); Refresh(FALSE, cSamplesPremixMax, &cSamplesPremixed, &dtimeNextNotify); }
// dwlT = GetPentiumCounter();
_VMCPD_Set_Thread_State(Get_Cur_Thread_Handle(), &CPState);
dtimeInvalid = MulDivRD(cSamplesPremixed, 1000, m_pDest->m_nFrequency); dtime = VMM_Get_System_Time() - dtime; dtimeInvalid -= 2 * dtime;
dtimeSleep = min(dtimeNextNotify, dtimeInvalid/2); dtimeSleep = max(dtimeSleep, MIXER_MINPREMIX/2);
// gdwlTotalWasted += GetPentiumCounter() - dwlT;
// gdwlTotal += GetPentiumCounter() - dwlStartCycle;
}
// DPF(("CKeGrace::GlobalTimeOutProc : note: dtimeSleep=%dms", dtimeSleep));
ASSERT(!m_EventParams.hEvent); m_EventParams.hEvent = Set_Global_Time_Out(KeGrace_GlobalTimeOutProcAsm, dtimeSleep, (ULONG)&m_EventParams); _InterlockedExchange(&lMixerMutex, FALSE); }
HRESULT CKeGrace::Initialize(CGrDest *pDest) { HRESULT hr;
hr = CGrace::Initialize(pDest); if (S_OK != hr) return hr; DPF(("CKeGrace::Initialize : note: Setting up first GlobalTimeOut"));
// If we want to run the timer really fast, do this. So far I haven't seen
// any empirical evidence of this helping.
VTD_Begin_Min_Int_Period(5);
m_dtimePremix = MIXER_MINPREMIX; // Initial premix length
m_ddtimePremix = 2; // increment
// REMIND do error check
m_timeBusyWaitForMutex = 1; m_EventParams.pThis = this; m_EventParams.hEvent = Set_Global_Time_Out(KeGrace_GlobalTimeOutProcAsm, 1, (ULONG)&m_EventParams);
gdwlTotal = 0; gdwlTotalWasted = 0; glNum = 0; return hr; }
//--------------------------------------------------------------------------;
//
// Terminate
//
// This function is called to terminate the grace mixer thread for the
// specified ds object. It returns the handle to the thread that is being
// terminated. After releasing any critical sections that the grace mixer
// thread may be waiting on, the caller should wait for the thread handle
// to become signaled. For Win32 beginners: the thread handle is signalled
// after the thread terminates.
//
//--------------------------------------------------------------------------;
void CKeGrace::Terminate() { HTIMEOUT hEvent;
hEvent = _InterlockedExchange((PLONG)&m_EventParams.hEvent, 0); Cancel_Time_Out(hEvent);
CGrace::Terminate();
if (0 != glNum) { DPF(("Wasted time = %d cycles", (int)(gdwlTotalWasted / glNum))); DPF(("Total time = %d cycles", (int)(gdwlTotal / glNum))); } }
int CKeGrace::GetMaxRemix(void) { // return max number of samples we might remix
return (MulDivRU(MIXER_MAXPREMIX, m_pDest->m_nFrequency, 1000)); }
#pragma VxD_PAGEABLE_CODE_SEG
#pragma VxD_PAGEABLE_DATA_SEG
class CKeGrDest : public CGrDest { public: CKeGrDest(LPNAGRDESTDATA); HRESULT Initialize(void); void Terminate(void); HRESULT SetFormat(LPWAVEFORMATEX pwfx); HRESULT AllocMixer(CMixer **ppMixer); void FreeMixer(void); HRESULT GetSamplePosition(int *pposPlay, int *pposWrite); HRESULT GetSamplePositionNoWin16(int *pposPlay, int *pposWrite); HRESULT Lock(PVOID *ppBuffer1, int *pcbBuffer1, PVOID *ppBuffer2, int *pcbBuffer2, int ibWrite, int cbWrite); HRESULT Unlock(PVOID pBuffer1, int cbBuffer1, PVOID pBuffer2, int cbBuffer2); void Play(); void Stop();
private: CKeGrace* m_pKeGrace; DWORD m_fdwDriverDesc; CBuf* m_pDrvBuf; // Let's only send a stop if we are currently playing
BOOL m_fStopped; };
CKeGrDest::CKeGrDest(LPNAGRDESTDATA pData) { m_cbBuffer = pData->cbBuffer; m_pBuffer = pData->pBuffer; m_pDrvBuf = ((CBuf*)((PIDSDRIVERBUFFER)pData->hBuffer)); m_fdwDriverDesc = pData->fdwDriverDesc; m_fStopped = TRUE; }
HRESULT CKeGrDest::Initialize(void) { m_cSamples = m_cbBuffer >> m_nBlockAlignShift; return DS_OK; }
void CKeGrDest::Terminate(void) { return; }
HRESULT CKeGrDest::AllocMixer(CMixer **ppMixer) { HRESULT hr; ASSERT(m_pBuffer); *ppMixer = NULL; m_pKeGrace = new CKeGrace; if (m_pKeGrace) { hr = m_pKeGrace->Initialize(this); if (S_OK != hr) { delete m_pKeGrace; m_pKeGrace = NULL; } } else { hr = DSERR_OUTOFMEMORY; }
if (S_OK == hr) *ppMixer = m_pKeGrace; return hr; }
void CKeGrDest::FreeMixer() { ASSERT(m_pKeGrace);
m_pKeGrace->Terminate(); delete m_pKeGrace; m_pKeGrace = NULL; }
HRESULT CKeGrDest::SetFormat(LPWAVEFORMATEX pwfx) { HRESULT hr;
SetFormatInfo(pwfx); hr = m_pDrvBuf->SetFormat(pwfx); return hr; }
void CKeGrDest::Play() { HRESULT hr; // REMIND we're not propagating errors here!
hr = m_pDrvBuf->Play(0, 0, DSBPLAY_LOOPING); if (SUCCEEDED(hr)) m_fStopped = FALSE; }
void CKeGrDest::Stop() { HRESULT hr; if (m_fStopped == FALSE) { hr = m_pDrvBuf->Stop(); if (SUCCEEDED(hr)) m_fStopped = TRUE; } } HRESULT CKeGrDest::Lock(PVOID *ppBuffer1, int *pcbBuffer1, PVOID *ppBuffer2, int *pcbBuffer2, int ibWrite, int cbWrite) { LOCKCIRCULARBUFFER lcb; HRESULT hr; lcb.pHwBuffer = m_pDrvBuf; lcb.pvBuffer = m_pBuffer; lcb.cbBuffer = m_cbBuffer; lcb.fPrimary = TRUE; lcb.fdwDriverDesc = m_fdwDriverDesc; lcb.ibRegion = ibWrite; lcb.cbRegion = cbWrite;
hr = LockCircularBuffer(&lcb);
if(SUCCEEDED(hr)) { *ppBuffer1 = lcb.pvLock[0]; *pcbBuffer1 = lcb.cbLock[0];
if(ppBuffer2) { *ppBuffer2 = lcb.pvLock[1]; } else { ASSERT(!lcb.pvLock[1]); }
if(pcbBuffer2) { *pcbBuffer2 = lcb.cbLock[1]; } else { ASSERT(!lcb.cbLock[1]); } }
return hr; }
HRESULT CKeGrDest::Unlock(PVOID pBuffer1, int cbBuffer1, PVOID pBuffer2, int cbBuffer2) { LOCKCIRCULARBUFFER lcb;
lcb.pHwBuffer = m_pDrvBuf; lcb.pvBuffer = m_pBuffer; lcb.cbBuffer = m_cbBuffer; lcb.fPrimary = TRUE; lcb.fdwDriverDesc = m_fdwDriverDesc; lcb.pvLock[0] = pBuffer1; lcb.cbLock[0] = cbBuffer1; lcb.pvLock[1] = pBuffer2; lcb.cbLock[1] = cbBuffer2;
return UnlockCircularBuffer(&lcb); }
HRESULT CKeGrDest::GetSamplePosition(int *pposPlay, int *pposWrite) { HRESULT hr; DWORD dwPlay, dwWrite;
ASSERT(pposPlay && pposWrite); hr = m_pDrvBuf->GetPosition(&dwPlay, &dwWrite); if (S_OK == hr) {
*pposPlay = dwPlay >> m_nBlockAlignShift; *pposWrite = dwWrite >> m_nBlockAlignShift;
// Until we write code to actually profile the performance, we'll just
// pad the write position with a hard coded amount
*pposWrite += m_nFrequency * HW_WRITE_CURSOR_MSEC_PAD / 1024; if (*pposWrite >= m_cSamples) *pposWrite -= m_cSamples; ASSERT(*pposWrite < m_cSamples);
} else { *pposPlay = *pposWrite = 0; }
return hr; }
inline HRESULT CKeGrDest::GetSamplePositionNoWin16(int *pposPlay, int *pposWrite) { return GetSamplePosition(pposPlay, pposWrite); }
int ioctlMixer_Run(PDIOCPARAMETERS pdiocp) { CMixer *pMixer; HRESULT hr;
IOSTART(1*4);
IOINPUT(pMixer, CMixer*);
hr = pMixer->Run();
IOOUTPUT(hr, HRESULT); IORETURN; return 0; }
int ioctlMixer_Stop(PDIOCPARAMETERS pdiocp) { BOOL f; CMixer *pMixer;
IOSTART(1*4);
IOINPUT(pMixer, CMixer*);
f = pMixer->Stop();
IOOUTPUT(f, BOOL); IORETURN; return 0; }
int ioctlMixer_PlayWhenIdle(PDIOCPARAMETERS pdiocp) { CMixer *pMixer;
IOSTART(1*4);
IOINPUT(pMixer, CMixer*);
pMixer->PlayWhenIdle();
IORETURN; return 0; }
int ioctlMixer_StopWhenIdle(PDIOCPARAMETERS pdiocp) { CMixer *pMixer;
IOSTART(1*4);
IOINPUT(pMixer, CMixer*);
pMixer->StopWhenIdle();
IORETURN; return 0; }
int ioctlMixer_MixListAdd(PDIOCPARAMETERS pdiocp) { CMixer *pMixer; CMixSource *pSource;
IOSTART(2*4);
IOINPUT(pMixer, CMixer*); IOINPUT(pSource, CMixSource*);
pMixer->MixListAdd(pSource);
IORETURN; return 0; }
int ioctlMixer_MixListRemove(PDIOCPARAMETERS pdiocp) { CMixer *pMixer; CMixSource *pSource;
IOSTART(2*4);
IOINPUT(pMixer, CMixer*); IOINPUT(pSource, CMixSource*);
pMixer->MixListRemove(pSource);
IORETURN; return 0; }
int ioctlMixer_FilterOn(PDIOCPARAMETERS pdiocp) { CMixer *pMixer; CMixSource *pSource;
IOSTART(2*4);
IOINPUT(pMixer, CMixer*); IOINPUT(pSource, CMixSource*);
pMixer->FilterOn(pSource);
IORETURN; return 0; }
int ioctlMixer_FilterOff(PDIOCPARAMETERS pdiocp) { CMixer *pMixer; CMixSource *pSource;
IOSTART(2*4);
IOINPUT(pMixer, CMixer*); IOINPUT(pSource, CMixSource*);
pMixer->FilterOff(pSource);
IORETURN; return 0; }
int ioctlMixer_GetBytePosition(PDIOCPARAMETERS pdiocp) { CMixer *pMixer; CMixSource *pSource; int *pibPlay; int *pibWrite;
IOSTART(4*4);
IOINPUT(pMixer, CMixer*); IOINPUT(pSource, CMixSource*); IOINPUT(pibPlay, int*); IOINPUT(pibWrite, int*);
pMixer->GetBytePosition(pSource, pibPlay, pibWrite);
IORETURN; return 0; }
int ioctlMixer_SignalRemix(PDIOCPARAMETERS pdiocp) { CMixer *pMixer;
IOSTART(1*4);
IOINPUT(pMixer, CMixer*);
pMixer->SignalRemix();
IORETURN; return 0; }
int ioctlKeDest_New(PDIOCPARAMETERS pdiocp) { LPNAGRDESTDATA pData; CKeGrDest *pKeGrDest;
IOSTART(1*4);
IOINPUT(pData, LPNAGRDESTDATA); pKeGrDest = new CKeGrDest(pData);
IOOUTPUT(pKeGrDest, CKeGrDest*); IORETURN; return 0; }
int ioctlMixDest_Delete(PDIOCPARAMETERS pdiocp) { CMixDest *pMixDest;
IOSTART(1*4);
IOINPUT(pMixDest, CMixDest*);
delete pMixDest;
IORETURN; return 0; }
int ioctlMixDest_Initialize(PDIOCPARAMETERS pdiocp) { CMixDest *pMixDest; HRESULT hr;
IOSTART(1*4);
IOINPUT(pMixDest, CMixDest*);
hr = pMixDest->Initialize();
IOOUTPUT(hr, HRESULT);
IORETURN; return 0; } int ioctlMixDest_Terminate(PDIOCPARAMETERS pdiocp) { CMixDest *pMixDest;
IOSTART(1*4);
IOINPUT(pMixDest, CMixDest*);
pMixDest->Terminate();
IORETURN; return 0; } int ioctlMixDest_SetFormat(PDIOCPARAMETERS pdiocp) { CMixDest *pMixDest; LPWAVEFORMATEX pwfx; HRESULT hr;
IOSTART(2*4);
IOINPUT(pMixDest, CMixDest*); IOINPUT(pwfx, LPWAVEFORMATEX);
hr = pMixDest->SetFormat(pwfx);
IOOUTPUT(hr, HRESULT); IORETURN; return 0; }
int ioctlMixDest_SetFormatInfo(PDIOCPARAMETERS pdiocp) { CMixDest *pMixDest; LPWAVEFORMATEX pwfx;
IOSTART(2*4);
IOINPUT(pMixDest, CMixDest*); IOINPUT(pwfx, LPWAVEFORMATEX);
pMixDest->SetFormatInfo(pwfx);
IORETURN; return 0; }
int ioctlMixDest_AllocMixer(PDIOCPARAMETERS pdiocp) { CMixDest *pMixDest; CMixer **ppMixer; HRESULT hr;
IOSTART(2*4);
IOINPUT(pMixDest, CMixDest*); IOINPUT(ppMixer, CMixer**);
hr = pMixDest->AllocMixer(ppMixer);
IOOUTPUT(hr, HRESULT); IORETURN; return 0; }
int ioctlMixDest_FreeMixer(PDIOCPARAMETERS pdiocp) { CMixDest *pMixDest;
IOSTART(1*4);
IOINPUT(pMixDest, CMixDest*);
pMixDest->FreeMixer();
IORETURN; return 0; }
int ioctlMixDest_Play(PDIOCPARAMETERS pdiocp) { CMixDest *pMixDest;
IOSTART(1*4);
IOINPUT(pMixDest, CMixDest*);
pMixDest->Play();
IORETURN; return 0; }
int ioctlMixDest_Stop(PDIOCPARAMETERS pdiocp) { CMixDest *pMixDest;
IOSTART(1*4);
IOINPUT(pMixDest, CMixDest*);
pMixDest->Stop();
IORETURN; return 0; }
int ioctlMixDest_GetFrequency(PDIOCPARAMETERS pdiocp) { CMixDest *pMixDest; int nFrequency;
IOSTART(1*4);
IOINPUT(pMixDest, CMixDest*);
nFrequency = pMixDest->GetFrequency();
IOOUTPUT(nFrequency, int); IORETURN; return 0; }
int ioctlDsvxd_GetMixerMutexPtr(PDIOCPARAMETERS pdiocp) { IOSTART(0*4); IOOUTPUT(&lMixerMutex, PLONG); IORETURN; return 0; }
|