windows-nt/Source/XPSP1/NT/enduser/netmeeting/av/nac/dscthread.cpp
2020-09-26 16:20:57 +08:00

567 lines
13 KiB
C++

#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<RecvMediaStream *> (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;
}