You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1157 lines
30 KiB
1157 lines
30 KiB
|
|
#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);
|
|
}
|
|
|