windows-nt/Source/XPSP1/NT/multimedia/directx/dsound/dsvxd/kegrace.cpp

874 lines
19 KiB
C++
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
//--------------------------------------------------------------------------;
//
// 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;
}