|
|
#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;
}
|