#include "precomp.h" #include //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; }