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

1157 lines
28 KiB
C++
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "precomp.h"
#include "mixer.h"
#include "agc.h"
// #define LOGSTATISTICS_ON 1
DWORD SendAudioStream::RecordingThread ()
{
HRESULT hr = DPR_SUCCESS;
MediaPacket *pPacket;
DWORD dwWait;
HANDLE hEvent;
DWORD dwDuplexType;
DWORD dwVoiceSwitch;
DWORD_PTR dwPropVal;
DWORD dwSamplesPerPkt;
DWORD dwSamplesPerSec;
DWORD dwSilenceLimit, dwMaxStrength, dwLengthMS;
WORD wPeakStrength;
UINT u, uBufferSize;
UINT uSilenceCount = 0;
UINT uPrefeed = 0;
UINT uTimeout = 0;
DevMediaQueue dq;
BOOL fSilent;
AGC agc(NULL); // audio gain control object
CMixerDevice *pMixer = NULL;
int nFailCount = 0;
bool bCanSignalOpen=true; // should we signal that the device is open
// note: pMC is an artifact of when this thread was in the Datapump
// namespace. We can probably start phasing this variable out.
// in the mean time: "pMC = this" will suffice
// SendAudioStream *pMC = (SendAudioStream *)(m_pDP->m_Audio.pSendStream);
SendAudioStream *pMC = this;
ASSERT(pMC && (pMC->m_DPFlags & DPFLAG_INITIALIZED));
TxStream *pStream = pMC->m_SendStream;
AcmFilter *pAudioFilter = pMC->m_pAudioFilter;
// warning: typecasting a base class ptr to a derived class ptr.
WaveInControl *pMediaCtrl = (WaveInControl *)pMC->m_InMedia;
FX_ENTRY ("DP::RcrdTh:")
// get thread context
if (pStream == NULL || pAudioFilter == NULL || pMediaCtrl == NULL)
{
return DPR_INVALID_PARAMETER;
}
// Enter critical section: QoS thread also reads the statistics
EnterCriticalSection(&pMC->m_crsQos);
// Initialize QoS structure
ZeroMemory(&pMC->m_Stats, 4UL * sizeof(DWORD));
// Initialize oldest QoS callback timestamp
pMC->m_Stats.dwNewestTs = pMC->m_Stats.dwOldestTs = timeGetTime();
// Leave critical section
LeaveCriticalSection(&pMC->m_crsQos);
pMediaCtrl->GetProp(MC_PROP_MEDIA_DEV_ID, &dwPropVal);
if (dwPropVal != (DWORD)WAVE_MAPPER)
{
pMixer = CMixerDevice::GetMixerForWaveDevice(NULL, (DWORD)dwPropVal, MIXER_OBJECTF_WAVEIN);
}
// even if pMixer is null, this is fine, AGC will catch subsequent errors
agc.SetMixer(pMixer);
// get thresholds
pMediaCtrl->GetProp (MC_PROP_TIMEOUT, &dwPropVal);
uTimeout = (DWORD)dwPropVal;
pMediaCtrl->GetProp (MC_PROP_PREFEED, &dwPropVal);
uPrefeed = (DWORD)dwPropVal;
// get duplex type
pMediaCtrl->GetProp (MC_PROP_DUPLEX_TYPE, &dwPropVal);
dwDuplexType = (DWORD)dwPropVal;
// get Samples/Pkt and Samples/Sec
pMediaCtrl->GetProp (MC_PROP_SPP, &dwPropVal);
dwSamplesPerPkt = (DWORD)dwPropVal;
pMediaCtrl->GetProp (MC_PROP_SPS, &dwPropVal);
dwSamplesPerSec = (DWORD)dwPropVal;
pMediaCtrl->GetProp (MC_PROP_SILENCE_DURATION, &dwPropVal);
dwSilenceLimit = (DWORD)dwPropVal;
// calculate silence limit in units of packets
// silence_time_in_ms/packet_duration_in_ms
dwSilenceLimit = dwSilenceLimit*dwSamplesPerSec/(dwSamplesPerPkt*1000);
// length of a packet in millisecs
dwLengthMS = (dwSamplesPerPkt * 1000) / dwSamplesPerSec;
dq.SetSize (MAX_TXRING_SIZE);
WaitForSignal:
// DEBUGMSG (1, ("%s: WaitForSignal\r\n", _fx_));
{
pMediaCtrl->GetProp (MC_PROP_MEDIA_DEV_HANDLE, &dwPropVal);
if (dwPropVal)
{
DEBUGMSG (ZONE_DP, ("%s: already open\r\n", _fx_));
goto SendLoop; // sound device already open
}
// in the full-duplex case, open and prepare the device and charge ahead.
// in the half duplex case wait for playback's signal before opening the device
while (TRUE)
{
// should I stop now???
if (pMC->m_ThreadFlags & DPTFLAG_STOP_RECORD)
{
DEBUGMSG (ZONE_DP, ("%s: STOP_1\r\n", _fx_));
goto MyEndThread;
}
dwWait = (dwDuplexType & DP_FLAG_HALF_DUPLEX) ? WaitForSingleObject (g_hEventHalfDuplex, uTimeout)
: WAIT_OBJECT_0;
// now, let's check why I don't need to wait
if (dwWait == WAIT_OBJECT_0)
{
//DEBUGMSG (ZONE_DP, ("%s: try to open audio dev\r\n", _fx_));
LOG((LOGMSG_OPEN_AUDIO));
hr = pMediaCtrl->Open ();
if (hr != DPR_SUCCESS)
{
DEBUGMSG (ZONE_DP, ("%s: MediaCtrl::Open failed, hr=0x%lX\r\n", _fx_, hr));
pMediaCtrl->SetProp(MC_PROP_AUDIO_JAMMED, TRUE);
SetEvent(g_hEventHalfDuplex);
nFailCount++;
if (nFailCount == MAX_FAILCOUNT)
{
// three attempts to open the device have failed
// signal to the UI that something is wrong
m_pDP->StreamEvent(MCF_SEND, MCF_AUDIO, STREAM_EVENT_DEVICE_FAILURE, 0);
bCanSignalOpen = true;
}
Sleep(2000); // Sleep for two seconds
continue;
}
// Notification is not used. if needed do it thru Channel
//pMC->m_Connection->DoNotification(CONNECTION_OPEN_MIC);
pMediaCtrl->PrepareHeaders ();
goto SendLoop;
}
} // while
}
SendLoop:
nFailCount = 0;
pMediaCtrl->SetProp(MC_PROP_AUDIO_JAMMED, FALSE);
if (bCanSignalOpen)
{
m_pDP->StreamEvent(MCF_SEND, MCF_AUDIO, STREAM_EVENT_DEVICE_OPEN, 0);
bCanSignalOpen = false; // don't signal more than once per session
}
// DEBUGMSG (1, ("%s: SendLoop\r\n", _fx_));
// get event handle
pMediaCtrl->GetProp (MC_PROP_EVENT_HANDLE, &dwPropVal);
hEvent = (HANDLE) dwPropVal;
if (hEvent == NULL)
{
DEBUGMSG (ZONE_DP, ("%s: invalid event\r\n", _fx_));
return DPR_CANT_CREATE_EVENT;
}
// hey, in the very beginning, let's 'Start' it
hr = pMediaCtrl->Start ();
if (hr != DPR_SUCCESS)
{
DEBUGMSG (ZONE_DP, ("%s: MediaControl::Start failed, hr=0x%lX\r\n", _fx_, hr));
goto MyEndThread;
}
// update timestamp to account for the 'sleep' period
pMC->m_SendTimestamp += (GetTickCount() - pMC->m_SavedTickCount)*dwSamplesPerSec/1000;
// let's feed four buffers first
for (u = 0; u < uPrefeed; u++)
{
if ((pPacket = pStream->GetFree ()) != NULL)
{
if ((hr = pPacket->Record ()) != DPR_SUCCESS)
{
DEBUGMSG (ZONE_DP, ("%s: Record failed, hr=0x%lX\r\n", _fx_, hr));
}
dq.Put (pPacket);
}
}
// let's get into the loop, mm system notification loop
pMC->m_fSending= FALSE;
while (TRUE)
{
dwWait = WaitForSingleObject (hEvent, uTimeout);
// should I stop now???
if (pMC->m_ThreadFlags & DPTFLAG_STOP_RECORD)
{
DEBUGMSG (ZONE_DP, ("%s: STOP_3\r\n", _fx_));
goto HalfDuplexYield;
}
// get current voice switching mode
pMediaCtrl->GetProp (MC_PROP_VOICE_SWITCH, &dwPropVal);
dwVoiceSwitch = (DWORD)dwPropVal;
// see why I don't need to wait
if (dwWait != WAIT_TIMEOUT)
{
while (TRUE)
{
if ((pPacket = dq.Peek ()) != NULL)
{
if (! pPacket->IsBufferDone ())
{
break;
}
else
{
if (pMC->m_mmioSrc.fPlayFromFile && pMC->m_mmioSrc.hmmioSrc)
pPacket->ReadFromFile (&pMC->m_mmioSrc);
u--; // one less buffer with the wave device
}
}
else
{
DEBUGMSG (ZONE_VERBOSE, ("%s: Peek is NULL\r\n", _fx_));
break;
}
pPacket = dq.Get ();
((AudioPacket*)pPacket)->ComputePower (&dwMaxStrength, &wPeakStrength);
// is this packet silent?
fSilent = pMC->m_AudioMonitor.SilenceDetect((WORD)dwMaxStrength);
if((dwVoiceSwitch == DP_FLAG_AUTO_SWITCH)
&& fSilent)
{
// pPacket->SetState (MP_STATE_RESET); // note: done in Recycle
if (++uSilenceCount >= dwSilenceLimit)
{
pMC->m_fSending = FALSE; // stop sending packets
// if half duplex mode and playback thread may be waiting
if (dwDuplexType & DP_FLAG_HALF_DUPLEX)
{
IMediaChannel *pIMC = NULL;
RecvMediaStream *pRecv;
m_pDP->GetMediaChannelInterface(MCF_RECV | MCF_AUDIO, &pIMC);
if (pIMC)
{
pRecv = static_cast<RecvMediaStream *> (pIMC);
if (pRecv->IsEmpty()==FALSE)
{
//DEBUGMSG (ZONE_DP, ("%s: too many silence and Yield\r\n", _fx_));
LOG((LOGMSG_REC_YIELD));
pPacket->Recycle ();
pStream->PutNextRecorded (pPacket);
uSilenceCount = 0;
pIMC->Release();
goto HalfDuplexYield;
}
pIMC->Release();
}
}
}
}
else
{
switch(dwVoiceSwitch)
{
// either there was NO silence, or manual switching is in effect
default:
case DP_FLAG_AUTO_SWITCH: // this proves no silence (in this path because of non-silence)
case DP_FLAG_MIC_ON:
pMC->m_fSending = TRUE;
uSilenceCount = 0;
break;
case DP_FLAG_MIC_OFF:
pMC->m_fSending = FALSE;
break;
}
}
if (pMC->m_fSending)
{
pPacket->SetState (MP_STATE_RECORDED);
// do AUTOMIX, but ignore DTMF tones
if (pMC->m_bAutoMix)
{
agc.Update(wPeakStrength, dwLengthMS);
}
}
else
{
pPacket->Recycle();
// Enter critical section: QoS thread also reads the statistics
EnterCriticalSection(&pMC->m_crsQos);
// Update total number of packets recorded
pMC->m_Stats.dwCount++;
// Leave critical section
LeaveCriticalSection(&pMC->m_crsQos);
}
pPacket->SetProp(MP_PROP_TIMESTAMP,pMC->m_SendTimestamp);
// pPacket->SetProp(MP_PROP_TIMESTAMP,GetTickCount());
pMC->m_SendTimestamp += dwSamplesPerPkt;
pStream->PutNextRecorded (pPacket);
} // while
}
else
{
if (dwDuplexType & DP_FLAG_HALF_DUPLEX)
{
DEBUGMSG (ZONE_DP, ("%s: Timeout and Yield\r\n", _fx_));
goto HalfDuplexYield;
}
} // if
pMC->Send();
// Make sure the recorder has an adequate number of buffers
while ((pPacket = pStream->GetFree()) != NULL)
{
if ((hr = pPacket->Record ()) == DPR_SUCCESS)
{
dq.Put (pPacket);
}
else
{
dq.Put (pPacket);
DEBUGMSG (ZONE_DP, ("%s: Record FAILED, hr=0x%lX\r\n", _fx_, hr));
break;
}
u++;
}
if (u < uPrefeed)
{
DEBUGMSG (ZONE_DP, ("%s: NO FREE BUFFERS\r\n", _fx_));
}
} // while TRUE
goto MyEndThread;
HalfDuplexYield:
// stop and reset audio device
pMediaCtrl->Reset ();
// flush dq
while ((pPacket = dq.Get ()) != NULL)
{
pStream->PutNextRecorded (pPacket);
pPacket->Recycle ();
}
// save real time so we can update the timestamp when we restart
pMC->m_SavedTickCount = GetTickCount();
// reset the event
ResetEvent (hEvent);
// close audio device
pMediaCtrl->UnprepareHeaders ();
pMediaCtrl->Close ();
// signal playback thread to start
SetEvent (g_hEventHalfDuplex);
if (!(pMC->m_ThreadFlags & DPTFLAG_STOP_RECORD)) {
// yield
// playback has to claim the device within 100ms or we take it back.
Sleep (100);
// wait for playback's signal
goto WaitForSignal;
}
MyEndThread:
if (pMixer)
delete pMixer;
pMediaCtrl->SetProp(MC_PROP_AUDIO_JAMMED, FALSE);
pMC->m_fSending = FALSE;
DEBUGMSG (ZONE_DP, ("%s: Exiting.\r\n", _fx_));
return hr;
}
DWORD RecvAudioStream::PlaybackThread ( void)
{
HRESULT hr = DPR_SUCCESS;
MediaPacket * pPacket;
MediaPacket * pPrevPacket;
MediaPacket * pNextPacket;
DWORD dwWait;
HANDLE hEvent;
DWORD dwDuplexType;
DWORD_PTR dwPropVal;
UINT u;
UINT uMissingCount = 0;
UINT uPrefeed = 0;
UINT uTimeout = 0;
UINT uSamplesPerPkt=0;
DevMediaQueue dq;
UINT uGoodPacketsQueued = 0;
int nFailCount = 0;
bool bCanSignalOpen=true;
//warning: casting from base to dervied class
// note: pMC is an artifact of when this thread was in the Datapump
// namespace. We can probably start phasing this variable out.
// in the mean time: "pMC = this" will suffice
// RecvAudioStream *pMC = (RecvAudioStream *)(m_pDP->m_Audio.pRecvStream);
RecvAudioStream *pMC = this;
RxStream *pStream = pMC->m_RecvStream;
MediaControl *pMediaCtrl = pMC->m_OutMedia;
#if 0
NETBUF * pStaticNetBuf;
#endif
FX_ENTRY ("DP::PlayTh")
if (pStream == NULL || m_pAudioFilter == NULL || pMediaCtrl == NULL)
{
return DPR_INVALID_PARAMETER;
}
// get event handle
pMediaCtrl->GetProp (MC_PROP_EVENT_HANDLE, &dwPropVal);
hEvent = (HANDLE) dwPropVal;
if (hEvent == NULL)
{
DEBUGMSG (ZONE_DP, ("%s: invalid event\r\n", _fx_));
return DPR_CANT_CREATE_EVENT;
}
// get thresholds
pMediaCtrl->GetProp (MC_PROP_TIMEOUT, &dwPropVal);
uTimeout = (DWORD)dwPropVal;
uPrefeed = pStream->BufferDelay();
// get samples per pkt
pMediaCtrl->GetProp(MC_PROP_SPP, &dwPropVal);
uSamplesPerPkt = (DWORD)dwPropVal;
// get duplex type
pMediaCtrl->GetProp (MC_PROP_DUPLEX_TYPE, &dwPropVal);
dwDuplexType = (DWORD)dwPropVal;
// set dq size
dq.SetSize (uPrefeed);
WaitForSignal:
// DEBUGMSG (1, ("%s: WaitForSignal\r\n", _fx_));
pMediaCtrl->GetProp (MC_PROP_MEDIA_DEV_HANDLE, &dwPropVal);
if (dwPropVal)
{
DEBUGMSG (ZONE_DP, ("%s: already open\r\n", _fx_));
goto RecvLoop; // already open
}
// in the full-duplex case, open and prepare the device and charge ahead.
// in the half duplex case wait for playback's signal before opening the device
while (TRUE)
{
// should I stop now???
if (pMC->m_ThreadFlags & DPTFLAG_STOP_PLAY)
{
DEBUGMSG (ZONE_VERBOSE, ("%s: STOP_1\r\n", _fx_));
goto MyEndThread;
}
dwWait = (dwDuplexType & DP_FLAG_HALF_DUPLEX) ? WaitForSingleObject (g_hEventHalfDuplex, uTimeout)
: WAIT_OBJECT_0;
// to see why I don't need to wait
if (dwWait == WAIT_OBJECT_0)
{
// DEBUGMSG (1, ("%s: try to open audio dev\r\n", _fx_));
pStream->FastForward(FALSE); // GJ - flush receive queue
hr = pMediaCtrl->Open ();
if (hr != DPR_SUCCESS)
{
// somebody may have commandeered the wave out device
// this could be a temporary problem so lets give it some time
DEBUGMSG (ZONE_DP, ("%s: MediaControl::Open failed, hr=0x%lX\r\n", _fx_, hr));
pMediaCtrl->SetProp(MC_PROP_AUDIO_JAMMED, TRUE);
SetEvent(g_hEventHalfDuplex);
nFailCount++;
if (nFailCount == MAX_FAILCOUNT)
{
// three attempts to open the device have failed
// signal to the UI that something is wrong
m_pDP->StreamEvent(MCF_RECV, MCF_AUDIO, STREAM_EVENT_DEVICE_FAILURE, 0);
bCanSignalOpen = true;
}
Sleep(2000); // sleep for two seconds
continue;
}
// Notification is not used. if needed do it thru Channel
//pMC->m_Connection->DoNotification(CONNECTION_OPEN_SPK);
pMediaCtrl->PrepareHeaders ();
goto RecvLoop;
}
} // while
RecvLoop:
nFailCount = 0;
pMediaCtrl->SetProp(MC_PROP_AUDIO_JAMMED, FALSE);
if (bCanSignalOpen)
{
m_pDP->StreamEvent(MCF_RECV, MCF_AUDIO, STREAM_EVENT_DEVICE_OPEN, 0);
bCanSignalOpen = false; // don't signal open more than once per session
}
// Set my thread priority high
// This thread doesnt do any compute intensive work (except maybe
// interpolate?).
// Its sole purpose is to stream ready buffers to the sound device
SetThreadPriority(pMC->m_hRenderingThread, THREAD_PRIORITY_HIGHEST);
// DEBUGMSG (1, ("%s: SendLoop\r\n", _fx_));
// let's feed four buffers first
// But make sure the receive stream has enough buffering delay
// so we dont read past the last packet.
//if (uPrefeed > pStream->BufferDelay())
uGoodPacketsQueued = 0;
for (u = 0; u < uPrefeed; u++)
{
if ((pPacket = pStream->GetNextPlay ()) != NULL)
{
if (pPacket->GetState () == MP_STATE_RESET)
{
// hr = pPacket->Play (pStaticNetBuf);
hr = pPacket->Play (&pMC->m_mmioDest, MP_DATATYPE_SILENCE);
}
else
{
// hr = pPacket->Play ();
hr = pPacket->Play (&pMC->m_mmioDest, MP_DATATYPE_FROMWIRE);
uGoodPacketsQueued++;
}
if (hr != DPR_SUCCESS)
{
DEBUGMSG (ZONE_DP, ("%s: Play failed, hr=0x%lX\r\n", _fx_, hr));
SetEvent(hEvent);
}
dq.Put (pPacket);
}
}
pMC->m_fReceiving = TRUE;
// let's get into the loop
uMissingCount = 0;
while (TRUE)
{
dwWait = WaitForSingleObject (hEvent, uTimeout);
// should I stop now???
if (pMC->m_ThreadFlags & DPTFLAG_STOP_PLAY)
{
DEBUGMSG (ZONE_VERBOSE, ("%s: STOP_3\r\n", _fx_));
goto HalfDuplexYield;
}
// see why I don't need to wait
if (dwWait != WAIT_TIMEOUT)
{
while (TRUE)
{
if ((pPacket = dq.Peek ()) != NULL)
{
if (! pPacket->IsBufferDone ())
{
break;
}
}
else
{
DEBUGMSG (ZONE_VERBOSE, ("%s: Peek is NULL\r\n", _fx_));
break;
}
pPacket = dq.Get ();
if (pPacket->GetState() != MP_STATE_PLAYING_SILENCE)
uGoodPacketsQueued--; // a non-empty buffer just got done
pMC->m_PlaybackTimestamp = pPacket->GetTimestamp() + uSamplesPerPkt;
pPacket->Recycle ();
pStream->Release (pPacket);
if ((pPacket = pStream->GetNextPlay ()) != NULL)
{
// check if we are in half-duplex mode and also if
// the recording thread is around.
if (dwDuplexType & DP_FLAG_HALF_DUPLEX)
{
IMediaChannel *pIMC = NULL;
BOOL fSending = FALSE;
m_pDP->GetMediaChannelInterface(MCF_SEND | MCF_AUDIO, &pIMC);
if (pIMC)
{
fSending = (pIMC->GetState() == MSSTATE_STARTED);
pIMC->Release();
}
if (fSending) {
if (pPacket->GetState () == MP_STATE_RESET)
{
// Decide if its time to yield
// Dont want to yield until we've finished playing all data packets
//
if (!uGoodPacketsQueued &&
(pStream->IsEmpty() || ++uMissingCount >= DEF_MISSING_LIMIT))
{
//DEBUGMSG (ZONE_DP, ("%s: too many missings and Yield\r\n", _fx_));
LOG( (LOGMSG_PLAY_YIELD));
pPacket->Recycle ();
pStream->Release (pPacket);
goto HalfDuplexYield;
}
}
else
{
uMissingCount = 0;
}
}
}
if (pPacket->GetState () == MP_STATE_RESET)
{
pPrevPacket = pStream->PeekPrevPlay ();
pNextPacket = pStream->PeekNextPlay ();
hr = pPacket->Interpolate(pPrevPacket, pNextPacket);
if (hr != DPR_SUCCESS)
{
//DEBUGMSG (ZONE_DP, ("%s: Interpolate failed, hr=0x%lX\r\n", _fx_, hr));
hr = pPacket->Play (&pMC->m_mmioDest, MP_DATATYPE_SILENCE);
}
else
hr = pPacket->Play (&pMC->m_mmioDest, MP_DATATYPE_INTERPOLATED);
}
else
{
// hr = pPacket->Play ();
hr = pPacket->Play (&pMC->m_mmioDest, MP_DATATYPE_FROMWIRE);
uGoodPacketsQueued++;
}
if (hr != DPR_SUCCESS)
{
DEBUGMSG (ZONE_DP, ("%s: Play failed, hr=0x%lX\r\n", _fx_, hr));
SetEvent(hEvent);
}
dq.Put (pPacket);
} else {
DEBUGMSG( ZONE_DP, ("%s: NO PLAY BUFFERS!",_fx_));
}
} // while
}
else
{
if (dwDuplexType & DP_FLAG_HALF_DUPLEX)
{
DEBUGMSG (ZONE_DP, ("%s: Timeout and Yield!\r\n", _fx_));
goto HalfDuplexYield;
}
}
} // while TRUE
goto MyEndThread;
HalfDuplexYield:
pMC->m_fReceiving = FALSE;
// stop and reset audio device
pMediaCtrl->Reset ();
// flush dq
while ((pPacket = dq.Get ()) != NULL)
{
pPacket->Recycle ();
pStream->Release (pPacket);
}
// reset the event
ResetEvent (hEvent);
// close audio device
pMediaCtrl->UnprepareHeaders ();
pMediaCtrl->Close ();
// signal recording thread to start
SetEvent (g_hEventHalfDuplex);
if (!(pMC->m_ThreadFlags & DPTFLAG_STOP_PLAY)) {
// yield
Sleep (0);
// wait for recording's signal
// restore thread priority
SetThreadPriority(pMC->m_hRenderingThread,THREAD_PRIORITY_NORMAL);
goto WaitForSignal;
}
MyEndThread:
pMediaCtrl->SetProp(MC_PROP_AUDIO_JAMMED, FALSE);
DEBUGMSG(ZONE_DP, ("%s: Exiting.\n", _fx_));
return hr;
}
DWORD SendAudioStream::Send()
{
MMRESULT mmr;
MediaPacket *pAP;
void *pBuffer;
DWORD dwBeforeEncode;
DWORD dwAfterEncode;
DWORD dwPacketSize;
UINT uBytesSent;
#ifdef LOGSTATISTICS_ON
char szDebug[256];
DWORD dwDebugSaveBits;
#endif
while ( pAP = m_SendStream->GetNext()) {
if (!(m_ThreadFlags & DPTFLAG_PAUSE_SEND)) {
dwBeforeEncode = timeGetTime();
mmr = m_pAudioFilter->Convert((AudioPacket*)pAP, AP_ENCODE);
if (mmr == MMSYSERR_NOERROR)
{
pAP->SetState(MP_STATE_ENCODED);
}
// Time the encoding operation
dwAfterEncode = timeGetTime() - dwBeforeEncode;
if (mmr == MMSYSERR_NOERROR)
{
SendPacket((AudioPacket*)pAP, &uBytesSent);
}
else
{
uBytesSent = 0;
}
UPDATE_COUNTER(g_pctrAudioSendBytes, uBytesSent*8);
// Enter critical section: QoS thread also reads the statistics
EnterCriticalSection(&m_crsQos);
// Update total number of packets recorded
m_Stats.dwCount++;
// Save the perfs in our stats structure for QoS
#ifdef LOGSTATISTICS_ON
dwDebugSaveBits = m_Stats.dwBits;
#endif
// Add this new frame size to the cumulated size
m_Stats.dwBits += (uBytesSent * 8);
// Add this compression time to total compression time
m_Stats.dwMsComp += dwAfterEncode;
#ifdef LOGSTATISTICS_ON
wsprintf(szDebug, " A: (Voiced) dwBits = %ld up from %ld (file: %s line: %ld)\r\n", m_Stats.dwBits, dwDebugSaveBits, __FILE__, __LINE__);
OutputDebugString(szDebug);
#endif
// Leave critical section
LeaveCriticalSection(&m_crsQos);
}
// whether or not we sent the packet, we need to return
// it to the free queue
pAP->m_fMark=0;
pAP->SetState(MP_STATE_RESET);
m_SendStream->Release(pAP);
}
return DPR_SUCCESS;
}
// queues and sends the packet
// if the packet failed the encode process, it doesn't get sent
HRESULT SendAudioStream::SendPacket(AudioPacket *pAP, UINT *puBytesSent)
{
PS_QUEUE_ELEMENT psq;
UINT uLength;
int nPacketsSent=0;
if (pAP->GetState() != MP_STATE_ENCODED)
{
DEBUGMSG (ZONE_ACM, ("SendAudioStream::SendPacket: Packet not compressed\r\n"));
*puBytesSent = 0;
return E_FAIL;
}
ASSERT(m_pRTPSend);
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;
*puBytesSent = uLength + sizeof(RTP_HDR) + IP_HEADER_SIZE + UDP_HEADER_SIZE;
// add audio packets to the front of the queue
m_pDP->m_PacketSender.m_SendQueue.PushFront(psq);
while (m_pDP->m_PacketSender.SendPacket())
{
;
}
return S_OK;
};
#ifdef OLDSTUFF
/*
// Winsock 1 receive thread
// Creates a hidden window and a message loop to process WINSOCK window
// messages. Also processes private messages from the datapump to start/stop
// receiving on a particular media stream
*/
DWORD
DataPump::CommonRecvThread (void )
{
HRESULT hr;
HWND hWnd = (HWND)NULL;
RecvMediaStream *pRecvMC;
BOOL fChange = FALSE;
MSG msg;
DWORD curTime, nextUpdateTime = 0, t;
UINT timerId = 0;
FX_ENTRY ("DP::RecvTh")
// Create hidden window
hWnd =
CreateWindowEx(
WS_EX_NOPARENTNOTIFY,
"SockMgrWClass", /* See RegisterClass() call. */
NULL,
WS_CHILD , /* Window style. */
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
m_hAppWnd, /* the application window is the parent. */
(HMENU)this, /* hardcoded ID */
m_hAppInst, /* the application owns this window. */
NULL /* Pointer not needed. */
);
if(!hWnd)
{
hr = GetLastError();
DEBUGMSG(ZONE_DP,("CreateWindow returned %d\n",hr));
goto CLEANUPEXIT;
}
SetThreadPriority(m_hRecvThread, THREAD_PRIORITY_ABOVE_NORMAL);
// This function is guaranteed to create a queue on this thread
PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE);
// notify thread creator that we're ready to recv messages
SetEvent(m_hRecvThreadAckEvent);
// Wait for control messages from Start()/Stop() or Winsock messages directed to
// our hidden window
while (GetMessage(&msg, NULL, 0, 0)) {
switch(msg.message) {
case MSG_START_RECV:
// Start receiving on the specified media stream
DEBUGMSG(ZONE_VERBOSE,("%s: MSG_START_RECV\n",_fx_));
pRecvMC = (RecvMediaStream *)msg.lParam;
// call the stream to post recv buffers and
// tell Winsock to start sending socket msgs to our window
pRecvMC->StartRecv(hWnd);
fChange = TRUE;
break;
case MSG_STOP_RECV:
// Stop receiving on the specified media stream
DEBUGMSG(ZONE_VERBOSE,("%s: MSG_STOP_RECV\n",_fx_));
pRecvMC = (RecvMediaStream *)msg.lParam;
// call the stream to cancel outstanding recvs etc.
// currently we assume this can be done synchronously
pRecvMC->StopRecv();
fChange = TRUE;
break;
case MSG_EXIT_RECV:
// Exit the recv thread.
// Assume that we are not currently receving on any stream.
DEBUGMSG(ZONE_VERBOSE,("%s: MSG_EXIT_RECV\n",_fx_));
fChange = TRUE;
if (DestroyWindow(hWnd)) {
break;
}
DEBUGMSG(ZONE_DP,("DestroyWindow returned %d\n",GetLastError()));
// fall thru to PostQuitMessage()
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_TIMER:
if (msg.hwnd == NULL) {
// this timer is for the benefit of ThreadTimer::UpdateTime()
// however, we are calling UpdateTime after every message (see below)
// so we dont do anything special here.
break;
}
default:
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (fChange) {
// the thread MSGs need to be acked
SetEvent(m_hRecvThreadAckEvent);
fChange = FALSE;
}
t = m_RecvTimer.UpdateTime(curTime=GetTickCount());
if (t != nextUpdateTime) {
// Thread timer wants to change its update time
nextUpdateTime = t;
if (timerId) {
KillTimer(NULL,timerId);
timerId = 0;
}
// if nextTime is zero, there are no scheduled timeouts so we dont need to call UpdateTime
if (nextUpdateTime)
timerId = SetTimer(NULL, 0, nextUpdateTime - curTime + 50, NULL);
}
}
CLEANUPEXIT:
DEBUGMSG(ZONE_DP,("%s terminating.\n", _fx_));
return hr;
}
#endif
/*
Winsock 2 receive thread. Main differnce here is that it has a WaitEx loop
where we wait for Start/Stop commands from the datapump while allowing
WS2 APCs to be handled.
Note: Only way to use the same thread routine for WS1 and WS2 is with
MsgWaitForMultipleObjectsEx, which unfortunately is not implemented in Win95
*/
DWORD
DataPump::CommonWS2RecvThread (void )
{
HRESULT hr;
RecvMediaStream *pRecvMC;
BOOL fChange = FALSE, fExit = FALSE;
DWORD dwWaitStatus;
DWORD curTime, t;
FX_ENTRY ("DP::WS2RecvTh")
SetThreadPriority(m_hRecvThread, THREAD_PRIORITY_ABOVE_NORMAL);
// notify thread creator that we're ready to recv messages
SetEvent(m_hRecvThreadAckEvent);
while (!fExit) {
// Wait for control messages from Start()/Stop() or Winsock async
// thread callbacks
// dispatch expired timeouts and check how long we need to wait
t = m_RecvTimer.UpdateTime(curTime=GetTickCount());
t = (t ? t-curTime+50 : INFINITE);
dwWaitStatus = WaitForSingleObjectEx(m_hRecvThreadSignalEvent,t,TRUE);
if (dwWaitStatus == WAIT_OBJECT_0) {
switch(m_CurRecvMsg) {
case MSG_START_RECV:
// Start receiving on the specified media stream
DEBUGMSG(ZONE_VERBOSE,("%s: MSG_START_RECV\n",_fx_));
pRecvMC = m_pCurRecvStream;
// call the stream to post recv buffers and
// tell Winsock to start sending socket msgs to our window
pRecvMC->StartRecv(NULL);
fChange = TRUE;
break;
case MSG_STOP_RECV:
// Stop receiving on the specified media stream
DEBUGMSG(ZONE_VERBOSE,("%s: MSG_STOP_RECV\n",_fx_));
pRecvMC = m_pCurRecvStream;
// call the stream to cancel outstanding recvs etc.
// currently we assume this can be done synchronously
pRecvMC->StopRecv();
fChange = TRUE;
break;
case MSG_EXIT_RECV:
// Exit the recv thread.
// Assume that we are not currently receving on any stream.
DEBUGMSG(ZONE_VERBOSE,("%s: MSG_EXIT_RECV\n",_fx_));
fChange = TRUE;
fExit = TRUE;
break;
case MSG_PLAY_SOUND:
fChange = TRUE;
pRecvMC->OnDTMFBeep();
break;
default:
// shouldnt be anything else
ASSERT(0);
}
if (fChange) {
// the thread MSGs need to be acked
SetEvent(m_hRecvThreadAckEvent);
fChange = FALSE;
}
} else if (dwWaitStatus == WAIT_IO_COMPLETION) {
// nothing to do here
} else if (dwWaitStatus != WAIT_TIMEOUT) {
DEBUGMSG(ZONE_DP,("%s: Wait failed with %d",_fx_,GetLastError()));
fExit=TRUE;
}
}
DEBUGMSG(ZONE_DP,("%s terminating.\n", _fx_));
return 0;
}
void ThreadTimer::SetTimeout(TTimeout *pTObj)
{
DWORD time = pTObj->GetDueTime();
// insert in increasing order of timeout
for (TTimeout *pT = m_TimeoutList.pNext; pT != &m_TimeoutList; pT = pT->pNext) {
if ((int)(pT->m_DueTime- m_CurTime) > (int) (time - m_CurTime))
break;
}
pTObj->InsertAfter(pT->pPrev);
}
void ThreadTimer::CancelTimeout(TTimeout *pTObj)
{
pTObj->Remove(); // remove from list
}
// Called by thread with the current time as input (usually obtained from GetTickCount())
// Returns the time by which UpdateTime() should be called again or currentTime+0xFFFFFFFF if there
// are no scheduled timeouts
DWORD ThreadTimer::UpdateTime(DWORD curTime)
{
TTimeout *pT;
m_CurTime = curTime;
// figure out which timeouts have elapsed and do the callbacks
while (!IsEmpty()) {
pT = m_TimeoutList.pNext;
if ((int)(pT->m_DueTime-m_CurTime) <= 0) {
pT->Remove();
pT->TimeoutIndication();
} else
break;
}
return (IsEmpty() ? m_CurTime+INFINITE : m_TimeoutList.pNext->m_DueTime);
}