#include "precomp.h" #include "datapump.h" #include "DSCStream.h" #include "agc.h" static const int DSC_TIMEOUT = 1000; static const int DSC_MAX_LAG = 500; static const int DSC_SUCCESS = 0; static const int DSC_NEED_TO_EXIT = 1; static const int DSC_FRAME_SENT = 2; static const int DSC_SILENCE_DETECT = 3; static const int DSC_ERROR = 4; static const int SILENCE_TIMEOUT= 600; // milliseconds static const int HEADER_SIZE = sizeof(RTP_HDR) + IP_HEADER_SIZE + UDP_HEADER_SIZE; static const UINT DSC_QOS_INITIALIZE = 100; static const UINT DSC_QOS_PACKET_SENT = 101; static inline UINT QMOD(const int x, const int mod) { if (x >= mod) return (x-mod); if (x < 0) return (x+mod); else return x; } BOOL SendDSCStream::UpdateQosStats(UINT uStatType, UINT uStatValue1, UINT uStatValue2) { EnterCriticalSection(&m_crsQos); switch (uStatType) { case DSC_QOS_INITIALIZE: { m_Stats.dwMsCap = m_Stats.dwMsComp = m_Stats.dwBits = m_Stats.dwCount = 0; m_Stats.dwNewestTs = m_Stats.dwOldestTs = timeGetTime(); break; } case DSC_QOS_PACKET_SENT: { // uStatvalue1 is the CPU time, uStatvalue2 is the size in bytes m_Stats.dwCount++; m_Stats.dwMsComp += uStatValue1; m_Stats.dwBits += (uStatValue2) * 8; // statview really wants bits per second UPDATE_COUNTER(g_pctrAudioSendBytes, uStatValue2*8); break; } }; LeaveCriticalSection(&m_crsQos); return TRUE; } inline BOOL SendDSCStream::ThreadExitCheck() { return (m_ThreadFlags & DPTFLAG_STOP_RECORD); } // resyncs the Timestamp with the last known timestamp inline void SendDSCStream::UpdateTimestamp() { UINT uTime; uTime = (timeGetTime() - m_SavedTickCount)*((m_wfPCM.nSamplesPerSec)/1000); // if (uTime < 0) // uTime = 0; m_SendTimestamp += uTime; } // WaitForControl - Thread Function // opens the DirectSound device or waits for it to become available // returns either DSC_SUCCESS or DSC_NEED_TO_EXIT DWORD SendDSCStream::WaitForControl() { DWORD dwRet; HRESULT hr=E_FAIL; while (!(m_ThreadFlags & DPTFLAG_STOP_RECORD)) { if (m_bFullDuplex == FALSE) { dwRet = WaitForSingleObject(g_hEventHalfDuplex, 1000); if (dwRet == WAIT_TIMEOUT) continue; } hr = CreateDSCBuffer(); if (FAILED(hr)) { m_nFailCount++; Sleep(2000); // wait and try again hr = CreateDSCBuffer(); } if (SUCCEEDED(hr)) { break; } m_nFailCount++; if ((m_nFailCount >= MAX_FAILCOUNT) && m_bCanSignalFail) { m_pDP->StreamEvent(MCF_SEND, MCF_AUDIO, STREAM_EVENT_DEVICE_FAILURE, 0); m_bCanSignalOpen = TRUE; m_bCanSignalFail = FALSE; // don't signal failure more than once m_bJammed = TRUE; } // if we can't open the device, even after being signaled // then yield some time to playback in hopes that it becomes available again // check the thread flags again such so that we don't // hold up the client for too long when he calls Stop() if (!(m_ThreadFlags & DPTFLAG_STOP_RECORD)) { SetEvent(g_hEventHalfDuplex); Sleep(2000); } } if (m_ThreadFlags & DPTFLAG_STOP_RECORD) { return DSC_NEED_TO_EXIT; } m_bJammed = FALSE; m_nFailCount = 0; m_bCanSignalFail = TRUE; if (m_bCanSignalOpen) { m_pDP->StreamEvent(MCF_SEND, MCF_AUDIO, STREAM_EVENT_DEVICE_OPEN, 0); m_bCanSignalOpen = FALSE; // don't signal more than once per session } return DSC_SUCCESS; } // YieldControl is a thread function // It releases the DirectSound device // and signals the half duplex event DWORD SendDSCStream::YieldControl() { ReleaseDSCBuffer(); SetEvent(g_hEventHalfDuplex); if (m_ThreadFlags & DPTFLAG_STOP_RECORD) { return DSC_NEED_TO_EXIT; } // half duplex yielding // playback has 100ms to grab device otherwise we take it back Sleep(100); return DSC_SUCCESS; } // ProcessFrame is a thread function // Given a position in the DirectSoundCapture buffer, // it will apply silence detection to the frame, and send it if // appropriate // returns DSC_FRAME_SENT or DSC_SILENCE_DETECT DWORD SendDSCStream::ProcessFrame(DWORD dwBufferPos, BOOL fMark) { HRESULT hr; DWORD dwSize1=0, dwSize2=0, dwMaxStrength; WORD wPeakStrength; VOID *pBuf1=NULL, *pBuf2=NULL; void *pPacketBuffer = NULL; UINT uSize, uLength; AudioPacket *pAP = m_aPackets[0]; BOOL fSilent, bRet; pAP->GetDevData(&pPacketBuffer, &uSize); pAP->SetProp(MP_PROP_TIMESTAMP,m_SendTimestamp); pAP->m_fMark = fMark; ASSERT(uSize == m_dwFrameSize); // copy the frame out of the DSC buffer and into the packet object hr = m_pDSCBuffer->Lock(dwBufferPos, m_dwFrameSize, &pBuf1, &dwSize1, &pBuf2, &dwSize2, 0); if (SUCCEEDED(hr)) { CopyMemory((BYTE*)pPacketBuffer, pBuf1, dwSize1); if (pBuf2 && dwSize2) { CopyMemory(((BYTE*)pPacketBuffer)+dwSize1, pBuf2, dwSize2); } m_pDSCBuffer->Unlock(pBuf1, dwSize2, pBuf2, dwSize2); pAP->SetState(MP_STATE_RECORDED); } else { DEBUGMSG (ZONE_DP, ("SendDSCStream::ProcessFrame - could not lock DSC buffer\r\n")); return DSC_ERROR; } if (m_mmioSrc.fPlayFromFile && m_mmioSrc.hmmioSrc) { AudioFile::ReadSourceFile(&m_mmioSrc, (BYTE*)pPacketBuffer, uSize); } // do silence detection pAP->ComputePower(&dwMaxStrength, &wPeakStrength); fSilent = m_AudioMonitor.SilenceDetect((WORD)dwMaxStrength); if (fSilent) { m_dwSilenceTime += m_dwFrameTimeMS; if (m_dwSilenceTime < SILENCE_TIMEOUT) { fSilent = FALSE; } } else { m_dwSilenceTime = 0; // only do automix on packets above the silence threshold if (m_bAutoMix) { m_agc.Update(wPeakStrength, m_dwFrameTimeMS); } } m_fSending = !(fSilent); // m_fSending indicates that we are transmitting if (fSilent) { // we don't send this packet, but we do cache it because // if the next one get's sent, we send this one too. ASSERT(pAP == m_aPackets[0]); // swap the audio packets // m_aPackets[1] always holds a cached packet pAP = m_aPackets[0]; m_aPackets[0] = m_aPackets[1]; m_aPackets[1] = pAP; pAP = m_aPackets[0]; return DSC_SILENCE_DETECT; } // the packet is valid. send it, and maybe the one before it Send(); return DSC_FRAME_SENT; } // this function is called by process frame (thread function) // sends the current packet, and maybe any packet prior to it. // returns the number of packets sent DWORD SendDSCStream::Send() { DWORD dwTimestamp0, dwTimestamp1; DWORD dwState0, dwState1; DWORD dwCount=0; MMRESULT mmr; HRESULT hr; // we know we have to send m_aPackets[0], and maybe m_aPackets[1] // we send m_aPackets[1] if it is actually the beginning of this talk spurt dwTimestamp0 = m_aPackets[0]->GetTimestamp(); dwTimestamp1 = m_aPackets[1]->GetTimestamp(); dwState0 = m_aPackets[0]->GetState(); dwState1 = m_aPackets[1]->GetState(); ASSERT(dwState0 == MP_STATE_RECORDED); if (dwState0 != MP_STATE_RECORDED) return 0; // evaluate if we need to send the prior packet if (dwState1 == MP_STATE_RECORDED) { if ((dwTimestamp1 + m_dwFrameTimeMS) == dwTimestamp0) { m_aPackets[1]->m_fMark = TRUE; // set the mark bit on the first packet m_aPackets[0]->m_fMark = FALSE; // reset the mark bit on the next packet hr = SendPacket(m_aPackets[1]); if (SUCCEEDED(hr)) { dwCount++; } } else { m_aPackets[1]->SetState(MP_STATE_RESET); } } hr = SendPacket(m_aPackets[0]); if (SUCCEEDED(hr)) dwCount++; return dwCount; } // thread function called by Send. Sends a packet to RTP. HRESULT SendDSCStream::SendPacket(AudioPacket *pAP) { MMRESULT mmr; PS_QUEUE_ELEMENT psq; UINT uLength; UINT uEncodeTime; uEncodeTime = timeGetTime(); mmr = m_pAudioFilter->Convert(pAP, AP_ENCODE); uEncodeTime = timeGetTime() - uEncodeTime; if (mmr == MMSYSERR_NOERROR) { pAP->SetState(MP_STATE_ENCODED); // do we need to do this ? psq.pMP = pAP; psq.dwPacketType = PS_AUDIO; psq.pRTPSend = m_pRTPSend; pAP->GetNetData((void**)(&(psq.data)), &uLength); ASSERT(psq.data); psq.dwSize = uLength; psq.fMark = pAP->m_fMark; psq.pHeaderInfo = NULL; psq.dwHdrSize = 0; m_pDP->m_PacketSender.m_SendQueue.PushFront(psq); while (m_pDP->m_PacketSender.SendPacket()) { ; } UpdateQosStats(DSC_QOS_PACKET_SENT, uEncodeTime, uLength+HEADER_SIZE); } pAP->SetState(MP_STATE_RESET); return S_OK; } DWORD SendDSCStream::RecordingThread() { HRESULT hr; DWORD dwWaitTime = DSC_TIMEOUT; // one sec DWORD dwRet, dwReadPos, dwCapPos; DWORD dwFirstValidFramePos, dwLastValidFramePos, dwNumFrames; DWORD dwLag, dwMaxLag, dwLagDiff; DWORD dwNextExpected, dwCurrentFramePos, dwIndex; BOOL bNeedToYield; BOOL fMark; IMediaChannel *pIMC = NULL; RecvMediaStream *pRecv = NULL; CMixerDevice *pMixer = NULL; // initialize recording thread m_SendTimestamp = timeGetTime(); m_SavedTickCount = 0; m_fSending = TRUE; m_bJammed = FALSE; m_nFailCount = 0; m_bCanSignalOpen = TRUE; m_bCanSignalFail = TRUE; UpdateQosStats(DSC_QOS_INITIALIZE, 0, 0); SetThreadPriority(m_hCapturingThread, THREAD_PRIORITY_HIGHEST); // automix object pMixer = CMixerDevice::GetMixerForWaveDevice(NULL, m_CaptureDevice, MIXER_OBJECTF_WAVEIN); m_agc.SetMixer(pMixer); // if pMixer is NULL, then it's still ok m_agc.Reset(); LOG((LOGMSG_DSC_STATS, m_dwDSCBufferSize, m_dwFrameSize)); while (!(ThreadExitCheck())) { dwRet = WaitForControl(); if (dwRet == DSC_NEED_TO_EXIT) { break; } hr = m_pDSCBuffer->Start(DSCBSTART_LOOPING); if (FAILED(hr)) { // ERROR! We expected this call to succeed YieldControl(); Sleep(1000); continue; } ResetEvent(m_hEvent); m_pDSCBuffer->GetCurrentPosition(&dwCapPos, &dwReadPos); // set the next expected position to be on the next logical // frame boundary up from where it is now dwNextExpected = QMOD(m_dwFrameSize + (dwReadPos / m_dwFrameSize) * m_dwFrameSize, m_dwDSCBufferSize); dwMaxLag = (m_dwNumFrames/2) * m_dwFrameSize; m_dwSilenceTime = 0; bNeedToYield = FALSE; fMark = TRUE; UpdateTimestamp(); while( (bNeedToYield == FALSE) && (!(ThreadExitCheck())) ) { dwRet = WaitForSingleObject(m_hEvent, dwWaitTime); LOG((LOGMSG_DSC_TIMESTAMP, timeGetTime())); m_pDSCBuffer->GetCurrentPosition(&dwCapPos, &dwReadPos); LOG((LOGMSG_DSC_GETCURRENTPOS, dwCapPos, dwReadPos)); if (dwRet == WAIT_TIMEOUT) { DEBUGMSG(ZONE_DP, ("DSCThread.cpp: Timeout on the DSC Buffer has occurred.\r\n")); LOG((LOGMSG_DSC_LOG_TIMEOUT)); dwNextExpected = QMOD(m_dwFrameSize + (dwReadPos / m_dwFrameSize) * m_dwFrameSize, m_dwDSCBufferSize); continue; } dwLag = QMOD(dwReadPos - dwNextExpected, m_dwDSCBufferSize); if (dwLag > dwMaxLag) { // we got here because of one of two conditions // 1. WaitFSO above returned earlier than expected. // This can happen when the previous interation of // the loop has sent multiple packets. The read cursor // is most likely only within one frame behind the expected // cursor. // In this cases, just keep Waiting for the current // read position to (dwReadPos) "catch up" to dwNextExpected // 2. A huge delay or something really bad. ("burp") // we could simply continue waiting for the read position // to catch up to dwNextExpected, but it's probably better // to reposition dwNextExpected so that we don't wait // too long before sending a frame again dwLagDiff = QMOD((dwLag + m_dwFrameSize), m_dwDSCBufferSize); if (dwLagDiff < m_dwFrameSize) { LOG((LOGMSG_DSC_EARLY)); // only lagging behind by one frame // WaitFSO probably returned early ; } else { LOG((LOGMSG_DSC_LAGGING, dwLag, dwNextExpected)); // consider repositioning dwNextExpected, advancing // m_SendTimeStamp, and setting fMark if this condition // happens a lot } continue; } dwFirstValidFramePos = QMOD(dwNextExpected - m_dwFrameSize, m_dwDSCBufferSize); dwLastValidFramePos = (dwReadPos / m_dwFrameSize) * m_dwFrameSize; dwNumFrames = QMOD(dwLastValidFramePos - dwFirstValidFramePos, m_dwDSCBufferSize) / m_dwFrameSize; dwCurrentFramePos = dwFirstValidFramePos; LOG((LOGMSG_DSC_SENDING, dwNumFrames, dwFirstValidFramePos, dwLastValidFramePos)); for (dwIndex = 0; dwIndex < dwNumFrames; dwIndex++) { m_SendTimestamp += m_dwSamplesPerFrame; // increment in terms of samples // Send The data dwRet = ProcessFrame(dwCurrentFramePos, fMark); dwCurrentFramePos = QMOD(dwCurrentFramePos + m_dwFrameSize, m_dwDSCBufferSize); if (dwRet == DSC_FRAME_SENT) { fMark = FALSE; } else if ((dwRet == DSC_SILENCE_DETECT) && (m_bFullDuplex == FALSE)) { m_pDP->GetMediaChannelInterface(MCF_RECV | MCF_AUDIO, &pIMC); fMark = TRUE; if (pIMC) { pRecv = static_cast (pIMC); if (pRecv->IsEmpty() == FALSE) { bNeedToYield = TRUE; } pIMC->Release(); pIMC = NULL; if (bNeedToYield) { break; } } } else { fMark = TRUE; } } dwNextExpected = QMOD(dwLastValidFramePos + m_dwFrameSize, m_dwDSCBufferSize); if (bNeedToYield) { YieldControl(); m_SavedTickCount = timeGetTime(); } } // while (!bNeedToYield) } // while (!ThreadExitCheck()) // time to exit YieldControl(); delete pMixer; return TRUE; }