#include "precomp.h"
#include <nmdsprv.h>

//bytes <-> PCM16 samples
inline UINT BYTESTOSAMPLES(UINT bytes) { return bytes/2;}
inline UINT SAMPLESTOBYTES(UINT samples) {return samples*2;}
// 'quick' modulo operator. reason its quick is because it only works if  -mod < x < 2*mod
inline UINT QMOD(const int x, const int mod)
{ 	if (x >= mod)
		return (x-mod);
	if (x < 0)
		return (x+mod);
	else
		return x;
}

#define BUFFER_RECEIVED 1		// used to indicate that the buffer is ready to play
#define BUFFER_SILENT	2		// buffer appears to be silent

#define DSFLAG_ALLOCATED 1

const int MIN_DSBUF_SIZE = 4000;

struct DSINFO {
	struct DSINFO *pNext;
	DWORD flags;
	GUID guid;
	LPSTR pszDescription;
	LPSTR pszModule;
	LPDIRECTSOUND pDS;
	LPDIRECTSOUNDBUFFER pDSPrimaryBuf;
	UINT uRef;
};

// initial all the globals
DSINFO *DirectSoundMgr::m_pDSInfoList = NULL;
BOOL DirectSoundMgr::m_fInitialized = FALSE;
HINSTANCE DirectSoundMgr::m_hDS = NULL;
LPFNDSCREATE DirectSoundMgr::m_pDirectSoundCreate=NULL;
LPFNDSENUM DirectSoundMgr::m_pDirectSoundEnumerate=NULL;

GUID myNullGuid = {0};

HRESULT DirectSoundMgr::Initialize()
{
	HRESULT hr;

	// currently there seems no need to re-enumerate the list of devices
	// but that can be changed if the need arises
	if (m_fInitialized)
		return (m_pDSInfoList == NULL ? DPR_NO_PLAY_CAP : S_OK);

	ASSERT(!m_pDSInfoList);
	m_hDS = ::LoadLibrary("DSOUND");
	if (m_hDS != NULL)
	{
		if (GetProcAddress(m_hDS, "DirectSoundCaptureCreate")	// this identifies DS5 or later
			&& (m_pDirectSoundCreate = (LPFNDSCREATE)GetProcAddress(m_hDS,"DirectSoundCreate"))
			&& (m_pDirectSoundEnumerate = (LPFNDSENUM)GetProcAddress(m_hDS,"DirectSoundEnumerateA"))
			)
		{
			if ((hr=(*m_pDirectSoundEnumerate)(DSEnumCallback, 0)) != S_OK)
			{
				DEBUGMSG(ZONE_DP,("DSEnumerate failed with %x\n",hr));
			} else {
				if (!m_pDSInfoList) {
					DEBUGMSG(ZONE_DP,("DSEnumerate - no devices found\n"));
					hr = DPR_NO_PLAY_CAP;	// no devices were found
				}
			}
		
		} else {
			hr = DPR_INVALID_PLATFORM;	// better error code?
		}
		if (hr != S_OK) {
			FreeLibrary(m_hDS);
			m_hDS = NULL;
		}
	}
	else
	{
		DEBUGMSG(ZONE_INIT,("LoadLibrary(DSOUND) failed"));
		hr = DPR_NO_PLAY_CAP;
	}

	m_fInitialized = TRUE;
	return hr;
}


HRESULT DirectSoundMgr::UnInitialize()
{
	DSINFO *pDSINFO = m_pDSInfoList, *pDSNEXT;
	if (m_fInitialized)
	{

		while (pDSINFO)
		{
			pDSNEXT = pDSINFO->pNext;
			delete [] pDSINFO->pszDescription;
			delete [] pDSINFO->pszModule;
			delete pDSINFO;
			pDSINFO = pDSNEXT;
		}

		m_fInitialized = FALSE;
		m_pDSInfoList = NULL;
	}

	return S_OK;
}



BOOL __stdcall DirectSoundMgr::DSEnumCallback(
	LPGUID lpGuid,
	LPCSTR lpstrDescription,
	LPCSTR lpstrModule,
	LPVOID lpContext
	)
{
	DSINFO *pDSInfo;
	
    DBG_SAVE_FILE_LINE
	pDSInfo = new DSINFO;
	if (pDSInfo) {
		pDSInfo->uRef = 0;
		pDSInfo->guid = (lpGuid ? *lpGuid : GUID_NULL);

        DBG_SAVE_FILE_LINE
		pDSInfo->pszDescription = new CHAR [lstrlen(lpstrDescription)+1];
		if (pDSInfo->pszDescription)
			lstrcpy(pDSInfo->pszDescription, lpstrDescription);

        DBG_SAVE_FILE_LINE
		pDSInfo->pszModule = new CHAR [lstrlen(lpstrModule)+1];
		if (pDSInfo->pszModule)
			lstrcpy(pDSInfo->pszModule, lpstrModule);

		// append to list
		pDSInfo->pNext = m_pDSInfoList;
		m_pDSInfoList = pDSInfo;
	}
	DEBUGMSG(ZONE_DP,("DSound device found: (%s) ; driver (%s);\n",lpstrDescription, lpstrModule));
	return TRUE;
}

HRESULT
DirectSoundMgr::MapWaveIdToGuid(UINT waveId, GUID *pGuid)
{
	// try to figure out which Guid maps to a wave id
	// Do this by opening the wave device corresponding to the wave id and then
	// all the DS devices in sequence and see which one fails.
	// Yes, this is a monstrous hack and clearly unreliable
	HWAVEOUT hWaveOut = NULL;
	MMRESULT mmr;
	HRESULT hr;
	DSINFO *pDSInfo;
	LPDIRECTSOUND pDS;
	DSCAPS dscaps;
	BOOL fEmulFound;
	WAVEFORMATEX wfPCM8K16 = {WAVE_FORMAT_PCM,1,8000,16000,2,16,0};
	WAVEOUTCAPS	waveOutCaps;

	if (!m_fInitialized)
		Initialize();	// get the list of DS devices

	if (!m_pDSInfoList)
		return DPR_CANT_OPEN_DEV;
	else if (waveId == WAVE_MAPPER || waveOutGetNumDevs()==1) {
		// we want the default or there is only one DS device, take the easy way out
		*pGuid =  GUID_NULL;
		return S_OK;
	}


	// try using the IKsProperty interface on a DirectSoundPrivate object
	// to find out what GUID maps to the waveId in question
	// Only likely to work on Win98 and NT 5.
	ZeroMemory(&waveOutCaps, sizeof(WAVEOUTCAPS));
	mmr = waveOutGetDevCaps(waveId, &waveOutCaps, sizeof(WAVEOUTCAPS));
	if (mmr == MMSYSERR_NOERROR)
	{
		hr = DsprvGetWaveDeviceMapping(waveOutCaps.szPname, FALSE, pGuid);
		if (SUCCEEDED(hr))
		{
			return hr;
		}
		// if we failed to make a mapping, fall through to the old code path
	}


	mmr = waveOutOpen(&hWaveOut, waveId,
						  &wfPCM8K16,
						  0, 0, CALLBACK_NULL);
	if (mmr != MMSYSERR_NOERROR) {
		DEBUGMSG(ZONE_DP,("MapWaveIdToGuid - cannot open wave(%d)\n", waveId));
		return DPR_CANT_OPEN_DEV;
	}
	// now open all the DS devices in turn
	for (pDSInfo = m_pDSInfoList; pDSInfo; pDSInfo = pDSInfo->pNext) {
		hr = (*m_pDirectSoundCreate)(&pDSInfo->guid, &pDS, NULL);
		if (hr != S_OK) {
			pDSInfo->flags |= DSFLAG_ALLOCATED;	// this is a candidate
		} else {
			pDSInfo->flags &= ~DSFLAG_ALLOCATED;
			pDS->Release();
		}
	}
	waveOutClose(hWaveOut);
	hr = DPR_CANT_OPEN_DEV;

	dscaps.dwSize = sizeof(dscaps);
	fEmulFound = FALSE;
	// try opening the DS devices that failed the first time
	for (pDSInfo = m_pDSInfoList; pDSInfo; pDSInfo = pDSInfo->pNext) {
		if (pDSInfo->flags & DSFLAG_ALLOCATED) {
			hr = (*m_pDirectSoundCreate)(&pDSInfo->guid, &pDS, NULL);
			if (hr == S_OK) {
				*pGuid = pDSInfo->guid;
				// get dsound capabilities.
				// NOTE: consider putting the caps in DSINFO if its used often
				pDS->GetCaps(&dscaps);
				pDS->Release();
				DEBUGMSG(ZONE_DP,("mapped waveid %d to DS device(%s)\n", waveId, pDSInfo->pszDescription));
				if (dscaps.dwFlags & DSCAPS_EMULDRIVER)
					fEmulFound = TRUE;	// keep looking in case there's also a native driver
				else
					break;	// native DS driver. Look no further
					
			}
		}
	}
	if (fEmulFound)
		hr = S_OK;
		
	if (hr != S_OK) {
		DEBUGMSG(ZONE_DP,("Cant map id %d to DSound guid!\n", waveId));
		hr = DPR_CANT_OPEN_DEV;
	}

	return hr;
}

HRESULT
DirectSoundMgr::Instance(LPGUID pDeviceGuid,LPDIRECTSOUND *ppDS, HWND hwnd,  WAVEFORMATEX *pwf)
{
	DSINFO *pDSInfo = m_pDSInfoList;
	HRESULT hr;
	DSBUFFERDESC dsBufDesc;
	FX_ENTRY("DirectSoundInstance");

	if (pDeviceGuid == NULL)
		pDeviceGuid = &myNullGuid;
	// search for the Guid in the list
	*ppDS = NULL;

	if (!m_fInitialized)
		Initialize();
		
	while (pDSInfo) {
		if (pDSInfo->guid == *pDeviceGuid)
			break;
		pDSInfo = pDSInfo->pNext;
	}
	ASSERT (pDSInfo);

	if (!pDSInfo || !pDSInfo->pDS) {
		// need to create DS object
		PlaySound(NULL,NULL,0);		// hack to stop system sounds
			
		hr = (*m_pDirectSoundCreate)((*pDeviceGuid==GUID_NULL ? NULL: pDeviceGuid), ppDS, NULL);
		//set priority cooperative level, so we can set the format of the primary buffer.
		if (hr == S_OK 	&& 	(hr = (*ppDS)->SetCooperativeLevel(hwnd,DSSCL_PRIORITY)) == S_OK)
 		{
			if (!pDSInfo) {
				DEBUGMSG(ZONE_DP,("%s: GUID not in List!\n",_fx_));
				// BUGBUG: remove this block. Enumerate should have created the entry (except for NULL guid?)

                DBG_SAVE_FILE_LINE
				pDSInfo = new DSINFO;
				if (pDSInfo) {
					pDSInfo->uRef = 0;
					pDSInfo->guid = *pDeviceGuid;
					pDSInfo->pNext = m_pDSInfoList;
					m_pDSInfoList = pDSInfo;
				} else {
					(*ppDS)->Release();
					return DPR_OUT_OF_MEMORY;
				}
					
			}
			pDSInfo->pDS = *ppDS;
			++pDSInfo->uRef;
			// Create a primary buffer only to set the format
			// (what if its already set?)
			ZeroMemory(&dsBufDesc,sizeof(dsBufDesc));
			dsBufDesc.dwSize = sizeof(dsBufDesc);
			dsBufDesc.dwFlags = DSBCAPS_PRIMARYBUFFER|DSBCAPS_STICKYFOCUS;
			// STICKYFOCUS flags is supposed to preserve the format
			// when the app is not in-focus.
			hr = pDSInfo->pDS->CreateSoundBuffer(&dsBufDesc,&pDSInfo->pDSPrimaryBuf,NULL);
			if (hr == S_OK && pwf) {
				pDSInfo->pDSPrimaryBuf->SetFormat(pwf);
			} else {
				DEBUGMSG (ZONE_DP, ("%s: Create PrimarySoundBuffer failed, hr=0x%lX\r\n", _fx_, hr));
				hr = S_OK;	// Non fatal error
			}
			//DEBUGMSG(ZONE_DP, ("%s: Created Direct Sound object (%s)\n", _fx_,pDSInfo->pszDescription));
		} else {
			DEBUGMSG(ZONE_DP, ("%s: Could not create DS object (%s)\n", _fx_,pDSInfo->pszDescription));

		}
		LOG((LOGMSG_DSCREATE, hr));
	} else {
		*ppDS = pDSInfo->pDS;
		++pDSInfo->uRef;
		hr = S_OK;
	}
				
	return hr;	
}

HRESULT
DirectSoundMgr::ReleaseInstance(LPDIRECTSOUND pDS)
{
	// deref the DS object and release it if necessary
	DSINFO *pDSInfo = m_pDSInfoList;

	while (pDSInfo) {
		if (pDSInfo->pDS == pDS) {
			ASSERT(pDSInfo->uRef > 0);
			if (--pDSInfo->uRef == 0) {
				ULONG uref;
				if (pDSInfo->pDSPrimaryBuf) {
					pDSInfo->pDSPrimaryBuf->Release();
					pDSInfo->pDSPrimaryBuf = NULL;
				}
				uref = pDS->Release();
				pDSInfo->pDS = 0;
				LOG((LOGMSG_DSRELEASE, uref));
				//DEBUGMSG(ZONE_DP, ("Release Direct Sound object (%s) uref=%d\n", pDSInfo->pszDescription, uref));
				// dont bother freeing DSINFO. Its okay
				// to keep it around till the process dies
			}
			break;
		}
		pDSInfo = pDSInfo->pNext;
	}
	return (pDSInfo ? S_OK : DPR_INVALID_PARAMETER);
}


void DSTimeout::TimeoutIndication()
{
	ASSERT(m_pRDSStream);
	m_pRDSStream->RecvTimeout();
}


HRESULT STDMETHODCALLTYPE RecvDSAudioStream::QueryInterface(REFIID iid, void **ppVoid)
{
	// resolve duplicate inheritance to the SendMediaStream;

	extern IID IID_IProperty;

	if (iid == IID_IUnknown)
	{
		*ppVoid = (IUnknown*)((RecvMediaStream*)this);
	}
	else if (iid == IID_IMediaChannel)
	{
		*ppVoid = (IMediaChannel*)((RecvMediaStream *)this);
	}
	else if (iid == IID_IAudioChannel)
	{
		*ppVoid = (IAudioChannel*)this;
	}
	else if (iid == IID_IProperty)
	{
		*ppVoid = NULL;
		ERROR_OUT(("Don't QueryInterface for IID_IProperty, use IMediaChannel"));
		return E_NOINTERFACE;
	}
	else
	{
		*ppVoid = NULL;
		return E_NOINTERFACE;
	}
	AddRef();

	return S_OK;

}

ULONG STDMETHODCALLTYPE RecvDSAudioStream::AddRef(void)
{
	return InterlockedIncrement(&m_lRefCount);
}

ULONG STDMETHODCALLTYPE RecvDSAudioStream::Release(void)
{
	LONG lRet;

	lRet = InterlockedDecrement(&m_lRefCount);

	if (lRet == 0)
	{
		delete this;
		return 0;
	}

	else
		return lRet;

}



HRESULT
RecvDSAudioStream::Initialize( DataPump *pDP)
{
	HRESULT hr = DPR_OUT_OF_MEMORY;
	DWORD dwFlags =  DP_FLAG_ACM| DP_FLAG_DIRECTSOUND  | DP_FLAG_HALF_DUPLEX | DP_FLAG_AUTO_SWITCH ;
	MEDIACTRLINIT mcInit;
	FX_ENTRY ("RecvDSAudioStream::Initialize")

	InitializeCriticalSection(&m_crsAudQoS);

	// enable Recv by default
	m_DPFlags = dwFlags | DPFLAG_ENABLE_RECV;
	// store a back pointer to the datapump container
	m_pDP = pDP;
	m_Net = NULL;
	m_dwSrcSize = 0;
	m_pIRTPRecv = NULL;
	m_nFailCount = 0;
	m_bJammed = FALSE;
	m_bCanSignalOpen = TRUE;

	

	// Initialize data (should be in constructor)
	m_DSguid = GUID_NULL;	// use default device

	// Create decode audio filters
	m_hStrmConv = NULL; // replaced by AcmFilter

    DBG_SAVE_FILE_LINE
	m_pAudioFilter = new AcmFilter;
	if (!m_pAudioFilter)
	{
		DEBUGMSG (ZONE_DP, ("%s: AcmManager new failed\r\n", _fx_));
		goto FilterAllocError;
	}

	ZeroMemory (&m_StrmConvHdr, sizeof (ACMSTREAMHEADER));


	// determine if the wave devices are available
	if (waveOutGetNumDevs()) m_DPFlags |= DP_FLAG_PLAY_CAP;
	

	m_DPFlags |= DPFLAG_INITIALIZED;

	UPDATE_REPORT_ENTRY(g_prptSystemSettings, 1, REP_SYS_AUDIO_DSOUND);
	RETAILMSG(("NAC: Audio Subsystem: DirectSound"));

	return DPR_SUCCESS;


FilterAllocError:
	if (m_pAudioFilter) delete m_pAudioFilter;

	ERRORMESSAGE( ("%s: exit, hr=0x%lX\r\n", _fx_, hr));

	return hr;
}

RecvDSAudioStream::~RecvDSAudioStream()
{

	if (m_DPFlags & DPFLAG_INITIALIZED) {
		m_DPFlags &= ~DPFLAG_INITIALIZED;
	
		if (m_DPFlags & DPFLAG_CONFIGURED_RECV)
			UnConfigure();

		if (m_pIRTPRecv)
		{
			m_pIRTPRecv->Release();
			m_pIRTPRecv = NULL;
		}

		if (m_pAudioFilter)
			delete m_pAudioFilter;

		m_pDP->RemoveMediaChannel(MCF_RECV|MCF_AUDIO, (IMediaChannel*)(RecvMediaStream*)this);

	}
	DeleteCriticalSection(&m_crsAudQoS);
}

extern UINT ChoosePacketSize(WAVEFORMATEX *pwf);
extern UINT g_MaxAudioDelayMs;
extern UINT g_MinWaveAudioDelayMs;
extern UINT g_MinDSEmulAudioDelayMs; // emulated DS driver delay


HRESULT STDMETHODCALLTYPE RecvDSAudioStream::Configure(
	BYTE *pFormat,
	UINT cbFormat,
	BYTE *pChannelParams,
	UINT cbParams,
	IUnknown *pUnknown)
{
	HRESULT hr=E_FAIL;
	BOOL fRet;
	DWORD dwMaxDecompressedSize;
	UINT cbSamplesPerPkt;
	DWORD dwPropVal;
	DWORD dwFlags;
	UINT uAudioCodec;
	AUDIO_CHANNEL_PARAMETERS audChannelParams;
	UINT ringSize = MAX_RXRING_SIZE;
	WAVEFORMATEX *pwfRecv;
	UINT maxRingSamples;
	MMRESULT mmr;

	FX_ENTRY ("RecvDSAudioStream::Configure")

//	m_Net = pNet;


	if (m_DPFlags & DPFLAG_STARTED_RECV)
	{
		return DPR_IO_PENDING; // anything better to return
	}

	if (m_DPFlags & DPFLAG_CONFIGURED_RECV)
	{
		DEBUGMSG(ZONE_DP, ("Stream Re-Configuration - calling UnConfigure"));
		UnConfigure();  // a re-configure will release the RTP object, need to call SetNetworkInterface again
	}


	if ((NULL == pFormat) ||
		(NULL == pChannelParams) ||
		(cbParams != sizeof(audChannelParams)) ||
		(cbFormat < sizeof(WAVEFORMATEX)) )

	{
		return DPR_INVALID_PARAMETER;
	}

	audChannelParams = *(AUDIO_CHANNEL_PARAMETERS *)pChannelParams;
	pwfRecv = (WAVEFORMATEX *)pFormat;

	if (! (m_DPFlags & DPFLAG_INITIALIZED))
		return DPR_OUT_OF_MEMORY;		//BUGBUG: return proper error;
		
//	if (m_Net)
//	{
//		hr = m_Net->QueryInterface(IID_IRTPRecv, (void **)&m_pIRTPRecv);
//		if (!SUCCEEDED(hr))
//			return hr;
//	}

	AcmFilter::SuggestDecodeFormat(pwfRecv, &m_fDevRecv);
	
	UPDATE_REPORT_ENTRY(g_prptCallParameters, pwfRecv->wFormatTag, REP_RECV_AUDIO_FORMAT);
	UPDATE_REPORT_ENTRY(g_prptCallParameters, pwfRecv->nSamplesPerSec, REP_RECV_AUDIO_SAMPLING);
	UPDATE_REPORT_ENTRY(g_prptCallParameters, pwfRecv->nAvgBytesPerSec*8, REP_RECV_AUDIO_BITRATE);
	RETAILMSG(("NAC: Audio Recv Format: %s", (pwfRecv->wFormatTag == 66) ? "G723.1" : (pwfRecv->wFormatTag == 112) ? "LHCELP" : (pwfRecv->wFormatTag == 113) ? "LHSB08" : (pwfRecv->wFormatTag == 114) ? "LHSB12" : (pwfRecv->wFormatTag == 115) ? "LHSB16" : (pwfRecv->wFormatTag == 6) ? "MSALAW" : (pwfRecv->wFormatTag == 7) ? "MSULAW" : (pwfRecv->wFormatTag == 130) ? "MSRT24" : "??????"));
	RETAILMSG(("NAC: Audio Recv Sampling Rate (Hz): %ld", pwfRecv->nSamplesPerSec));
	RETAILMSG(("NAC: Audio Recv Bitrate (w/o network overhead - bps): %ld", pwfRecv->nAvgBytesPerSec*8));
	// note that parameters such as samples/packet are channel specific

	cbSamplesPerPkt = audChannelParams.ns_params.wFrameSize
		*audChannelParams.ns_params.wFramesPerPkt;

	// turn on receive silence detection only if the sender is not using
	// silence suppression
	if (!audChannelParams.ns_params.UseSilenceDet)
		m_DPFlags |= DP_FLAG_AUTO_SILENCE_DETECT;	
	else
		m_DPFlags &= ~DP_FLAG_AUTO_SILENCE_DETECT;
	UPDATE_REPORT_ENTRY(g_prptCallParameters, cbSamplesPerPkt, REP_RECV_AUDIO_PACKET);
	RETAILMSG(("NAC: Audio Recv Packetization (ms/packet): %ld", pwfRecv->nSamplesPerSec ? cbSamplesPerPkt * 1000UL / pwfRecv->nSamplesPerSec : 0));
	INIT_COUNTER_MAX(g_pctrAudioReceiveBytes, (pwfRecv->nAvgBytesPerSec * 8 + pwfRecv->nSamplesPerSec * (sizeof(RTP_HDR) + IP_HEADER_SIZE + UDP_HEADER_SIZE) / cbSamplesPerPkt) << 3);


	// make the ring buffer size large enought to hold 4 seconds of audio
	// This seems to be suitable for congested networks, in which
	// packets can get delayed and them for many to suddelnly arrive at once
	maxRingSamples = (pwfRecv->nSamplesPerSec * MIN_DSBUF_SIZE)/1000;


	// describe the DirectSound buffer
	
	ZeroMemory(&m_DSBufDesc,sizeof(m_DSBufDesc));
	m_DSBufDesc.dwSize = sizeof (m_DSBufDesc);
	m_DSBufDesc.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2;
	m_DSBufDesc.dwBufferBytes = maxRingSamples * (m_fDevRecv.wBitsPerSample/8);
	m_DSBufDesc.dwReserved = 0;
	m_DSBufDesc.lpwfxFormat = &m_fDevRecv;
	
	m_pDS = NULL;
	m_pDSBuf = NULL;

	
	// Initialize the recv-stream filter manager object
	dwMaxDecompressedSize = cbSamplesPerPkt * (m_fDevRecv.nBlockAlign);


	mmr = m_pAudioFilter->Open(pwfRecv, &m_fDevRecv);
	if (mmr != 0)
	{
		DEBUGMSG (ZONE_DP, ("%s: AcmFilter->Open failed, mmr=%d\r\n", _fx_, mmr));
		hr = DPR_CANT_OPEN_CODEC;
		goto RecvFilterInitError;
	}

	
	// set up the decode buffer
	m_pAudioFilter->SuggestSrcSize(dwMaxDecompressedSize, &m_dwSrcSize);

	ZeroMemory (&m_StrmConvHdr, sizeof (ACMSTREAMHEADER));
	m_StrmConvHdr.cbStruct = sizeof (ACMSTREAMHEADER);

    DBG_SAVE_FILE_LINE
	m_StrmConvHdr.pbSrc = new BYTE[m_dwSrcSize];
	m_StrmConvHdr.cbSrcLength = m_dwSrcSize;  // may change for variable bit rate codecs

    DBG_SAVE_FILE_LINE
	m_StrmConvHdr.pbDst = new BYTE[dwMaxDecompressedSize];
	m_StrmConvHdr.cbDstLength = dwMaxDecompressedSize;

	mmr = m_pAudioFilter->PrepareHeader(&m_StrmConvHdr);
	if (mmr != MMSYSERR_NOERROR)
	{
		DEBUGMSG (ZONE_DP, ("%s: AcmFilter->Open failed, mmr=%d\r\n", _fx_, mmr));
		hr = DPR_CANT_OPEN_CODEC;
		goto RecvFilterInitError;
	}
	
	// Initialize the recv stream
	m_BufSizeT = BYTESTOSAMPLES(m_DSBufDesc.dwBufferBytes);
	m_fEmpty = TRUE;

	m_MinDelayT = 0;
	m_MaxDelayT = g_MaxAudioDelayMs * m_fDevRecv.nSamplesPerSec /1000;
	m_ArrT = m_ArrivalT0 = 0;
	m_ScaledAvgVarDelay = 0;

	m_DelayT = m_MinDelayT;

	m_SilenceDurationT = 0;


	InitAudioFlowspec(&m_flowspec, pwfRecv, m_dwSrcSize);

	m_DPFlags |= DPFLAG_CONFIGURED_RECV;

	return DPR_SUCCESS;

RecvFilterInitError:
	if (m_pIRTPRecv)
	{
		m_pIRTPRecv->Release();
		m_pIRTPRecv = NULL;
	}

	m_pAudioFilter->Close();

	delete [] m_StrmConvHdr.pbSrc;
	delete [] m_StrmConvHdr.pbDst;
	m_StrmConvHdr.pbSrc=NULL;
	m_StrmConvHdr.pbDst = NULL;


	ERRORMESSAGE(("%s:  failed, hr=0%u\r\n", _fx_, hr));
	return hr;
}


void RecvDSAudioStream::UnConfigure()
{

	if ((m_DPFlags & DPFLAG_CONFIGURED_RECV))
	{
		Stop();
		// Close the RTP state if its open
		m_Net = NULL;

		// release DS buffer and DS object
		//ReleaseDSBuffer();
		ASSERT(!m_pDSBuf);	// released in StopRecv()

		// Close the filters
		m_StrmConvHdr.cbSrcLength = m_dwSrcSize;
		m_pAudioFilter->UnPrepareHeader(&m_StrmConvHdr);
		m_pAudioFilter->Close();

		delete [] m_StrmConvHdr.pbSrc;
		delete [] m_StrmConvHdr.pbDst;
		m_StrmConvHdr.pbSrc=NULL;
		m_StrmConvHdr.pbDst = NULL;

		m_nFailCount = 0;
		m_bJammed = FALSE;
		m_bCanSignalOpen = TRUE;

		// Close the receive streams
		//m_RecvStream->Destroy();
        m_DPFlags &= ~(DPFLAG_CONFIGURED_RECV);
	}
}


HRESULT
RecvDSAudioStream::Start()
{
	HRESULT hr;
	IMediaChannel *pISendAudio;
	BOOL fStoppedRecording;
	FX_ENTRY ("RecvDSAudioStream::Start");
	
	if (m_DPFlags & DPFLAG_STARTED_RECV)
		return DPR_SUCCESS;
	// TODO: remove this check once audio UI calls the IComChan PAUSE_RECV prop
	if (!(m_DPFlags & DPFLAG_ENABLE_RECV))
		return DPR_SUCCESS;

	if ((!(m_DPFlags & DPFLAG_CONFIGURED_RECV)) || (!m_pIRTPRecv))
		return DPR_NOT_CONFIGURED;

	ASSERT(!m_hRenderingThread );
	m_ThreadFlags &= ~(DPTFLAG_STOP_PLAY|DPTFLAG_STOP_RECV);

	SetFlowSpec();

	pISendAudio = NULL;
	fStoppedRecording = FALSE;
	if (!(m_DPFlags & DP_FLAG_HALF_DUPLEX))
	{
	// make sure the recording device is closed before creating the DS object
	// Why ? Because SoundBlaster either sounds lousy or doesnt work at all if
	// you open waveIn before  waveOut or DirectSound.
		m_pDP->GetMediaChannelInterface(MCF_AUDIO|MCF_SEND, &pISendAudio);
		if (pISendAudio && pISendAudio->GetState()== MSSTATE_STARTED
		&& pISendAudio->Stop() == S_OK)
		{
			fStoppedRecording = TRUE;
			DEBUGMSG(ZONE_DP,("%s:Stopped Recording\n",_fx_));
		}
	}
	
	// Start receive thread. This will create the DSound object
    m_pDP->StartReceiving(this);

    if (pISendAudio) {
    	if (fStoppedRecording)
    		pISendAudio->Start();
    	pISendAudio->Release();
    }

    m_DPFlags |= DPFLAG_STARTED_RECV;
	return DPR_SUCCESS;
}

// LOOK: Identical to RecvVideoStream version.
HRESULT
RecvDSAudioStream::Stop()
{
	
	
	FX_ENTRY ("RecvDSAudioStream::Stop");

	if(!(m_DPFlags &  DPFLAG_STARTED_RECV))
	{
		return DPR_SUCCESS;
	}

	m_ThreadFlags = m_ThreadFlags  |
		DPTFLAG_STOP_RECV |  DPTFLAG_STOP_PLAY ;

	// delink from receive thread
	m_pDP->StopReceiving(this);

	if (m_pDSBuf)
		m_pDSBuf->Stop();
	
    //This is per channel, but the variable is "DPFlags"
	m_DPFlags &= ~DPFLAG_STARTED_RECV;
	
	return DPR_SUCCESS;
}

//  IProperty::GetProperty / SetProperty
//  (DataPump::MediaChannel::GetProperty)
//      Properties of the MediaChannel. Supports properties for both audio
//      and video channels.

STDMETHODIMP
RecvDSAudioStream::GetProperty(
	DWORD prop,
	PVOID pBuf,
	LPUINT pcbBuf
    )
{
	HRESULT hr = DPR_SUCCESS;
	RTP_STATS RTPStats;
	DWORD dwValue;
	UINT len = sizeof(DWORD);	// most props are DWORDs

	if (!pBuf || *pcbBuf < len)
    {
		*pcbBuf = len;
		return DPR_INVALID_PARAMETER;
	}

	switch (prop)
    {
	case PROP_RECV_AUDIO_STRENGTH:
		{
			return GetSignalLevel((UINT*)pBuf);
		}

#ifdef OLDSTUFF
	case PROP_NET_RECV_STATS:
		if (m_Net && *pcbBuf >= sizeof(RTP_STATS))
        {
			m_Net->GetRecvStats((RTP_STATS *)pBuf);
			*pcbBuf = sizeof(RTP_STATS);
		} else
			hr = DPR_INVALID_PROP_VAL;
			
		break;
#endif
	//case PROP_VOLUME:

	case PROP_DUPLEX_TYPE:
		
		if(m_DPFlags & DP_FLAG_HALF_DUPLEX)
			*(DWORD*)pBuf = DUPLEX_TYPE_HALF;
		else
			*(DWORD*)pBuf =	DUPLEX_TYPE_FULL;
		break;

	case PROP_WAVE_DEVICE_TYPE:
		*(DWORD*)pBuf = m_DPFlags & DP_MASK_WAVE_DEVICE;
		break;
	case PROP_PLAY_ON:
		*(DWORD *)pBuf = (m_ThreadFlags & DPFLAG_ENABLE_RECV)!=0;
		break;
	case PROP_PLAYBACK_DEVICE:
		*(DWORD *)pBuf = m_RenderingDevice;
		break;

	case PROP_VIDEO_AUDIO_SYNC:
		*(DWORD*)pBuf = ((m_DPFlags & DPFLAG_AV_SYNC) != 0);
		break;
	
	default:
		hr = DPR_INVALID_PROP_ID;
		break;
	}
	return hr;
}


// low order word is the signal strength
// high order work contains bits to indicate status
// (0x01 - transmitting)
// (0x02 - audio device is jammed)
STDMETHODIMP RecvDSAudioStream::GetSignalLevel(UINT *pSignalStrength)
{
	DWORD dwLevel;
	DWORD dwJammed;

	if ((!(m_DPFlags & DPFLAG_STARTED_RECV)) || (m_fEmpty) ||
		  (m_ThreadFlags & DPTFLAG_PAUSE_RECV) )
	{
		dwLevel = 0;
	}
	else
	{
		dwLevel = GetSignalStrength();
		dwLevel = LogScale[(dwLevel >> 8) & 0x00ff];

		if (m_bJammed)
		{
			dwLevel |= (2 << 16);
		}

		dwLevel |= (1 << 16);
	}
	*pSignalStrength = dwLevel;
	return S_OK;
};






DWORD
RecvDSAudioStream::GetSignalStrength()
{
	BYTE bMax, bMin, *pb;
	short sMax, sMin, *ps;
	UINT cbSize;
	DWORD dwMaxStrength = 0;
	cbSize = m_StrmConvHdr.cbDstLengthUsed;
	if (cbSize==0)
		return 0;
	switch (m_fDevRecv.wBitsPerSample)
	{
	case 8: // unsigned char

		pb = (PBYTE) (m_StrmConvHdr.pbDst);

		bMax = 0;
		bMin = 255;

		for ( ; cbSize; cbSize--, pb++)
		{
			if (*pb > bMax) bMax = *pb;
			if (*pb < bMin) bMin = *pb;
		}
	
			// 2^9 <-- 2^16 / 2^7
		dwMaxStrength = ((DWORD) (bMax - bMin)) << 8;
		break;

	case 16: // (signed) short

		ps = (short *) (m_StrmConvHdr.pbDst);
		cbSize = m_StrmConvHdr.cbDstLengthUsed;

		sMax = sMin = 0;

		for (cbSize >>= 1; cbSize; cbSize--, ps++)
		{
			if (*ps > sMax) sMax = *ps;
			if (*ps < sMin) sMin = *ps;
		}
	
		dwMaxStrength = (DWORD) (sMax - sMin); // drop sign bit
		break;

	}
	return dwMaxStrength;
}


STDMETHODIMP
RecvDSAudioStream::SetProperty(
	DWORD prop,
	PVOID pBuf,
	UINT cbBuf
    )
{
	DWORD dw;
	HRESULT hr = S_OK;
	
	if (cbBuf < sizeof (DWORD))
		return DPR_INVALID_PARAMETER;

	switch (prop)
    {
	//case PROP_VOLUME:
		

	case PROP_DUPLEX_TYPE:
		ASSERT(0);  // dead code for this case type;
		break;
		
	case DP_PROP_DUPLEX_TYPE:
		// internal version, called by DataPump::SetDuplexMode() after ensuring streams are stopped
		dw = *(DWORD *)pBuf;
		if (dw & DP_FLAG_HALF_DUPLEX)
			m_DPFlags |= DP_FLAG_HALF_DUPLEX;
		else
			m_DPFlags &= ~DP_FLAG_HALF_DUPLEX;
		break;
		

	case PROP_PLAY_ON:
	{

		if (*(DWORD *)pBuf)   // unmute
		{
			m_ThreadFlags &= ~DPTFLAG_PAUSE_RECV;
		}
		else  // mute
		{
			m_ThreadFlags |= DPTFLAG_PAUSE_RECV;
		}
	
//		DWORD flag =  DPFLAG_ENABLE_RECV;
//		if (*(DWORD *)pBuf) {
//			m_DPFlags |= flag; // set the flag
//			hr = Start();
//		}
//		else
//		{
//			m_DPFlags &= ~flag; // clear the flag
//			hr = Stop();
//		}

		RETAILMSG(("NAC: RecvAudioStream: %s", *(DWORD*)pBuf ? "Enabling":"Disabling"));
		break;
	}	
	case PROP_PLAYBACK_DEVICE:
		m_RenderingDevice = *(DWORD*)pBuf;
		RETAILMSG(("NAC: Setting default playback device to %d", m_RenderingDevice));
		if (m_RenderingDevice != WAVE_MAPPER)
			hr = DirectSoundMgr::MapWaveIdToGuid(m_RenderingDevice,&m_DSguid);
		break;

    case PROP_VIDEO_AUDIO_SYNC:
		if (*(DWORD *)pBuf)
    		m_DPFlags |= DPFLAG_AV_SYNC;
		else
			m_DPFlags &= ~DPFLAG_AV_SYNC;
    	break;

	default:
		return DPR_INVALID_PROP_ID;
		break;
	}
	return hr;
}

HRESULT
RecvDSAudioStream::GetCurrentPlayNTPTime(NTP_TS *pNtpTime)
{
	DWORD rtpTime;
#ifdef OLDSTUFF
	if ((m_DPFlags & DPFLAG_STARTED_RECV) && m_fReceiving) {
		if (m_Net->RTPtoNTP(m_PlaybackTimestamp,pNtpTime))
			return S_OK;
	}
#endif
	return 0xff;	// return proper error
		
}

BOOL RecvDSAudioStream::IsEmpty() {
	// check if anything in DSBuffer or in decode buffer
	return (m_fEmpty && !(m_StrmConvHdr.dwDstUser & BUFFER_RECEIVED));
}

/*
	Called by the recv thread to setup the stream for receiving.
	Post the initial recv buffer(s). Subsequently, the buffers are posted
	in the RTPRecvCallback()
*/
HRESULT
RecvDSAudioStream::StartRecv(HWND hWnd)
{
	HRESULT hr = S_OK;
	DWORD dwPropVal = 0;
	FX_ENTRY ("RecvDSAudioStream::StartRecv");
	
	if ((!(m_ThreadFlags & DPTFLAG_STOP_RECV) ) && (m_DPFlags  & DPFLAG_CONFIGURED_RECV)){
		if (!(m_DPFlags & DP_FLAG_HALF_DUPLEX) && !m_pDSBuf) {
		// Create the DS object only if its full-duplex
		// In the half-duplex case the DSbuffer is created
		// when the first packet is received
		// only  reason its here is so that SetDuplexMode can take effect right away
		// BUGBUG: opening waveIn before DS causes death of the waveOut on Memphis!!
			hr = CreateDSBuffer();
			
			if (hr  != S_OK) {
				DEBUGMSG (ZONE_DP, ("%s: CreateSoundBuffer create failed, hr=0x%lX\r\n", _fx_, hr));
				return hr;
			}
		}
		if (m_pDSBuf)
			hr = m_pDSBuf->Play(0,0,DSBPLAY_LOOPING);

//		m_RecvFilter->GetProp (FM_PROP_SRC_SIZE, &dwPropVal);
		//hr = m_Net->SetRecvNotification(&RTPRecvDSCallback, (DWORD)this, 2, dwPropVal, hWnd);	// for WS1 only
		hr =m_pIRTPRecv->SetRecvNotification(&RTPRecvCallback,(DWORD_PTR)this, 2);
			
		
	}
	return hr;
}

/*
	Called by the recv thread to suspend receiving  on this RTP session
	If there are outstanding receive buffers they have to be recovered
*/

HRESULT
RecvDSAudioStream::StopRecv()
{
	// dont recv on this stream
	m_pIRTPRecv->CancelRecvNotification();

	// cancel any pending timeout. (its okay if it wasnt scheduled )
	m_pDP->m_RecvTimer.CancelTimeout(&m_TimeoutObj);

	// Release DirectSound object
	ReleaseDSBuffer();

	return S_OK;		
}

/*
	Create a DirectSound object and a DirectSound secondary buffer.
	This routine is called after the stream is configured, so the wave format has been set
	and the DSBUFFERDESC struct has been initialized.
*/
HRESULT
RecvDSAudioStream::CreateDSBuffer()
{
	HRESULT hr;
	HWAVEOUT hwo=NULL;
	DSCAPS dscaps;
	FX_ENTRY ("RecvDSAudioStream::CreateDSBuffer");

	ASSERT(!m_pDSBuf);
	if (m_DPFlags & DP_FLAG_HALF_DUPLEX) {
		DWORD dwStatus;
		// Got to take the half duplex event
		// BUGBUG: this method wont cut it if there is more than one send and one recv stream
		dwStatus = WaitForSingleObject(g_hEventHalfDuplex, 0);
		if (dwStatus != WAIT_OBJECT_0)
			return DPR_CANT_OPEN_DEV;
	}
	//	Stop any high level ("PlaySound()") usage of wave device.
	
	// Create the direct sound object (if necessary)
	hr = DirectSoundMgr::Instance(m_RenderingDevice==WAVE_MAPPER ? NULL: &m_DSguid, &m_pDS, m_pDP->m_hAppWnd, &m_fDevRecv);

	if (hr == S_OK)
	{
		hr = m_pDS->CreateSoundBuffer(&m_DSBufDesc,&m_pDSBuf,NULL);
		if (hr == DSERR_INVALIDPARAM)
		{
			// if global focus (DX3) is not supported, try sticky focus
			m_DSBufDesc.dwFlags ^= (DSBCAPS_GLOBALFOCUS|DSBCAPS_STICKYFOCUS);
			hr = m_pDS->CreateSoundBuffer(&m_DSBufDesc,&m_pDSBuf,NULL);
		}
		m_PlayPosT = 0;		// DS play position is initially at the start of the buffer

		if (hr != S_OK)
		{
			DEBUGMSG (ZONE_DP, ("%s: CreateSoundBuffer create failed, hr=0x%lX\r\n", _fx_, hr));

			m_nFailCount++;
			if (m_nFailCount == MAX_FAILCOUNT)
			{
				m_pDP->StreamEvent(MCF_RECV, MCF_AUDIO, STREAM_EVENT_DEVICE_FAILURE, 0);
				m_bJammed = TRUE;
				m_bCanSignalOpen = TRUE;
			}
		}

		dscaps.dwSize = sizeof(dscaps);
		dscaps.dwFlags = 0;
		m_pDS->GetCaps(&dscaps);	// get DirectSound object attributes
		m_DSFlags = dscaps.dwFlags;

		if (m_DSFlags & DSCAPS_EMULDRIVER)
		{
			// use g_MinDSEmulAudioDelay since this is the emulated driver
			m_MinDelayT = (m_fDevRecv.nSamplesPerSec * g_MinDSEmulAudioDelayMs) / 1000;
			m_DelayT = m_MinDelayT;
		};
	}

	else
	{
		DEBUGMSG (ZONE_DP, ("%s: DirectSound create failed, hr=0x%lX\r\n", _fx_, hr));

		m_nFailCount++;
		if (m_nFailCount == MAX_FAILCOUNT)
		{
			m_pDP->StreamEvent(MCF_RECV, MCF_AUDIO, STREAM_EVENT_DEVICE_FAILURE, 0);
			m_bJammed = TRUE;
			m_bCanSignalOpen = TRUE;
		}
	}


	if (hr == S_OK)
	{
		if (m_DPFlags & DPFLAG_STARTED_RECV)
		{
			m_pDSBuf->Play(0,0,DSBPLAY_LOOPING);
		}

		if (m_bCanSignalOpen)
		{
			m_pDP->StreamEvent(MCF_RECV, MCF_AUDIO, STREAM_EVENT_DEVICE_OPEN, 0);
			m_bCanSignalOpen = FALSE; // don't signal open condition anymore
		}

		m_bJammed = FALSE;
		m_nFailCount = 0;
	}
	else
	{
		ReleaseDSBuffer();
	}
	return hr;
}

HRESULT
RecvDSAudioStream::ReleaseDSBuffer()
{
	m_fEmpty = TRUE;
	if (m_pDSBuf) {
		ULONG uref;
		uref = m_pDSBuf->Release();
		m_pDSBuf = NULL;
		//DEBUGMSG(ZONE_DP,("Releasing DirectSound buffer (%d)\n", uref));
	}
	if (m_pDS) {
		DirectSoundMgr::ReleaseInstance(m_pDS);
		m_pDS = NULL;
		if (m_DPFlags & DP_FLAG_HALF_DUPLEX)
			SetEvent(g_hEventHalfDuplex);
	}
	return S_OK;
		
}

HRESULT
RecvDSAudioStream::Decode(UCHAR *pData, UINT cbData)
{
	MMRESULT mmr;
	HRESULT hr=S_OK;
	FX_ENTRY ("RecvDSAudioStream::Decode");
	UINT uDstLength;


	if (m_dwSrcSize < cbData)
	{
		DEBUGMSG (ZONE_DP, ("%s: RecvDSAudioStream::Decode failed - buffer larger than expected\r\n", _fx_));
		return DPR_CONVERSION_FAILED;
	}

	CopyMemory(m_StrmConvHdr.pbSrc, pData, cbData);
	m_StrmConvHdr.cbSrcLength = cbData;
	mmr = m_pAudioFilter->Convert(&m_StrmConvHdr);

	if (mmr != MMSYSERR_NOERROR)
	{
		DEBUGMSG (ZONE_DP, ("%s: acmStreamConvert failed, mmr=%ld\r\n", _fx_, (ULONG) mmr));
		hr = DPR_CONVERSION_FAILED;
	}
	else
	{
		m_StrmConvHdr.dwDstUser = BUFFER_RECEIVED;	// buffer is ready to play
		// if receive side silence detection is turned on,
		// check decoded buffer signal level
		if (m_DPFlags & DP_FLAG_AUTO_SILENCE_DETECT)
		{
			if (m_AudioMonitor.SilenceDetect((WORD) GetSignalStrength()))
			{
				m_StrmConvHdr.dwDstUser = BUFFER_SILENT;
			}
		}
	}

	return hr;
	// end
}

// insert the decoded buf at the appropriate location in the DirectSound buffer
HRESULT
RecvDSAudioStream::PlayBuf(DWORD timestamp, UINT seq, BOOL fMark)
{
	UINT lenT = BYTESTOSAMPLES(m_StrmConvHdr.cbDstLengthUsed);
	DWORD curPlayPosT, curWritePosT, curWriteLagT;
	LPVOID p1, p2;
	DWORD cb1, cb2;
	HRESULT hr;
	DWORD dwDSStatus = 0;

	/*
	All of the following are expressed in samples:
	m_NextTimeT is timestamp of next expected packet. Usually timestamp equals m_NextT
	m_BufSizeT is the total buffer size in samples.
	m_NextPosT is the write position corresponding to m_NextT.
	m_PlayPosT is the current play position
	m_DelayT is the ideal playback delay
	*/

	LOG((LOGMSG_DSTIME, GetTickCount()));
	LOG((LOGMSG_DSENTRY, timestamp, seq, fMark));

	m_pDSBuf->GetCurrentPosition(&curPlayPosT,&curWritePosT);
	curPlayPosT = BYTESTOSAMPLES(curPlayPosT);	
	curWritePosT = BYTESTOSAMPLES(curWritePosT);
	m_pDSBuf->GetStatus(&dwDSStatus);

	if (!m_fEmpty)
	{
		// wasn't empty last time we checked but is it empty now?
		if (QMOD(curPlayPosT-m_PlayPosT, m_BufSizeT) > QMOD(m_NextPosT-m_PlayPosT, m_BufSizeT))
		{
			// play cursor has advanced beyond the last written byte
			m_fEmpty = TRUE;
			LOG((LOGMSG_DSEMPTY, curPlayPosT, m_PlayPosT, m_NextPosT));
		}
		// write silence into the part of the buffer that just played
		hr = m_pDSBuf->Lock(SAMPLESTOBYTES(m_PlayPosT),SAMPLESTOBYTES(QMOD(curPlayPosT-m_PlayPosT, m_BufSizeT)), &p1, &cb1, &p2, &cb2, 0);
		if (hr == S_OK)
		{
			ZeroMemory(p1,cb1);
			if (cb2)
				ZeroMemory(p2,cb2);
			m_pDSBuf->Unlock(p1,cb1,p2,cb2);
		}
	}
	hr = S_OK;	
	
	// calculate minimum write-behind margin.
	// This is low for native sound drivers and high for emulated drivers, so , assuming it's accurate
	// there's no need to distinguish between emulated and native drivers.
	curWriteLagT = QMOD(curWritePosT-curPlayPosT, m_BufSizeT);


	if (m_fEmpty)
	{
		// the DS buffer only has silence in it. In this state, m_NextPosT and m_NextTimeT are irrelevant.
		// We get to put the new buffer wherever we choose, so we put it m_DelayT after the current write position.
		curWritePosT = QMOD(curWritePosT+m_DelayT, m_BufSizeT);
		
	}
	else
	{
	
		if (TS_EARLIER(timestamp, m_NextTimeT))
			hr = DPR_OUT_OF_SEQUENCE;	// act dumb and discard misordered packets
		else
		{
			UINT curDelayT = QMOD(m_NextPosT - curPlayPosT, m_BufSizeT);
			if (fMark)
			{
				// we have some leeway in choosing the insertion point, because this is the start of a talkspurt
				if (curDelayT > m_DelayT + curWriteLagT)
				{
					// put it right after the last sample
					curWritePosT = m_NextPosT;
				}
				else
				{
					// put it m_DelayT after the current write position
					curWritePosT = QMOD(curWritePosT+m_DelayT, m_BufSizeT);
				}
			}
			else
			{
				// bytes in
				if ((timestamp-m_NextTimeT + curDelayT) < m_BufSizeT)
				{
					curWritePosT = QMOD(m_NextPosT +timestamp-m_NextTimeT, m_BufSizeT);
				}
				else
				{
					// overflow!! Could either dump whats in buffer or dump the packet
					// dumping the packet is easier for now
					hr = DPR_OUT_OF_SEQUENCE;
				}
			}
		}
	}
	if ((dwDSStatus & DSBSTATUS_PLAYING) && (seq != INVALID_RTP_SEQ_NUMBER))
		UpdateVariableDelay(timestamp,curPlayPosT );
	// When receive silence detection is enabled:
    // dont play the packet if we have received at least a quarter second of silent packets.
    // This will enable switch to talk (in half-duplex mode).
	if (m_StrmConvHdr.dwDstUser == BUFFER_SILENT)
		m_SilenceDurationT += lenT;
	else
		m_SilenceDurationT = 0;	
		
	if (hr == S_OK && m_SilenceDurationT < m_fDevRecv.nSamplesPerSec/4)
	{
		LOG((LOGMSG_DSPLAY,curPlayPosT, curWritePosT, lenT));
		// check if we have space for the whole packet
		if (QMOD(curWritePosT-curPlayPosT, m_BufSizeT) > m_BufSizeT - lenT)
		{
			// no
			curPlayPosT = QMOD(curWritePosT + lenT + 1000, m_BufSizeT);
			hr = m_pDSBuf->SetCurrentPosition(SAMPLESTOBYTES(curPlayPosT));
			LOG((LOGMSG_DSMOVPOS,curPlayPosT, hr));
		}
		
		hr = m_pDSBuf->Lock(SAMPLESTOBYTES(curWritePosT),m_StrmConvHdr.cbDstLengthUsed, &p1, &cb1, &p2, &cb2, 0);
		if (hr == S_OK)
		{
			CopyMemory(p1, m_StrmConvHdr.pbDst, cb1);
			if (cb2)
				CopyMemory(p2, m_StrmConvHdr.pbDst+cb1, cb2);
			m_pDSBuf->Unlock(p1,cb1,p2,cb2);

			m_fEmpty = FALSE;
		}
		else
		{
			DEBUGMSG(ZONE_DP,("DirectSoundBuffer->Lock failed with %x\n",hr));
		}
		m_StrmConvHdr.dwDstUser = 0;	// to indicate that the decode buffer is empty again
		m_NextTimeT = timestamp + lenT;
		m_NextPosT = QMOD(curWritePosT+lenT, m_BufSizeT);
		// now calculate total queued length
		lenT = QMOD(m_NextPosT- curPlayPosT, m_BufSizeT);
		// Reset the timer to trigger shortly after  the last valid sample has played
		// The timer serves two purposes:
		// - ensure that the DS buffer is silenced before it wraps around
		// - allow the DS object to be released in the half-duplex case, once the remote stops sending
		// convert to millisecs
		// Need to make sure the timeout happens before the DS buffer wrapsaround.

		if (lenT > m_BufSizeT/2)
			lenT = m_BufSizeT/2;
		lenT = lenT * 1000/ m_fDevRecv.nSamplesPerSec;
		m_pDP->m_RecvTimer.CancelTimeout(&m_TimeoutObj);
		m_TimeoutObj.SetDueTime(GetTickCount()+lenT+100);
		m_pDP->m_RecvTimer.SetTimeout(&m_TimeoutObj);
	}
	m_PlayPosT = curPlayPosT;
	return hr;
		
}
// This routine is called on every packet to perform the adaptive delay calculation
// Remote time is measured by the RTP timestamp and local time is measured by the DirectSound
// play pointer.
// The general idea is to average how much a packet is later than its 'expected' arrival time,
// assuming the packet with the shortest trip delay is dead on time.
//
void
RecvDSAudioStream::UpdateVariableDelay(DWORD sendT, DWORD curPlayPosT)
{
#define PLAYOUT_DELAY_FACTOR	2
	LONG deltaA, deltaS;
	DWORD delay;
	// update arrival time based on how much the DS play pointer has advanced
	// since the last packet
	m_ArrT += QMOD(curPlayPosT-m_PlayPosT, m_BufSizeT);
	// m_ArrivalT0 and m_SendT0 are the arrival and send timestamps of the packet
	// with the shortest trip delay. We could have just stored (m_ArrivalT0 - m_SendT0)
	// but since the local and remote clocks are completely unsynchronized, there would
	// be signed/unsigned complications.
	deltaS = sendT - m_SendT0;
	deltaA = m_ArrT - m_ArrivalT0;
	if (deltaA < deltaS 		// this packet took less time
		|| deltaA > (int)m_fDevRecv.nSamplesPerSec*8	// reset every 8 secs
		|| deltaS < -(int)m_fDevRecv.nSamplesPerSec	// or after big timestamp jumps
		)	
	{
		delay = 0;
		// delay = deltaS - deltaA
		// replace shortest trip delay times
		m_SendT0 = sendT;
		m_ArrivalT0 = m_ArrT;
	} else {
		// variable delay is how much longer this packet took
		delay = deltaA - deltaS;
	}
	// now update average variable delay according to
	// m_AvgVarDelay = m_AvgVarDelay + (delay - m_AvgVarDelay)*1/16;
	// however we are storing the scaled average, with a scaling
	// factor of 16. So the calculation becomes
	m_ScaledAvgVarDelay = m_ScaledAvgVarDelay + (delay - m_ScaledAvgVarDelay/16);
	// now calculate actual buffering delay we will use
	//  MinDelay adds some slack (may be necessary for some drivers)
	m_DelayT = m_MinDelayT + PLAYOUT_DELAY_FACTOR * m_ScaledAvgVarDelay/16;
	if (m_DelayT > m_MaxDelayT) m_DelayT = m_MaxDelayT;

	LOG((LOGMSG_JITTER,delay, m_ScaledAvgVarDelay/16, m_DelayT));


	UPDATE_COUNTER(g_pctrAudioJBDelay, (m_DelayT * 1000)/m_fDevRecv.nSamplesPerSec);

}

void
RecvDSAudioStream::RecvTimeout()
{
	DWORD curPlayPosT, curWritePosT;
	LPVOID p1, p2;
	DWORD cb1, cb2;
	UINT lenT;
	HRESULT hr;

	if (m_pDSBuf == NULL)
	{
		WARNING_OUT(("RecvDSAudioStream::RecvTimeout - DirectSoundBuffer is not valid\r\n"));
		return;
	}


	m_pDSBuf->GetCurrentPosition(&curPlayPosT,&curWritePosT);
	curPlayPosT = BYTESTOSAMPLES(curPlayPosT);
	curWritePosT = BYTESTOSAMPLES(curWritePosT);

	// this part is cut and pasted from PlayBuf
	if (!m_fEmpty) {
		// wasn't empty last time we checked but is it empty now?
		if (QMOD(curPlayPosT-m_PlayPosT, m_BufSizeT) > QMOD(m_NextPosT-m_PlayPosT, m_BufSizeT)) {
			// play cursor has advanced beyond the last written byte
			m_fEmpty = TRUE;
		}
		// write silence into the part of the buffer that just played
		hr = m_pDSBuf->Lock(SAMPLESTOBYTES(m_PlayPosT),SAMPLESTOBYTES(QMOD(curPlayPosT-m_PlayPosT, m_BufSizeT)), &p1, &cb1, &p2, &cb2, 0);
		if (hr == S_OK) {
			ZeroMemory(p1,cb1);
			if (cb2)
				ZeroMemory(p2,cb2);
			m_pDSBuf->Unlock(p1,cb1,p2,cb2);
		}
	}
	LOG((LOGMSG_DSTIMEOUT, curPlayPosT, m_NextPosT, GetTickCount()));
	
	m_PlayPosT = curPlayPosT;
	if (!m_fEmpty) {
		// The buffer isnt quite empty yet!
		// Reschedule??
		DEBUGMSG(ZONE_DP,("DSBuffer not empty after timeout\n"));
		lenT = QMOD(m_NextPosT- curPlayPosT, m_BufSizeT);
		// Reset the timer to trigger shortly after  the last valid sample has played
		// Need to make sure the timeout happens before the DS buffer wrapsaround.
		if (lenT > m_BufSizeT/2)
			lenT = m_BufSizeT/2;
		// convert to millisecs
		lenT = lenT * 1000/ m_fDevRecv.nSamplesPerSec;
		m_TimeoutObj.SetDueTime(GetTickCount()+lenT+100);
		m_pDP->m_RecvTimer.SetTimeout(&m_TimeoutObj);
	}
	else if (m_DPFlags & DP_FLAG_HALF_DUPLEX)
	{
		// need to release the DSBuffer and DSObject
		ReleaseDSBuffer();
	}
}

HRESULT RecvDSAudioStream::RTPCallback(WSABUF *pWsaBuf, DWORD timestamp, UINT seq, UINT fMark)
{
	HRESULT hr;

    if (m_ThreadFlags & DPTFLAG_PAUSE_RECV)
	{
		return E_FAIL;
    }

	// update number of bits received
	UPDATE_COUNTER(g_pctrAudioReceiveBytes,(pWsaBuf->len + IP_HEADER_SIZE + UDP_HEADER_SIZE)*8);

	hr = Decode((BYTE *)pWsaBuf->buf + sizeof(RTP_HDR), pWsaBuf->len - sizeof(RTP_HDR));
	if (hr == S_OK )
	{
		// Have we initialized DirectSound?
		// Yes, unless its half-duplex
		if (!m_pDSBuf)
		{
			hr = CreateDSBuffer();
		}
		if (hr == S_OK)
		{
			PlayBuf(timestamp, seq, fMark);
		}
	}
	m_pIRTPRecv->FreePacket(pWsaBuf);
	return S_OK;
}

// this method called from the UI thread only
HRESULT RecvDSAudioStream::DTMFBeep()
{
	if ( (!(m_DPFlags & DPFLAG_STARTED_RECV)) ||
		 (m_ThreadFlags & DPTFLAG_PAUSE_RECV) )
	{
		return E_FAIL;
	}

	m_pDP->RecvThreadMessage(MSG_PLAY_SOUND, this);

	return S_OK;
}


HRESULT RecvDSAudioStream::OnDTMFBeep()
{
	int nBeeps;
	DWORD dwBufSize = m_StrmConvHdr.cbDstLength;
	HRESULT hr=S_OK;
	int nIndex;

	if ( (!(m_DPFlags & DPFLAG_STARTED_RECV)) ||
		 (m_ThreadFlags & DPTFLAG_PAUSE_RECV) )
	{
		return E_FAIL;
	}

	if (dwBufSize == 0)
	{
		return E_FAIL;
	}


	nBeeps = DTMF_FEEDBACK_BEEP_MS / ((dwBufSize * 1000) / m_fDevRecv.nAvgBytesPerSec);

	if (nBeeps == 0)
	{
		nBeeps = 1;
	}

	MakeDTMFBeep(&m_fDevRecv, m_StrmConvHdr.pbDst , m_StrmConvHdr.cbDstLength);

	if (!m_pDSBuf)
	{
		hr = CreateDSBuffer();
		if (FAILED(hr))
		{
			return hr;
		}
	}


	m_StrmConvHdr.dwDstUser = BUFFER_RECEIVED;
	PlayBuf(m_NextTimeT , INVALID_RTP_SEQ_NUMBER, true);
	nBeeps--;

	for (nIndex = 0; nIndex < nBeeps; nIndex++)
	{
		m_StrmConvHdr.dwDstUser = BUFFER_RECEIVED;
		PlayBuf(m_NextTimeT, INVALID_RTP_SEQ_NUMBER, false);
	}
	
	return S_OK;

}